From d9d057da07729692f35834a896205b6d094ec417 Mon Sep 17 00:00:00 2001 From: Andrew Gene Brown Date: Mon, 22 Mar 2021 20:23:41 -0700 Subject: [PATCH] Convert NASIS database access from RODBC to DBI/odbc (#149) * Convert NASIS-related queries from RODBC->DBI #146 * remove require RODBC * roxygen for fetchNASIS * Color data NA moist state handling * use_sqlite = FALSE is the default for .openNASISchannel * Move VARCHAR(MAX) fields to end of query, per MSSQL specs * fix getHzErrorsNASIS * Validations and tests RE: #149 #146 * Move to fetchNASIS tests (skip on remote) * Add skip() and better handling of missing DB/no data * Use local_NASIS_defined everywhere w/ odbc::odbcListDataSources * cherry-pick: make a proper interface to sqlite NASIS queries cherry-pick: :dna: Stitch SQLite data source API up to fetchNASIS cherry-pick: Update demo cherry-pick: Default code use 25cm, as proposed Validate/fix NASIS methods * Change name of path argument * fetchNASIS: Remove RIGHT JOIN in geomorphic features query and turn off the MSSQL specific syntax used for `d.rf.data.v2` * Method for cloning local NASIS tables into static SQLite file (#154) * Method for cloning local NASIS tables into static SQLite file #149 * local_NASIS_defined: add static_path arg and use RSQLite::dbCanConnect * Add selected set and static path to getHzErrorsNASIS * get_cosoilmoist / get_vegplot should use selected set argument * Special handling for local_NASIS_defined (does not take SS arg) * Add static_path to local_NASIS_defined * Roxygen: markdown = TRUE Broken docs md-doc: Fix single bracket semantics * Update docs * forgot to commit this one * NEWS / version 2.6.x * Rd2roxygen initial conversion; old in ./manbak * fix \href{} * passing check_man * Rd2roxygen (#162) * Rd2roxygen initial conversion; old in ./manbak * fix \href{} * passing check_man * fixes for R CMD check * deprecate old manpages * Revert existing roxygen back to human-made * get_extended_data_from_NASIS_db: artifact data query should respect SS=FALSE * get extended NASIS photo text notes: check for paths >260 chars * proper sequence to get SQLite pedon snapshot fetchNASIS-ready * Use checkHzDepthLogic (fast=TRUE) in fetchNASIS_pedons * .fetchNASIS_pedons: Use data.table for extended data processing * get_extended_data_from_NASIS_db: Replace plyr::join * dbQueryNASIS: vectorize/test; dbConnectNASIS: add NASIS() alias * docs * Add test for local NASIS DBI issues * Fix for get_cosoilmoist_from_NASISWebReport example * Fix for list output of createStaticNASIS * Close RODBC connection used in tests * Testing some pedon_table_column checks @jskovlin * aqp::union has been removed from namespace * test-fetchKSSL: hide txtProgressBar when running tests * Refactoring utils.R for data.table in fetchNASIS flattening * Docs * Oops DBI/odbc/RSQLite back in Imports * Missing comma * Docs * Mopping up * Move driver packages (odbc, RSQLite) to Suggests * get_cotext_from_NASIS_db: Add support for static_path argument and use dbQueryNASIS * get_cotext_from_NASIS_db: check for try-error * .formatParentMaterialString: Return NA_character_ for conformal data.frame with NULL data * Remove dangling require RODBC * fetchNASIS: extended data flattening handle NULL table contents * createStaticNASIS: better default arguments * Fixes for selected set argument (found by drop all _View_1 tables in a static DB) * Updates to nasisDBI "demo" that runs all the NASIS methods by all the methods * Remove requireNamespace("RODBC")--merge artifact? * demo createStaticNASIS workflow * Add WCS/SDA viz to demo * Fix bug in decoding of horizon data; thanks @dylanbeaudette * Pass through static_path argument to uncode() * Fix get_comonth_from_NASIS_db fill=TRUE * Update demos * Small adjustments to default args for demo/comparisons * Rename static_path arg to dsn * Rename static_path arg to dsn (docs) * Version bump + update README * Update NEWS.md --- DESCRIPTION | 12 +- NAMESPACE | 7 + NEWS.md | 8 +- R/OSDquery.R | 49 +- R/ROSETTA.R | 39 +- R/SDA-spatial.R | 91 +- R/SSURGO_spatial_query.R | 40 +- R/STR.R | 58 +- R/createStaticNASIS.R | 155 ++ R/dbQueryNASIS.R | 62 + R/fetchHenry.R | 68 + R/fetchKSSL.R | 108 +- R/fetchNASIS.R | 189 +- R/fetchNASISLabData.R | 133 +- R/fetchNASIS_components.R | 264 ++- R/fetchNASIS_pedons.R | 224 ++- R/fetchNOAA.R | 2 +- R/fetchPedonPC.R | 194 +- R/fetchRaCA.R | 84 +- R/fetchSCAN.R | 171 +- R/fetchSDA_spatial.R | 8 +- R/fetchVegdata.R | 68 +- R/getHzErrorsNASIS.R | 78 +- R/get_RMF_from_NASIS_db.R | 27 +- R/get_colors_from_NASIS_db.R | 42 +- R/get_colors_from_pedon_db.R | 15 + R/get_component_data_from_NASIS_db.R | 425 ++-- R/get_component_from_GDB.R | 60 +- R/get_component_from_LIMS.R | 320 --- R/get_component_from_SDA.R | 170 +- R/get_concentrations_from_NASIS_db.R | 25 +- R/get_cosoilmoist_from_NASIS.R | 67 +- R/get_extended_data_from_NASIS_db.R | 247 ++- R/get_extended_data_from_pedon_db.R | 15 + R/get_hz_data_from_NASIS_db.R | 50 +- R/get_hz_data_from_pedon_db.R | 17 + R/get_lablayer_data_from_NASIS_db.R | 38 +- R/get_labpedon_data_from_NASIS_db.R | 40 +- R/get_phfmp_from_NASIS_db.R | 23 +- R/get_phlabresults_data_from_NASIS_db.R | 22 +- R/get_projectmapunit_from_NASIS.R | 23 +- R/get_site_data_from_NASIS_db.R | 63 +- R/get_site_data_from_pedon_db.R | 15 + R/get_soilseries_from_NASIS.R | 50 +- R/get_text_notes_from_NASIS_db.R | 99 +- R/get_veg_data_from_NASIS_db.R | 116 +- R/get_veg_from_AK_Site.R | 16 +- R/get_veg_from_MT_veg_db.R | 14 + R/get_veg_from_NPS_PLOTS_db.R | 16 + R/get_veg_other_from_MT_veg_db.R | 14 + R/get_veg_species_from_MT_veg_db.R | 14 + R/get_vegplot_data_from_NASIS_db.R | 302 ++- R/mix_and_clean_colors.R | 39 +- R/openNASISchannel.R | 84 +- R/parseWebReport.R | 27 +- R/siblings.R | 50 +- R/simplfyFragmentData.R | 31 +- R/simplifyColorData.R | 81 +- R/soilDB-package.R | 123 ++ R/uncode.R | 263 ++- R/utils.R | 1713 +++++++++-------- R/waterDayYear.R | 25 +- README.Rmd | 13 +- README.md | 40 +- man/ISSR800.wcs.Rd | 4 +- man/KSSL_VG_model.Rd | 14 +- man/OSDquery.Rd | 42 +- man/ROSETTA.Rd | 46 +- man/SCAN_SNOTEL_metadata.Rd | 47 +- man/SDA_query.Rd | 2 +- man/SDA_spatialQuery.Rd | 43 +- man/STRplot.Rd | 52 +- man/SoilWeb_spatial_query.Rd | 61 + man/createStaticNASIS.Rd | 50 + man/dbConnectNASIS.Rd | 20 + man/dbQueryNASIS.Rd | 23 + man/dot-dump_NASIS_table.Rd | 20 + man/estimateSTR.Rd | 69 +- man/fetchGDB.Rd | 97 +- man/fetchHenry.Rd | 102 +- man/fetchKSSL.Rd | 116 +- man/fetchNASIS.Rd | 130 +- man/fetchNASISLabData.Rd | 41 +- man/fetchNASISWebReport.Rd | 24 +- man/fetchOSD.Rd | 56 +- man/fetchPedonPC.Rd | 47 +- man/fetchRaCA.Rd | 20 +- man/fetchSCAN.Rd | 59 +- man/fetchSDA.Rd | 200 ++ man/fetchSDA_spatial.Rd | 4 +- man/fetchVegdata.Rd | 40 + man/format_SQL_in_statement.Rd | 2 +- man/getHzErrorsNASIS.Rd | 25 + man/get_NOAA_GHCND.Rd | 2 +- man/get_colors_from_NASIS_db.Rd | 37 +- man/get_colors_from_pedon_db.Rd | 32 +- man/get_comonth_from_NASIS_db.Rd | 56 +- man/get_component_data_from_NASIS_db.Rd | 50 +- man/get_cosoilmoist_from_NASIS.Rd | 63 +- man/get_extended_data_from_NASIS_db.Rd | 57 + man/get_extended_data_from_pedon_db.Rd | 53 +- man/get_hz_data_from_NASIS_db.Rd | 42 +- man/get_hz_data_from_pedon_db.Rd | 39 +- man/get_lablayer_data_from_NASIS_db.Rd | 39 +- man/get_labpedon_data_from_NASIS_db.Rd | 43 +- man/get_site_data_from_NASIS_db.Rd | 48 +- man/get_site_data_from_pedon_db.Rd | 34 +- man/get_soilseries_from_NASIS.Rd | 40 +- man/get_text_notes_from_NASIS_db.Rd | 39 +- man/get_veg_data_from_NASIS_db.Rd | 34 +- man/get_veg_from_AK_Site.Rd | 32 +- man/get_veg_from_MT_veg_db.Rd | 34 +- man/get_veg_from_NPS_PLOTS_db.Rd | 35 +- man/get_veg_other_from_MT_veg_db.Rd | 34 +- man/get_veg_species_from_MT_veg_db.Rd | 34 +- man/loafercreek.Rd | 140 +- man/local_NASIS_defined.Rd | 11 +- man/mix_and_clean_colors.Rd | 22 + man/mukey.wcs.Rd | 4 +- man/parseWebReport.Rd | 44 +- man/processSDA_WKT.Rd | 12 +- man/siblings.Rd | 73 +- man/simplifyColorData.Rd | 73 +- man/simplifyFragmentData.Rd | 44 + man/soilDB-package.Rd | 37 +- man/uncode.Rd | 93 +- man/us_ss_timeline.Rd | 41 +- man/waterDayYear.Rd | 47 +- misc/DBI-uncoding-test.R | 29 + misc/NASIS-RV-copedon.R | 140 ++ misc/NASIS-lite.R | 263 +++ misc/ca750-newpedons.R | 86 + misc/man-deprecated/ISSR800.wcs.Rd | 41 + misc/man-deprecated/KSSL_VG_model.Rd | 61 + misc/man-deprecated/OSDquery.Rd | 97 + misc/man-deprecated/ROSETTA.Rd | 92 + misc/man-deprecated/SCAN_SNOTEL_metadata.Rd | 34 + misc/man-deprecated/SDA_spatialQuery.Rd | 187 ++ .../man-deprecated}/SSURGO_spatial_query.Rd | 0 misc/man-deprecated/STRplot.Rd | 41 + misc/man-deprecated/WCS_details.Rd | 21 + misc/man-deprecated/createStaticNASIS.Rd | 38 + misc/man-deprecated/dbConnectNASIS.Rd | 17 + misc/man-deprecated/dbQueryNASIS.Rd | 23 + misc/man-deprecated/dot-dump_NASIS_table.Rd | 19 + misc/man-deprecated/estimateColorMixture.Rd | 27 + misc/man-deprecated/estimateSTR.Rd | 44 + misc/man-deprecated/fetchGDB.Rd | 83 + misc/man-deprecated/fetchHenry.Rd | 63 + misc/man-deprecated/fetchKSSL.Rd | 89 + misc/man-deprecated/fetchNASIS.Rd | 79 + misc/man-deprecated/fetchNASISLabData.Rd | 20 + misc/man-deprecated/fetchNASISWebReport.Rd | 158 ++ misc/man-deprecated/fetchPedonPC.Rd | 26 + misc/man-deprecated/fetchSCAN.Rd | 54 + .../man-deprecated}/fetchSDA_component.Rd | 0 misc/man-deprecated/fetchSDA_spatial.Rd | 74 + misc/man-deprecated/fetchSoilGrids.Rd | 43 + misc/man-deprecated/filter_geochem.Rd | 34 + .../man-deprecated/format_SQL_in_statement.Rd | 62 + misc/man-deprecated/getHzErrorsNASIS.Rd | 21 + misc/man-deprecated/get_NOAA_GHCND.Rd | 38 + .../get_NOAA_stations_nearXY.Rd | 34 + .../get_colors_from_NASIS_db.Rd | 24 + .../get_colors_from_pedon_db.Rd | 26 + .../get_comonth_from_NASIS_db.Rd | 42 + .../get_component_data_from_NASIS_db.Rd | 42 + .../get_cosoilmoist_from_NASIS.Rd | 37 + .../get_extended_data_from_NASIS.Rd | 0 .../get_extended_data_from_pedon_db.Rd | 25 + .../get_hz_data_from_NASIS_db.Rd | 27 + .../get_hz_data_from_pedon_db.Rd | 27 + .../get_lablayer_data_from_NASIS_db.Rd | 23 + .../get_labpedon_data_from_NASIS_db.Rd | 23 + .../get_site_data_from_NASIS_db.Rd | 23 + .../get_site_data_from_pedon_db.Rd | 28 + .../get_soilseries_from_NASIS.Rd | 26 + .../get_text_notes_from_NASIS_db.Rd | 42 + .../get_veg_data_from_NASIS_db.Rd | 31 + misc/man-deprecated/get_veg_from_AK_Site.Rd | 26 + misc/man-deprecated/get_veg_from_MT_veg_db.Rd | 25 + .../get_veg_from_NPS_PLOTS_db.Rd | 20 + .../get_veg_other_from_MT_veg_db.Rd | 25 + .../get_veg_species_from_MT_veg_db.Rd | 25 + misc/man-deprecated/loafercreek.Rd | 73 + misc/man-deprecated/local_NASIS_defined.Rd | 25 + misc/man-deprecated/makeChunks.Rd | 28 + misc/man-deprecated/mukey.wcs.Rd | 41 + misc/man-deprecated/parseWebReport.Rd | 30 + misc/man-deprecated/processSDA_WKT.Rd | 30 + misc/man-deprecated/seriesExtent.Rd | 63 + misc/man-deprecated/siblings.Rd | 60 + .../man-deprecated}/simplfyFragmentData.Rd | 0 misc/man-deprecated/simplifyColorData.Rd | 38 + misc/man-deprecated/soilDB-package.Rd | 15 + misc/man-deprecated/taxaExtent.Rd | 82 + misc/man-deprecated/uncode.Rd | 53 + misc/man-deprecated/us_ss_timeline.Rd | 33 + misc/man-deprecated/waterDayYear.Rd | 38 + .../check_pedon_table_columns.R | 64 + .../pedon_table_columns.txt | 69 + misc/run-all-NASIS-get-methods.R | 120 ++ tests/testthat/test-DBI.R | 42 + tests/testthat/test-dbQueryNASIS.R | 32 + tests/testthat/test-fetchNASIS.R | 65 +- 205 files changed, 9822 insertions(+), 3804 deletions(-) create mode 100644 R/createStaticNASIS.R create mode 100644 R/dbQueryNASIS.R create mode 100644 R/soilDB-package.R create mode 100644 man/SoilWeb_spatial_query.Rd create mode 100644 man/createStaticNASIS.Rd create mode 100644 man/dbConnectNASIS.Rd create mode 100644 man/dbQueryNASIS.Rd create mode 100644 man/dot-dump_NASIS_table.Rd create mode 100644 man/fetchSDA.Rd create mode 100644 man/fetchVegdata.Rd create mode 100644 man/getHzErrorsNASIS.Rd create mode 100644 man/get_extended_data_from_NASIS_db.Rd create mode 100644 man/mix_and_clean_colors.Rd create mode 100644 man/simplifyFragmentData.Rd create mode 100644 misc/DBI-uncoding-test.R create mode 100644 misc/NASIS-RV-copedon.R create mode 100644 misc/NASIS-lite.R create mode 100644 misc/ca750-newpedons.R create mode 100644 misc/man-deprecated/ISSR800.wcs.Rd create mode 100644 misc/man-deprecated/KSSL_VG_model.Rd create mode 100644 misc/man-deprecated/OSDquery.Rd create mode 100644 misc/man-deprecated/ROSETTA.Rd create mode 100644 misc/man-deprecated/SCAN_SNOTEL_metadata.Rd create mode 100644 misc/man-deprecated/SDA_spatialQuery.Rd rename {man => misc/man-deprecated}/SSURGO_spatial_query.Rd (100%) create mode 100644 misc/man-deprecated/STRplot.Rd create mode 100644 misc/man-deprecated/WCS_details.Rd create mode 100644 misc/man-deprecated/createStaticNASIS.Rd create mode 100644 misc/man-deprecated/dbConnectNASIS.Rd create mode 100644 misc/man-deprecated/dbQueryNASIS.Rd create mode 100644 misc/man-deprecated/dot-dump_NASIS_table.Rd create mode 100644 misc/man-deprecated/estimateColorMixture.Rd create mode 100644 misc/man-deprecated/estimateSTR.Rd create mode 100644 misc/man-deprecated/fetchGDB.Rd create mode 100644 misc/man-deprecated/fetchHenry.Rd create mode 100644 misc/man-deprecated/fetchKSSL.Rd create mode 100644 misc/man-deprecated/fetchNASIS.Rd create mode 100644 misc/man-deprecated/fetchNASISLabData.Rd create mode 100644 misc/man-deprecated/fetchNASISWebReport.Rd create mode 100644 misc/man-deprecated/fetchPedonPC.Rd create mode 100644 misc/man-deprecated/fetchSCAN.Rd rename {man => misc/man-deprecated}/fetchSDA_component.Rd (100%) create mode 100644 misc/man-deprecated/fetchSDA_spatial.Rd create mode 100644 misc/man-deprecated/fetchSoilGrids.Rd create mode 100644 misc/man-deprecated/filter_geochem.Rd create mode 100644 misc/man-deprecated/format_SQL_in_statement.Rd create mode 100644 misc/man-deprecated/getHzErrorsNASIS.Rd create mode 100644 misc/man-deprecated/get_NOAA_GHCND.Rd create mode 100644 misc/man-deprecated/get_NOAA_stations_nearXY.Rd create mode 100644 misc/man-deprecated/get_colors_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_colors_from_pedon_db.Rd create mode 100644 misc/man-deprecated/get_comonth_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_component_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_cosoilmoist_from_NASIS.Rd rename {man => misc/man-deprecated}/get_extended_data_from_NASIS.Rd (100%) create mode 100644 misc/man-deprecated/get_extended_data_from_pedon_db.Rd create mode 100644 misc/man-deprecated/get_hz_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_hz_data_from_pedon_db.Rd create mode 100644 misc/man-deprecated/get_lablayer_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_labpedon_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_site_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_site_data_from_pedon_db.Rd create mode 100644 misc/man-deprecated/get_soilseries_from_NASIS.Rd create mode 100644 misc/man-deprecated/get_text_notes_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_veg_data_from_NASIS_db.Rd create mode 100644 misc/man-deprecated/get_veg_from_AK_Site.Rd create mode 100644 misc/man-deprecated/get_veg_from_MT_veg_db.Rd create mode 100644 misc/man-deprecated/get_veg_from_NPS_PLOTS_db.Rd create mode 100644 misc/man-deprecated/get_veg_other_from_MT_veg_db.Rd create mode 100644 misc/man-deprecated/get_veg_species_from_MT_veg_db.Rd create mode 100644 misc/man-deprecated/loafercreek.Rd create mode 100644 misc/man-deprecated/local_NASIS_defined.Rd create mode 100644 misc/man-deprecated/makeChunks.Rd create mode 100644 misc/man-deprecated/mukey.wcs.Rd create mode 100644 misc/man-deprecated/parseWebReport.Rd create mode 100644 misc/man-deprecated/processSDA_WKT.Rd create mode 100644 misc/man-deprecated/seriesExtent.Rd create mode 100644 misc/man-deprecated/siblings.Rd rename {man => misc/man-deprecated}/simplfyFragmentData.Rd (100%) create mode 100644 misc/man-deprecated/simplifyColorData.Rd create mode 100644 misc/man-deprecated/soilDB-package.Rd create mode 100644 misc/man-deprecated/taxaExtent.Rd create mode 100644 misc/man-deprecated/uncode.Rd create mode 100644 misc/man-deprecated/us_ss_timeline.Rd create mode 100644 misc/man-deprecated/waterDayYear.Rd create mode 100644 misc/nasis_pedon_object/check_pedon_table_columns.R create mode 100644 misc/nasis_pedon_object/pedon_table_columns.txt create mode 100644 misc/run-all-NASIS-get-methods.R create mode 100644 tests/testthat/test-DBI.R create mode 100644 tests/testthat/test-dbQueryNASIS.R diff --git a/DESCRIPTION b/DESCRIPTION index 0fd4dfae..5774aa24 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: soilDB Type: Package Title: Soil Database Interface -Version: 2.6.0 -Date: 2021-02-18 +Version: 2.6.1 +Date: 2021-03-22 Authors@R: c(person(given="Dylan", family="Beaudette", role = c("aut"), email = "dylan.beaudette@usda.gov"), person(given="Jay", family="Skovlin", role = c("aut")), person(given="Stephen", family="Roecker", role = c("aut")), @@ -14,11 +14,11 @@ License: GPL (>= 3) LazyLoad: yes Depends: R (>= 3.5.0) Imports: aqp, grDevices, graphics, stats, utils, plyr, xml2, sp, reshape2, - raster, curl, lattice, methods, data.table -Suggests: rgdal, jsonlite, RODBC, httr, sf, rgeos, rvest, - testthat, latticeExtra, - ggplot2, gridExtra, viridisLite, mapview, rasterVis + raster, curl, lattice, methods, data.table, DBI +Suggests: rgdal, jsonlite, RODBC, httr, sf, rgeos, rvest, odbc, RSQLite, + testthat, latticeExtra, gridExtra, ggplot2, viridisLite, mapview, rasterVis Repository: CRAN URL: http://ncss-tech.github.io/AQP/ BugReports: https://github.com/ncss-tech/soilDB/issues RoxygenNote: 7.1.1 +Roxygen: list(markdown = TRUE) diff --git a/NAMESPACE b/NAMESPACE index 5f342c9b..4488a4ef 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -16,6 +16,13 @@ importFrom(data.table, data.table, as.data.table) +importFrom(DBI, + dbGetQuery, + dbConnect, + dbSendQuery, + dbFetch +) + importFrom(reshape2, dcast, melt diff --git a/NEWS.md b/NEWS.md index 0ff4fbdb..f841c3f2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,7 +1,13 @@ +# soilDB 2.6.1 (2021-03-22) + * Connections to the local NASIS database now use `DBI` and `odbc` instead of `RODBC` + * Two new methods `dbConnectNASIS` and `dbQueryNASIS` facilitate access with read-only credentials, submission of queries/fetching of results, and closing the DBI connection upon completion + * Use `dsn` argument to specify a local "static" SQLite file containing NASIS tables + * Default argument `dsn = NULL` uses `"nasis_local"` [ODBC connection](http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html) to a local NASIS SQL Server instance + # soilDB 2.6.0 (2021-02-18) * `OSDquery` gets a new argument (`everything`) for searching the entire document * `fetchNASIS(..., rmHzErrors=TRUE)` -- spurious removals of data due to missing "extended" records. `fetchNASIS` now uses `aqp::horizons<-` after building a minimal `SoilProfileCollection` from NASIS site and horizon tables. This allows `aqp` integrity methods to trigger where needed--preventing unintentional re-ordering or removals of "valid" horizon data. - + # soilDB 2.5.9 (2021-01-26) * `HenryTimeLine` moved to {sharpshootR} package * new functions `mukey.wcs()` and `ISSR800.wcs()` for hitting web coverage service (WCS) for gSSURGO, gNATSGO, and ISSR-800 grids diff --git a/R/OSDquery.R b/R/OSDquery.R index 2023d028..d1b9f40f 100644 --- a/R/OSDquery.R +++ b/R/OSDquery.R @@ -42,41 +42,38 @@ #' - combine search terms into a single expression: (grano:* | granite) #' #' Related documentation can be found in the following tutorials -#' \itemize{ -#' \item{\href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}} -#' -#' \item{\href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}} -#' -#' \item{\href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}} -#' } -#' +#' - [overview of all soil series query functions](http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html) +#' - [competing soil series](https://ncss-tech.github.io/AQP/soilDB/competing-series.html) +#' - [siblings](https://ncss-tech.github.io/AQP/soilDB/siblings.html) +#' +#' #' @references \url{https://www.nrcs.usda.gov/wps/portal/nrcs/detailfull/soils/home/?cid=nrcs142p2_053587} -#' +#' #' @author D.E. Beaudette -#' +#' #' @note SoilWeb maintains a snapshot of the Official Series Description data. -#' +#' #' @seealso \code{\link{fetchOSD}, \link{siblings}, \link{fetchOSD}} -#' +#' #' @keywords manip -#' +#' #' @return a \code{data.frame} object containing soil series names that match patterns supplied as arguments. #' @export #' #' @examples -#' -#' +#' +#' #' \donttest{ #' if(requireNamespace("curl") & #' curl::has_internet() & #' require(aqp)) { -#' +#' #' # find all series that list Pardee as a geographically associated soil. #' s <- OSDquery(geog_assoc_soils = 'pardee') -#' +#' #' # get data for these series #' x <- fetchOSD(s$series, extended = TRUE, colorState = 'dry') -#' +#' #' # simple figure #' par(mar=c(0,0,1,1)) #' plot(x$SPC) @@ -84,11 +81,11 @@ #' } #' OSDquery <- function(everything = NULL, mlra='', taxonomic_class='', typical_pedon='', brief_narrative='', ric='', use_and_veg='', competing_series='', geog_location='', geog_assoc_soils='') { - + # check for required packages if(!requireNamespace('httr', quietly=TRUE) | !requireNamespace('jsonlite', quietly=TRUE)) stop('please install the `httr` and `jsonlite` packages', call.=FALSE) - + # sanity checks # mode selection @@ -129,27 +126,25 @@ OSDquery <- function(everything = NULL, mlra='', taxonomic_class='', typical_ped # note: this is the load-balancer u <- 'https://casoilresource.lawr.ucdavis.edu/osd-search/search-entire-osd.php' } - - - + # submit via POST res <- httr::POST(u, body = parameters, encode='form') # TODO: figure out what an error state looks like # trap errors, likely related to SQL syntax errors request.status <- try(httr::stop_for_status(res), silent = TRUE) - + # the result is JSON # should simplify to data.frame nicely r.content <- httr::content(res, as = 'text', encoding = 'UTF-8') d <- jsonlite::fromJSON(r.content) - + # results will either be: data.frame, empty list, or NULL - + # ensure result is either data.frame or NULL if(inherits(d, 'list') & length(d) < 1) return(NULL) - + return(d) } diff --git a/R/ROSETTA.R b/R/ROSETTA.R index 015215c0..02ff45e9 100644 --- a/R/ROSETTA.R +++ b/R/ROSETTA.R @@ -96,45 +96,20 @@ #' @details Soil properties supplied in \code{x} must be described, in order, via \code{vars} argument. The API does not use the names but column ordering must follow: sand, silt, clay, bulk density, volumetric water content at 33kPa (1/3 bar), and volumetric water content at 1500kPa (15 bar). #' #' The ROSETTA model relies on a minimum of 3 soil properties, with increasing (expected) accuracy as additional properties are included: -#' \itemize{ -#' \item{required, sand, silt, clay: }{USDA soil texture separates (percentages) that sum to 100\%} -#' \item{optional, bulk density (any moisture basis): }{mass per volume after accounting for >2mm fragments, units of gm/cm3} -#' \item{optional, volumetric water content at 33 kPa: }{roughly "field capacity" for most soils, units of cm^3/cm^3} -#' \item{optional, volumetric water content at 1500 kPa: }{roughly "permanent wilting point" for most plants, units of cm^3/cm^3} -#' } +#' - required, sand, silt, clay: USDA soil texture separates (percentages) that sum to 100\% +#' - optional, bulk density (any moisture basis): mass per volume after accounting for >2mm fragments, units of gm/cm3 +#' - optional, volumetric water content at 33 kPa: roughly "field capacity" for most soils, units of cm^3/cm^3 +#' - optional, volumetric water content at 1500 kPa: roughly "permanent wilting point" for most plants, units of cm^3/cm^3 #' #' Column names not specified in \code{vars} are retained in the output. #' #' Three versions of the ROSETTA model are available, selected using \code{v = 1}, \code{v = 2}, or \code{v = 3}. #' -#' \describe{ +#' - version 1 - Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}. #' -#' \item{version 1}{Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}}. +#' - version 2 - Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}. #' -#' \item{version 2}{Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}}. -#' -#' -#' \item{version 3}{Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}}. -#' } -#' -#' @note Input data should not contain columns names that will conflict with the ROSETTA API results: `theta_r`, `theta_s`, `alpha`, `npar`, `ksat`. -#' -#' @return a \code{data.frame} object: -#' -#' \describe{ -#' -#' \item{... }{columns present in \code{x}} -#' -#' \item{theta_r: }{residual volumetric water content (cm^3/cm^3)} -#' \item{theta_s: }{saturated volumetric water content (cm^3/cm^3)} -#' \item{alpha:}{related to the inverse of the air entry suction, log10-transformed values with units of cm} -#' \item{npar: }{index of pore size distribution, log10-transformed values with units of 1/cm} -#' \item{ksat: }{saturated hydraulic conductivity, log10-transformed values with units of cm/day} -#' -#' \item{.rosetta.model}{best-available model selection (-1 signifies that prediction was not possible due to missing values in \code{x})} -#' \item{.rosetta.version}{ROSETTA algorithm version, selected via function argument \code{v}} -#' -#' } +#' - version 3 - Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}. #' #' @references #' Consider using the interactive version, with copy/paste functionality at: \url{https://www.handbook60.org/rosetta}. diff --git a/R/SDA-spatial.R b/R/SDA-spatial.R index a4fa51f0..b825e1c8 100644 --- a/R/SDA-spatial.R +++ b/R/SDA-spatial.R @@ -1,4 +1,3 @@ - ## chunked queries for large number of records: # https://github.com/ncss-tech/soilDB/issues/71 @@ -16,22 +15,25 @@ ## TODO: geometry collections are not allowed in sp objects.. ## TODO: consider moving to sf -#' @title Post-process WKT returned from SDA. + + +#' Post-process WKT returned from SDA. +#' +#' This is a helper function, commonly used with \code{SDA_query} to extract +#' WKT (well-known text) representation of geometry to an sp-class object. #' -#' @description This is a helper function, commonly used with \code{SDA_query} to extract WKT (well-known text) representation of geometry to an sp-class object. +#' The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov}. +#' See the [SDA Tutorial](http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html) for detailed examples. #' -#' @param d \code{data.frame} returned by \code{SDA_query}, containing WKT representation of geometry +#' @param d \code{data.frame} returned by \code{SDA_query}, containing WKT +#' representation of geometry #' @param g name of column in \code{d} containing WKT geometry #' @param p4s PROJ4 CRS definition, typically GCS WGS84 -#' -#' @details The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov}. See the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for detailed examples. -#' -#' @note This function requires the `httr`, `jsonlite`, `XML`, and `rgeos` packages. -#' -#' @author D.E. Beaudette -#' #' @return A \code{Spatial*} object. -#' +#' @note This function requires the \code{httr}, \code{jsonlite}, \code{XML}, +#' and \code{rgeos} packages. +#' @author D.E. Beaudette +#' @export processSDA_WKT processSDA_WKT <- function(d, g='geom', p4s='+proj=longlat +datum=WGS84') { # iterate over features (rows) and convert into list of SPDF p <- list() @@ -158,36 +160,53 @@ FROM geom_data; # 10-20x speed improvement over SDA_query_features -#' @title SDA Spatial Query + + +#' SDA Spatial Query #' -#' @description Query SDA (SSURGO / STATSGO) records via spatial intersection with supplied geometries. Input can be SpatialPoints, SpatialLines, or SpatialPolygons objects with a valid CRS. Map unit keys, overlapping polygons, or the spatial intersection of \code{geom} + SSURGO / STATSGO polygons can be returned. See details. +#' Query SDA (SSURGO / STATSGO) records via spatial intersection with supplied +#' geometries. Input can be SpatialPoints, SpatialLines, or SpatialPolygons +#' objects with a valid CRS. Map unit keys, overlapping polygons, or the +#' spatial intersection of \code{geom} + SSURGO / STATSGO polygons can be +#' returned. See details. #' -#' @param geom a Spatial* object, with valid CRS. May contain multiple features. -#' @param what a character vector specifying what to return. 'mukey': \code{data.frame} with intersecting map unit keys and names, \code{geom} overlapping or intersecting map unit polygons -#' @param geomIntersection logical; \code{FALSE}: overlapping map unit polygons returned, \code{TRUE}: intersection of \code{geom} + map unit polygons is returned. -#' @param db a character vector identifying the Soil Geographic Databases -#' ('SSURGO' or 'STATSGO') to query. Option \var{STATSGO} currently works -#' only in combination with \code{what = "geom"}. -#' -#' @return A \code{data.frame} if \code{what = 'mukey'}, otherwise \code{SpatialPolygonsDataFrame} object. +#' Queries for map unit keys are always more efficient vs. queries for +#' overlapping or intersecting (i.e. least efficient) features. \code{geom} is +#' converted to GCS / WGS84 as needed. Map unit keys are always returned when +#' using \code{what = "geom"}. #' +#' There is a 100,000 record limit and 32Mb JSON serializer limit, per query. +#' +#' SSURGO (detailed soil survey, typically 1:24,000 scale) and STATSGO +#' (generalized soil survey, 1:250,000 scale) data are stored together within +#' SDA. This means that queries that don't specify an area symbol may result in +#' a mixture of SSURGO and STATSGO records. See the examples below and the +#' [SDA Tutorial](http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html) +#' for details. +#' +#' @aliases SDA_spatialQuery SDA_make_spatial_query SDA_query_features +#' @param geom a Spatial* object, with valid CRS. May contain multiple +#' features. +#' @param what a character vector specifying what to return. 'mukey': +#' \code{data.frame} with intersecting map unit keys and names, \code{geom} +#' overlapping or intersecting map unit polygons +#' @param geomIntersection logical; \code{FALSE}: overlapping map unit polygons +#' returned, \code{TRUE}: intersection of \code{geom} + map unit polygons is +#' returned. +#' @param db a character vector identifying the Soil Geographic Databases +#' ('SSURGO' or 'STATSGO') to query. Option \var{STATSGO} currently works only +#' in combination with \code{what = "geom"}. +#' @return A \code{data.frame} if \code{what = 'mukey'}, otherwise +#' \code{SpatialPolygonsDataFrame} object. +#' @note Row-order is not preserved across features in \code{geom} and returned +#' object. Use \code{sp::over()} or similar functionality to extract from +#' results. Polygon area in acres is computed server-side when \code{what = +#' 'geom'} and \code{geomIntersection = TRUE}. #' @author D.E. Beaudette, A.G. Brown, D.R. Schlaepfer #' @seealso \code{\link{SDA_query}} #' @keywords manip -#' -#' @aliases SDA_make_spatial_query SDA_query_features -#' -#' @note Row-order is not preserved across features in \code{geom} and returned object. Use \code{sp::over()} or similar functionality to extract from results. Polygon area in acres is computed server-side when \code{what = 'geom'} and \code{geomIntersection = TRUE}. -#' -#' -#' @details Queries for map unit keys are always more efficient vs. queries for overlapping or intersecting (i.e. least efficient) features. \code{geom} is converted to GCS / WGS84 as needed. Map unit keys are always returned when using \code{what = "geom"}. -#' -#' There is a 100,000 record limit and 32Mb JSON serializer limit, per query. -#' -#' SSURGO (detailed soil survey, typically 1:24,000 scale) and STATSGO (generalized soil survey, 1:250,000 scale) data are stored together within SDA. This means that queries that don't specify an area symbol may result in a mixture of SSURGO and STATSGO records. See the examples below and the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for details. -#' -#' #' @examples +#' #' \donttest{ #' if(requireNamespace("curl") & #' curl::has_internet() & @@ -324,6 +343,8 @@ FROM geom_data; #' } #' } #' +#' +#' @export SDA_spatialQuery SDA_spatialQuery <- function(geom, what='mukey', geomIntersection=FALSE, db = c("SSURGO", "STATSGO")) { diff --git a/R/SSURGO_spatial_query.R b/R/SSURGO_spatial_query.R index bf2dbe53..72d0a638 100644 --- a/R/SSURGO_spatial_query.R +++ b/R/SSURGO_spatial_query.R @@ -1,5 +1,43 @@ - # currently only queries SoilWeb for mapunit-level data + + +#' Get SSURGO Data via Spatial Query +#' +#' Get SSURGO Data via Spatial Query to SoilWeb +#' +#' Data are currently available from SoilWeb. These data are a snapshot of the +#' "official" data. The snapshot date is encoded in the "soilweb_last_update" +#' column in the function return value. Planned updates to this function will +#' include a switch to determine the data source: "official" data via USDA-NRCS +#' servers, or a "snapshot" via SoilWeb. +#' +#' @param bbox a bounding box in WGS84 geographic coordinates, see examples +#' @param coords a coordinate pair in WGS84 geographic coordinates, see +#' examples +#' @param what data to query, currently ignored +#' @param source the data source, currently ignored +#' @return The data returned from this function will depend on the query style. +#' See examples below. +#' @note This function should be considered experimental; arguments, results, +#' and side-effects could change at any time. SDA now supports spatial queries, +#' consider using \code{\link{SDA_query_features}} instead. +#' @author D.E. Beaudette +#' @keywords manip +#' @examples +#' +#' \donttest{ +#' if(requireNamespace("curl") & +#' curl::has_internet()) { +#' +#' # query by bbox +#' SoilWeb_spatial_query(bbox=c(-122.05, 37, -122, 37.05)) +#' +#' # query by coordinate pair +#' SoilWeb_spatial_query(coords=c(-121, 38)) +#' } +#' } +#' +#' @export SoilWeb_spatial_query SoilWeb_spatial_query <- function(bbox=NULL, coords=NULL, what='mapunit', source='soilweb') { # check for required packages diff --git a/R/STR.R b/R/STR.R index 5644ed21..96534181 100644 --- a/R/STR.R +++ b/R/STR.R @@ -1,5 +1,30 @@ - # + + +#' Graphical Description of US Soil Taxonomy Soil Temperature Regimes +#' +#' Graphical Description of US Soil Taxonomy Soil Temperature Regimes +#' +#' [Soil Temperature Regime Evaluation Tutorial](http://ncss-tech.github.io/AQP/soilDB/STR-eval.html) +#' +#' @param mast single value or vector of mean annual soil temperature (deg C) +#' @param msst single value or vector of mean summer soil temperature (deg C) +#' @param mwst single value of mean winter soil temperature (deg C) +#' @param permafrost logical: permafrost presence / absence +#' @param pt.cex symbol size +#' @param leg.cex legend size +#' @author D.E. Beaudette +#' @seealso \code{\link{estimateSTR}} +#' @references Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. +#' U.S. Department of Agriculture, Natural Resources Conservation Service, +#' National Soil Survey Center, Lincoln, Nebraska. +#' @keywords hplot +#' @examples +#' +#' par(mar=c(4,1,0,1)) +#' STRplot(mast = 0:25, msst = 10, mwst = 1) +#' +#' @export STRplot STRplot <- function(mast, msst, mwst, permafrost=FALSE, pt.cex=2.75, leg.cex=0.85) { # make a row of rectangles with colors based on STR @@ -126,6 +151,37 @@ STRplot <- function(mast, msst, mwst, permafrost=FALSE, pt.cex=2.75, leg.cex=0.8 # vectors of MAST, summer mean, winter mean all in Deg C + + +#' Estimate Soil Temperature Regime +#' +#' Estimate soil temperature regime (STR) based on mean annual soil temperature +#' (MAST), mean summer temperature (MSST), mean winter soil temperature (MWST), +#' presence of O horizons, saturated conditions, and presence of permafrost. +#' Several assumptions are made when O horizon or saturation are undefined. +#' +#' [Soil Temperature Regime Evaluation Tutorial](http://ncss-tech.github.io/AQP/soilDB/STR-eval.html) +#' +#' @param mast vector of mean annual soil temperature (deg C) +#' @param mean.summer vector of mean summer soil temperature (deg C) +#' @param mean.winter vector of mean winter soil temperature (deg C) +#' @param O.hz logical vector of O horizon presence / absence +#' @param saturated logical vector of seasonal saturation +#' @param permafrost logical vector of permafrost presence / absence +#' @return Vector of soil temperature regimes. +#' @author D.E. Beaudette +#' @seealso \code{\link{STRplot}} +#' @references Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. +#' U.S. Department of Agriculture, Natural Resources Conservation Service, +#' National Soil Survey Center, Lincoln, Nebraska. +#' @keywords manip +#' @examples +#' +#' # simple example +#' estimateSTR(mast=17, mean.summer = 22, mean.winter = 12) +#' +#' +#' @export estimateSTR estimateSTR <- function(mast, mean.summer, mean.winter, O.hz=NA, saturated=NA, permafrost=FALSE) { # check to make sure that the lengths of vectors are the same diff --git a/R/createStaticNASIS.R b/R/createStaticNASIS.R new file mode 100644 index 00000000..d6705258 --- /dev/null +++ b/R/createStaticNASIS.R @@ -0,0 +1,155 @@ +#' Method for "dumping" contents of an entire NASIS table +#' +#' Method for "dumping" contents of an entire NASIS table +#' +#' +#' @param table_name Character name of table. +#' @param dsn Optional: path to SQLite database containing NASIS table +#' structure; Default: \code{NULL} +#' @return A data.frame or other result of \code{DBI::dbGetQuery} +#' @export .dump_NASIS_table +.dump_NASIS_table <- function(table_name, dsn = NULL) { + + # connect to NASIS, identify columns + con <- dbConnectNASIS(dsn) + allcols <- "*" + + # handling for MSSQL/ODBC weirdness + if (is.null(dsn)) { + + # assuming that default connection uses ODBC + if (!requireNamespace("odbc")) + stop("package `odbc` is required ", call. = FALSE) + + columns <- odbc::odbcConnectionColumns(con, table_name) + + # re-arrange VARCHAR(MAX) columns + longcols <- subset(columns, columns$field.type == "varchar" & columns$column_size == 0)$name + allcols <- columns$name + + if (length(longcols) > 0) { + allcols[which(allcols %in% longcols)] <- NA + allcols <- c(na.omit(allcols), longcols) + } + } + + # construct query and return result + q <- sprintf("SELECT %s FROM %s", paste(allcols, collapse = ", "), table_name) + return(dbQueryNASIS(con, q)) +} + + + +#' Create a memory or file-based instance of NASIS database (for selected +#' tables) +#' +#' Create a memory or file-based instance of NASIS database (for selected +#' tables) +#' +#' +#' @param tables Character vector of target tables. Default: \code{NULL} is all +#' tables meeting the following criteria. +#' @param SS Logical. Include "selected set" tables (ending with suffix +#' \code{"_View_1"}). Default: \code{TRUE} +#' @param ignore_pattern A regular expression identifying tables in NASIS schema to ignore. Default: \code{"^sys|_SS|_State|NONDEL|View_0|dm_"} +#' @param dsn Optional: path to SQLite database containing NASIS table +#' structure; Default: \code{NULL} +#' @param output_path Optional: path to new/existing SQLite database to write +#' tables to. Default: \code{NULL} returns table results as named list. +#' @param verbose Issue error messages for unqueryable tables? +#' +#' @return A named list of results from calling \code{dbQueryNASIS} for all +#' columns in each NASIS table. +#' @examples +#' +#' +#' \dontrun{ +#' str(createStaticNASIS(tables = c("calculation","formtext"))) +#' } +#' +#' +#' @export createStaticNASIS +createStaticNASIS <- function(tables = NULL, SS = TRUE, + ignore_pattern = "^sys|_SS|_State|NONDEL|View_0|dm_|xml_", + dsn = NULL, output_path = NULL, + verbose = FALSE) { + + # can make static DB from another static DB, or default is local NASIS install (dsn=NULL) + con <- dbConnectNASIS(dsn = dsn) + + nasis_table_names <- NULL + + # explicit handling of the connection types currently allowed + if (inherits(con, 'OdbcConnection')) { + + if (requireNamespace("odbc")) + nasis_table_names <- odbc::dbListTables(con) + + } else if (inherits(con, 'SQLiteConnection')) { + + if (requireNamespace("RSQLite")) + nasis_table_names <- RSQLite::dbListTables(con) + + } else { + stop("Currently only OdbcConnection and SQLiteConnection are supported", call. = FALSE) + } + + # must know names of tables in data source + stopifnot(!is.null(nasis_table_names)) + + # never pull the system table + if (!is.null(ignore_pattern)) { + systables.idx <- grep(ignore_pattern, nasis_table_names) + + if (length(systables.idx) > 0) { + nasis_table_names <- nasis_table_names[-systables.idx] + } + } + + # keep only explicitly listed tables, if any + if (!is.null(tables) & length(tables) > 0 & is.character(tables)) { + nasis_table_names <- nasis_table_names[nasis_table_names %in% tables] + } + + # remove selected set tables + if (!SS) { + sstables <- nasis_table_names[grep("_View_1$", nasis_table_names)] + nasis_table_names <- nasis_table_names[!nasis_table_names %in% sstables] + } + + # return list result if no output path + if (is.null(output_path)) { + + # return named list of data.frames or try-error (one per table) + res <- lapply(nasis_table_names, function(n) try(.dump_NASIS_table(n, dsn = dsn), + silent = verbose)) + names(res) <- nasis_table_names + return(res) + + # otherwise, we are writing SQLite to output_path + } else { + + + # assuming that default connection uses ODBC + if (!requireNamespace("RSQLite")) + stop("package `RSQLite` is required ", call. = FALSE) + + # create sqlite db + outcon <- DBI::dbConnect(RSQLite::SQLite(), output_path) + + # returns TRUE, invisibly, or try-error (one per table) + return(lapply(nasis_table_names, function(n) { + return(try({ + DBI::dbWriteTable(conn = outcon, name = n, + value = .dump_NASIS_table(n, dsn = dsn), + overwrite = TRUE) + })) + })) + + # close output connection + DBI::dbDisconnect(outcon) + } + + # close input connection + DBI::dbDisconnect(con) +} diff --git a/R/dbQueryNASIS.R b/R/dbQueryNASIS.R new file mode 100644 index 00000000..2f0b43cb --- /dev/null +++ b/R/dbQueryNASIS.R @@ -0,0 +1,62 @@ +#' Send queries to a NASIS DBIConnection +#' +#' Send queries to a NASIS DBIConnection +#' +#' +#' @param conn A \code{DBIConnection} object, as returned by \code{DBI::dbConnect()}. +#' @param q A statement to execute using \code{DBI::dbGetQuery}; or a (named) vector containing multiple statements to evaluate separately +#' @param close Close connection after query? Default: \code{TRUE} +#' @param ... Additional arguments to \code{DBI::dbGetQuery} +#' @return Result of \code{DBI::dbGetQuery} +#' @export dbQueryNASIS +dbQueryNASIS <- function(conn, q, close = TRUE, ...) { + + if (inherits(conn, 'try-error')) + stop("Failed to connect to NASIS database!") + + # vectorize queries (return a [possibly named] list) + if(length(q) > 1) { + # recursively call dbQueryNASIS(close=FALSE) + res <- lapply(q, function(x) dbQueryNASIS(conn, x, close = FALSE)) + names(res) <- names(q) + dd <- res + } else { + ## exec query + d <- DBI::dbGetQuery(conn, q, ...) + # res <- DBI::dbSendQuery(conn, q) + # d <- DBI::dbFetch(res) + # d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) + + dd <- data.frame(d) + + } + + ## close connection if needed + if (close) { + DBI::dbDisconnect(conn) + } + return(dd) +} + + + +#' Create a connection to a local NASIS database +#' +#' Create a connection to a local NASIS database with `DBI` +#' +#' @aliases NASIS +#' @param dsn Optional: path to SQLite database containing NASIS table +#' structure; Default: \code{NULL} +#' @return A \code{DBIConnection} object, as returned by +#' \code{DBI::dbConnect()}. +#' @export dbConnectNASIS +dbConnectNASIS <- function(dsn = NULL) { + # TODO: NASIS sqlite snapshot connection via DBI/RSQLite + + # default connection uses DBI/odbc (historically RODBC) + res <- .openNASISchannel(dsn) + + return(res) +} + +NASIS <- function(dsn = NULL) dbConnectNASIS(dsn = dsn) diff --git a/R/fetchHenry.R b/R/fetchHenry.R index a9ce8e3a..45004cde 100644 --- a/R/fetchHenry.R +++ b/R/fetchHenry.R @@ -169,6 +169,74 @@ month2season <- function(x) { # this loads and packages the data into a list of objects + + +#' Download Data from the Henry Mount Soil Temperature and Water Database +#' +#' This function is a front-end to the REST query functionality of the Henry +#' Mount Soil Temperature and Water Database. +#' +#' Filling missing days with NA is useful for computing and index of how +#' complete the data are, and for estimating (mostly) unbiased MAST and +#' seasonal mean soil temperatures. Summaries are computed by first averaging +#' over Julian day, then averaging over all days of the year (MAST) or just +#' those days that occur within "summer" or "winter". This approach makes it +#' possible to estimate summaries in the presence of missing data. The quality +#' of summaries should be weighted by the number of "functional years" (number +#' of years with non-missing data after combining data by Julian day) and +#' "complete years" (number of years of data with >= 365 days of non-missing +#' data). +#' +#' @aliases fetchHenry month2season summarizeSoilTemperature +#' @param what type of data to return: 'sensors': sensor metadata only | +#' 'soiltemp': sensor metadata + soil temperature data | 'soilVWC': sensor +#' metadata + soil moisture data | 'airtemp': sensor metadata + air temperature +#' data | 'waterlevel': sensor metadata + water level data |'all': sensor +#' metadata + all sensor data +#' @param usersiteid (optional) filter results using a NASIS user site ID +#' @param project (optional) filter results using a project ID +#' @param sso (optional) filter results using a soil survey office code +#' @param gran data granularity: "day", "week", "month", "year"; returned data +#' are averages +#' @param start.date (optional) starting date filter +#' @param stop.date (optional) ending date filter +#' @param pad.missing.days should missing data ("day" granularity) be filled +#' with NA? see details +#' @param soiltemp.summaries should soil temperature ("day" granularity only) +#' be summarized? see details +#' @return a list containing: \item{sensors}{a \code{SpatialPointsDataFrame} +#' object containing site-level information} \item{soiltemp}{a +#' \code{data.frame} object containing soil temperature timeseries data} +#' \item{soilVWC}{a \code{data.frame} object containing soil moisture +#' timeseries data} \item{airtemp}{a \code{data.frame} object containing air +#' temperature timeseries data} \item{waterlevel}{a \code{data.frame} object +#' containing water level timeseries data} +#' @note This function and the back-end database are very much a work in +#' progress. +#' @author D.E. Beaudette +#' @seealso \code{\link{fetchSCAN}} +#' @keywords manip +#' @examples +#' +#' \donttest{ +#' if(requireNamespace("curl") & +#' curl::has_internet() & +#' require(lattice)) { +#' +#' # get CA630 data as daily averages +#' x <- fetchHenry(project='CA630', gran = 'day') +#' +#' # inspect data gaps +#' levelplot(factor(!is.na(sensor_value)) ~ doy * factor(year) | name, +#' data=x$soiltemp, col.regions=c('grey', 'RoyalBlue'), cuts=1, +#' colorkey=FALSE, as.table=TRUE, scales=list(alternating=3), +#' par.strip.text=list(cex=0.75), strip=strip.custom(bg='yellow'), +#' xlab='Julian Day', ylab='Year') +#' +#' } +#' } +#' +#' @export fetchHenry fetchHenry <- function(what='all', usersiteid=NULL, project=NULL, sso=NULL, gran='day', start.date=NULL, stop.date=NULL, pad.missing.days=TRUE, soiltemp.summaries=TRUE) { # check for required packages diff --git a/R/fetchKSSL.R b/R/fetchKSSL.R index 13e5abb6..410b0ff3 100644 --- a/R/fetchKSSL.R +++ b/R/fetchKSSL.R @@ -1,4 +1,3 @@ - # create a valid URL filter for SoilWeb API # arguments are NA by default .buildFilter <- function(series, bbox, mlra, pedlabsampnum, pedon_id, pedon_key) { @@ -158,6 +157,113 @@ # fully vectorized in all arguments except BBOX + + +#' Fetch KSSL Data +#' +#' Download soil characterization and morphologic data via BBOX, MLRA, or soil +#' series name query, from the KSSL database. +#' +#' This is an experimental interface to a subset for the most commonly used +#' data from a snapshot of KSSL (lab characterization) and NASIS (morphologic) +#' data. +#' +#' Series-queries are case insensitive. Series name is based on the "correlated +#' as" field (from KSSL snapshot) when present. The "sampled as" +#' classification was promoted to "correlated as" if the "correlated as" +#' classification was missing. +#' +#' When \code{returnMorphologicData} is TRUE, the resulting object is a list. +#' The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} +#' object) is stored in the named element "SPC". The additional elements are +#' basic morphologic data: soil color, rock fragment volume, pores, structure, +#' and redoximorphic features. There is a 1:many relationship between the +#' horizon data in "SPC" and the additional dataframes in \code{morph}. See +#' examples for ideas on how to "flatten" these tables. +#' +#' When \code{returnGeochemicalData} is TRUE, the resulting object is a list. +#' The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} +#' object) is stored in the named element "SPC". The additional elements are +#' geochemical and mineralogy analysis tables, specifically: +#' geochemical/elemental analyses "geochem", optical mineralogy "optical", and +#' X-ray diffraction / thermal "xrd_thermal". \code{returnGeochemicalData} will +#' include additional dataframes \code{geochem}, \code{optical}, and +#' \code{xrd_thermal} in list result. +#' +#' Setting \code{simplifyColors=TRUE} will automatically flatten the soil color +#' data and join to horizon level attributes. +#' +#' Function arguments (\code{series}, \code{mlra}, etc.) are fully vectorized +#' except for \code{bbox}. +#' +#' @param series vector of soil series names, case insensitive +#' @param bbox a single bounding box in WGS84 geographic coordinates e.g. +#' \code{c(-120, 37, -122, 38)} +#' @param mlra vector of MLRA IDs, e.g. "18" or "22A" +#' @param pedlabsampnum vector of KSSL pedon lab sample number +#' @param pedon_id vector of user pedon ID +#' @param pedon_key vector of KSSL internal pedon ID +#' @param returnMorphologicData logical, optionally request basic morphologic +#' data, see details section +#' @param returnGeochemicalData logical, optionally request geochemical, +#' optical and XRD/thermal data, see details section +#' @param simplifyColors logical, simplify colors (from morphologic data) and +#' join with horizon data +#' @param progress logical, optionally give progress when iterating over +#' multiple requests +#' @return a \code{SoilProfileCollection} object when +#' \code{returnMorphologicData} is FALSE, otherwise a list. +#' @note SoilWeb maintains a snapshot of these KSSL and NASIS data. The SoilWeb +#' snapshot was developed using methods described here: +#' \url{https://github.com/dylanbeaudette/process-kssl-snapshot}. Please use +#' the link below for the live data. +#' @author D.E. Beaudette and A.G. Brown +#' @seealso \code{\link{fetchOSD}} +#' @references \url{http://ncsslabdatamart.sc.egov.usda.gov/} +#' @keywords utilities +#' @examples +#' +#' \donttest{ +#' if(requireNamespace("curl") & +#' curl::has_internet()) { +#' +#' library(aqp) +#' library(plyr) +#' library(reshape2) +#' +#' # search by series name +#' s <- fetchKSSL(series='auburn') +#' +#' # search by bounding-box +#' # s <- fetchKSSL(bbox=c(-120, 37, -122, 38)) +#' +#' # how many pedons +#' length(s) +#' +#' # plot +#' plotSPC(s, name='hzn_desgn', max.depth=150) +#' +#' ## +#' ## morphologic data +#' ## +#' +#' # get lab and morphologic data +#' s <- fetchKSSL(series='auburn', returnMorphologicData = TRUE) +#' +#' # extract SPC +#' pedons <- s$SPC +#' +#' ## automatically simplify color data +#' s <- fetchKSSL(series='auburn', returnMorphologicData = TRUE, simplifyColors=TRUE) +#' +#' # check +#' par(mar=c(0,0,0,0)) +#' plot(pedons, color='moist_soil_color', print.id=FALSE) +#' +#' } +#' } +#' +#' @export fetchKSSL fetchKSSL <- function(series=NA, bbox=NA, mlra=NA, pedlabsampnum=NA, pedon_id=NA, pedon_key=NA, returnMorphologicData=FALSE, returnGeochemicalData=FALSE, simplifyColors=FALSE, progress=TRUE) { if(!requireNamespace('jsonlite', quietly=TRUE)) diff --git a/R/fetchNASIS.R b/R/fetchNASIS.R index 18a34d8c..7fa27633 100644 --- a/R/fetchNASIS.R +++ b/R/fetchNASIS.R @@ -1,54 +1,135 @@ -# convenient interface to local NASIS data -# from: pedons | components | lab | ??? -# ... : arguments passed on to helper functions -fetchNASIS <- function(from='pedons', - url = NULL, - SS = TRUE, - rmHzErrors = TRUE, - nullFragsAreZero = TRUE, - soilColorState = 'moist', - lab = FALSE, - fill = FALSE, - stringsAsFactors = default.stringsAsFactors() - ) { - - res <- NULL - - # sanity check - if(! from %in% c('pedons', 'components', 'pedon_report')) { - stop('Must specify: pedons, components or pedon_report', call. = FALSE) - } - - if(from == 'pedons') { - # pass arguments through - res <- .fetchNASIS_pedons(SS = SS, - rmHzErrors = rmHzErrors, - nullFragsAreZero = nullFragsAreZero, - soilColorState = soilColorState, - lab = lab, - stringsAsFactors = stringsAsFactors - ) - } - - if(from == 'components') { - # pass arguments through - res <- .fetchNASIS_components(SS = TRUE, - rmHzErrors = rmHzErrors, - fill = fill, - stringsAsFactors = stringsAsFactors - ) - } - - if(from == 'pedon_report') { - # pass arguments through - res <- .fetchNASIS_report(url = url, - rmHzErrors = rmHzErrors, - nullFragsAreZero = nullFragsAreZero, - soilColorState = soilColorState, - stringsAsFactors = stringsAsFactors - ) - } - - return(res) - -} +# convenient interface to local NASIS data +# from: pedons | components | lab | ??? +# ... : arguments passed on to helper functions + + +#' Fetch commonly used site/pedon/horizon or component data from NASIS. +#' +#' Fetch commonly used site/pedon/horizon data or component from NASIS, +#' returned as a SoilProfileCollection object. +#' +#' This function imports data from NASIS into R as a +#' \code{SoilProfileCollection} object. It "flattens" NASIS pedon and component +#' tables, including their child tables, into several more easily manageable +#' data frames. Primarily these functions access the local NASIS database using +#' an ODBC connection. However using the \code{fetchNASIS()} argument +#' \code{from = "pedon_report"}, data can be read from the NASIS Report +#' 'fetchNASIS', as either a txt file or url. The primary purpose of +#' \code{fetchNASIS(from = "pedon_report")} is to facilitate importing datasets +#' larger than 8000+ pedons/components. +#' +#' The value of \code{nullFragsAreZero} will have a significant impact on the +#' rock fragment fractions returned by fetchNASIS. Set \code{nullFragsAreZero = +#' FALSE} in those cases where there are many data-gaps and \code{NULL} rock +#' fragment values should be interpreted as \code{NULL}. Set +#' \code{nullFragsAreZero = TRUE} in those cases where \code{NULL} rock +#' fragment values should be interpreted as 0. +#' +#' This function attempts to do most of the boilerplate work when extracting +#' site/pedon/horizon or component data from a local NASIS database. Pedons +#' that are missing horizon data, or have errors in their horizonation are +#' excluded from the returned object, however, their IDs are printed on the +#' console. Pedons with combination horizons (e.g. B/C) are erroneously marked +#' as errors due to the way in which they are stored in NASIS as two +#' overlapping horizon records. +#' +#' Tutorials: +#' +#' - [fetchNASIS Pedons Tutorial](http://ncss-tech.github.io/AQP/soilDB/fetchNASIS-mini-tutorial.html) +#' - [fetchNASIS Components Tutorial](http://ncss-tech.github.io/AQP/soilDB/NASIS-component-data.html) +#' +#' @aliases fetchNASIS get_phorizon_from_NASIS_db +#' get_component_copm_data_from_NASIS_db +#' get_component_horizon_data_from_NASIS_db +#' get_component_correlation_data_from_NASIS_db +#' get_component_cogeomorph_data_from_NASIS_db +#' get_component_esd_data_from_NASIS_db +#' get_component_otherveg_data_from_NASIS_db get_copedon_from_NASIS_db +#' get_legend_from_NASISget_lmuaoverlap_from_NASIS get_mapunit_from_NASIS +#' get_projectmapunit_from_NASIS get_component_diaghz_from_NASIS_db +#' get_mutext_from_NASIS_db get_phfmp_from_NASIS_db get_RMF_from_NASIS_db +#' get_concentrations_from_NASIS_db +#' get_cotext_from_NASIS_db +#' @param from determines what objects should fetched? ('pedons' | 'components' | 'pedon_report') +#' @param url string specifying the url for the NASIS pedon_report (default: +#' `NULL`) +#' @param SS fetch data from the currently loaded selected set in NASIS or from +#' the entire local database (default: `TRUE`) +#' @param rmHzErrors should pedons with horizon depth errors be removed from +#' the results? (default: `TRUE`) +#' @param nullFragsAreZero should fragment volumes of `NULL` be interpreted as `0`? +#' (default: `TRUE`), see details +#' @param soilColorState which colors should be used to generate the +#' convenience field `soil_color`? (`'moist'` or `'dry'`) +#' @param lab should the `phlabresults` child table be fetched with +#' site/pedon/horizon data (default: `FALSE`) +#' @param fill (`fetchNASIS(from='components')` only: include component records +#' without horizon data in result? (default: `FALSE`) +#' @param stringsAsFactors logical: should character vectors be converted to +#' factors? This argument is passed to the `uncode()` function. It does not +#' convert those vectors that have been set outside of `uncode()` (i.e. hard +#' coded). +#' @param dsn Optional: path to local SQLite database containing NASIS +#' table structure; default: `NULL` +#' @return A SoilProfileCollection object +#' @author D. E. Beaudette, J. M. Skovlin, S.M. Roecker, A.G. Brown +#' @export fetchNASIS +fetchNASIS <- function(from='pedons', + url = NULL, + SS = TRUE, + rmHzErrors = TRUE, + nullFragsAreZero = TRUE, + soilColorState = 'moist', + lab = FALSE, + fill = FALSE, + stringsAsFactors = default.stringsAsFactors(), + dsn = NULL) { + + res <- NULL + + # TODO: do we need _View_1 tables in the sqlite table snapshot? Could be handy for + # specialized selected sets crafted by NASIS/CVIR stuff; currently you are allowed + # to specify the selected set for a SQLite database, and I suppose the convention + # should be for those tables to be there, even if empty + + # if (!is.null(dsn)) + # SS <- FALSE + + # sanity check + if (!from %in% c('pedons', 'components', 'pedon_report')) { + stop('Must specify: pedons, components or pedon_report', call. = FALSE) + } + + if (from == 'pedons') { + # pass arguments through + res <- .fetchNASIS_pedons(SS = SS, + rmHzErrors = rmHzErrors, + nullFragsAreZero = nullFragsAreZero, + soilColorState = soilColorState, + lab = lab, + stringsAsFactors = stringsAsFactors, + dsn) + } + + if (from == 'components') { + # pass arguments through + res <- .fetchNASIS_components(SS = TRUE, + rmHzErrors = rmHzErrors, + fill = fill, + stringsAsFactors = stringsAsFactors + ) + } + + if (from == 'pedon_report') { + # pass arguments through + res <- .fetchNASIS_report(url = url, + rmHzErrors = rmHzErrors, + nullFragsAreZero = nullFragsAreZero, + soilColorState = soilColorState, + stringsAsFactors = stringsAsFactors + ) + } + + return(res) + +} diff --git a/R/fetchNASISLabData.R b/R/fetchNASISLabData.R index d5fb24ee..789b07b3 100644 --- a/R/fetchNASISLabData.R +++ b/R/fetchNASISLabData.R @@ -1,57 +1,76 @@ -# convenience function for loading most commonly used information from local NASIS database -fetchNASISLabData <- function(SS = TRUE) { - # must have RODBC installed - if(!requireNamespace('RODBC')) - stop('please install the `RODBC` package', call.=FALSE) - - # test connection - if(! 'nasis_local' %in% names(RODBC::odbcDataSources())) - stop('Local NASIS ODBC connection has not been setup. Please see the `setup_ODBC_local_NASIS.pdf` document included with this package.') - - # 1. load data in pieces, results are DF objects - s <- get_labpedon_data_from_NASIS_db(SS) - h <- get_lablayer_data_from_NASIS_db(SS) - - # stop if selected set is not loaded - if (nrow(h) == 0 | nrow(s) == 0) - stop('Selected set is missing either the Pedon or Layer NCSS Lab Data table, please load and try again :)') - - # fix some common problems - # replace missing lower boundaries - missing.lower.depth.idx <- which(!is.na(h$hzdept) & is.na(h$hzdepb)) - if(length(missing.lower.depth.idx) > 0) { - message(paste('replacing missing lower horizon depths with top depth + 1cm ... [', length(missing.lower.depth.idx), ' horizons]', sep='')) - h$hzdepb[missing.lower.depth.idx] <- h$hzdept[missing.lower.depth.idx] + 1 - } - - ## TODO: what to do with multiple samples / hz? - # test for bad horizonation... flag - message('finding horizonation errors ...') - h.test <- ddply(h, 'labpeiid', function(d) { - res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) - return(data.frame(hz_logic_pass=all(!res))) - }) - - # which are the good (valid) ones? - good.ids <- as.character(h.test$labpeiid[which(h.test$hz_logic_pass)]) - bad.ids <- as.character(h.test$labpeiid[which(!h.test$hz_logic_pass)]) - bad.pedon.ids <- s$upedonid[which(s$labpeiid %in% bad.ids)] - - # upgrade to SoilProfilecollection - depths(h) <- labpeiid ~ hzdept + hzdepb - - ## TODO: this will fail in the presence of duplicates - # add site data to object - site(h) <- s # left-join via labpeiid - - # set NASIS-specific horizon identifier - hzidname(h) <- 'labphiid' - - # 7. save and mention bad pedons - assign('bad.labpedon.ids', value=bad.pedon.ids, envir=soilDB.env) - if(length(bad.pedon.ids) > 0) - message("horizon errors detected, use `get('bad.labpedon.ids', envir=soilDB.env)` for a list of pedon IDs") - - # done - return(h) -} +# convenience function for loading most commonly used information from local NASIS database + + +#' Fetch lab data used site/horizon data from a PedonPC database. +#' +#' Fetch KSSL laboratory pedon/horizon layer data from a local NASIS database, +#' return as a SoilProfileCollection object. +#' +#' This function currently works only on Windows, and requires a 'nasis_local' +#' ODBC connection. +#' +#' @param SS fetch data from the currently loaded selected set in NASIS or from +#' the entire local database (default: `TRUE`)#' +#' @param dsn Optional: path to local SQLite database containing NASIS +#' table structure; default: `NULL` +#' +#' @return a SoilProfileCollection object +#' +#' @author J.M. Skovlin and D.E. Beaudette +#' @seealso \code{\link{get_labpedon_data_from_NASIS_db}} +#' @keywords manip +#' @export fetchNASISLabData +fetchNASISLabData <- function(SS = TRUE, dsn = NULL) { + + # test connection + if (!local_NASIS_defined(dsn)) + stop('Local NASIS ODBC connection has not been setup. Please see the `setup_ODBC_local_NASIS.pdf` document included with this package.') + + # 1. load data in pieces, results are DF objects + s <- get_labpedon_data_from_NASIS_db(SS) + h <- get_lablayer_data_from_NASIS_db(SS) + + # stop if selected set is not loaded + if (nrow(h) == 0 | nrow(s) == 0) + stop('Selected set is missing either the Pedon or Layer NCSS Lab Data table, please load and try again :)') + + # fix some common problems + # replace missing lower boundaries + missing.lower.depth.idx <- which(!is.na(h$hzdept) & is.na(h$hzdepb)) + if (length(missing.lower.depth.idx) > 0) { + message(paste('replacing missing lower horizon depths with top depth + 1cm ... [', + length(missing.lower.depth.idx), ' horizons]', sep = '')) + h$hzdepb[missing.lower.depth.idx] <- h$hzdept[missing.lower.depth.idx] + 1 + } + + ## TODO: what to do with multiple samples / hz? + # test for bad horizonation... flag + message('finding horizonation errors ...') + h.test <- ddply(h, 'labpeiid', function(d) { + res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) + return(data.frame(hz_logic_pass=all(!res))) + }) + + # which are the good (valid) ones? + # good.ids <- as.character(h.test$labpeiid[which(h.test$hz_logic_pass)]) + bad.ids <- as.character(h.test$labpeiid[which(!h.test$hz_logic_pass)]) + bad.pedon.ids <- s$upedonid[which(s$labpeiid %in% bad.ids)] + + # upgrade to SoilProfilecollection + depths(h) <- labpeiid ~ hzdept + hzdepb + + ## TODO: this will fail in the presence of duplicates + # add site data to object + site(h) <- s # left-join via labpeiid + + # set NASIS-specific horizon identifier + hzidname(h) <- 'labphiid' + + # 7. save and mention bad pedons + assign('bad.labpedon.ids', value = bad.pedon.ids, envir = soilDB.env) + if (length(bad.pedon.ids) > 0) + message("horizon errors detected, use `get('bad.labpedon.ids', envir=soilDB.env)` for a list of pedon IDs") + + # done + return(h) +} diff --git a/R/fetchNASIS_components.R b/R/fetchNASIS_components.R index e9400b13..3758f190 100644 --- a/R/fetchNASIS_components.R +++ b/R/fetchNASIS_components.R @@ -1,133 +1,131 @@ -## TODO: better documentation for "fill" argument -# https://github.com/ncss-tech/soilDB/issues/50 -## TODO: this will not ID horizons with no depths -## TODO: better error checking / reporting is needed: coiid, dmu id, component name -.fetchNASIS_components <- function(SS=TRUE, rmHzErrors=TRUE, fill = FALSE, stringsAsFactors = default.stringsAsFactors()) { - # must have RODBC installed - if(!requireNamespace('RODBC')) - stop('please install the `RODBC` package', call.=FALSE) - - # ensure that any old hz errors are cleared - if(exists('component.hz.problems', envir=soilDB.env)) - assign('component.hz.problems', value=character(0), envir=soilDB.env) - - # load data in pieces - f.comp <- get_component_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - f.chorizon <- get_component_horizon_data_from_NASIS_db(SS=SS, fill=fill) - f.copm <- get_component_copm_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - f.cogeomorph <- get_component_cogeomorph_data_from_NASIS_db(SS=SS) - f.otherveg <- get_component_otherveg_data_from_NASIS_db(SS=SS) - f.ecosite <- get_component_esd_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - f.diaghz <- get_component_diaghz_from_NASIS_db(SS=SS) - f.restrict <- get_component_restrictions_from_NASIS_db(SS=SS) - - filled.ids <- character(0) - - # optionally test for bad horizonation... flag, and remove - if(rmHzErrors & nrow(f.chorizon) > 0) { - f.chorizon.test <- plyr::ddply(f.chorizon, 'coiid', function(d) { - res <- aqp::hzDepthTests(top=d[['hzdept_r']], bottom=d[['hzdepb_r']]) - return(data.frame(hz_logic_pass=all(!res))) - }) - - # fill=TRUE adds horizons with NA chiid will have NA depths -- will not pass hzDepthTests - # therefore, only way to use fill effectively was with rmHzErrors=FALSE - # which runs the risk of duplication in the case of data entry errors or other many:1 issues in comp - filled.idx <- which(is.na(f.chorizon$chiid)) - if(length(filled.idx) > 0) { - filled.ids <- as.character(f.chorizon$coiid[filled.idx]) - #print(dput(filled.ids)) - } - - # which are the good (valid) ones? - good.ids <- as.character(f.chorizon.test$coiid[which(f.chorizon.test$hz_logic_pass)]) - bad.ids <- as.character(f.chorizon.test$coiid[which(!f.chorizon.test$hz_logic_pass)]) - - if(length(filled.ids) > 0) { - good.ids <- unique(c(good.ids, filled.ids)) - bad.ids <- unique(bad.ids[!bad.ids %in% filled.ids]) - } - - # keep the good ones - f.chorizon <- f.chorizon[which(f.chorizon$coiid %in% good.ids), ] - - # keep track of those components with horizonation errors - #if(length(bad.ids) > 0) # AGB removed this line of code b/c it prevents update of 'component.hz.problems' on subsequent error-free calls - assign('component.hz.problems', value=bad.ids, envir=soilDB.env) - } - - if(nrow(f.chorizon) > 0) { - # upgrade to SoilProfilecollection - depths(f.chorizon) <- coiid ~ hzdept_r + hzdepb_r - } else { - stop("No horizon data in NASIS component query result.", call.=FALSE) - } - - # add site data to object - site(f.chorizon) <- f.comp # left-join via coiid - - ## TODO: convert all ddply() calls into split() -> lapply() -> do.call('rbind') - - # join-in copm strings - ## 2017-3-13: short-circuts need testing, consider pre-marking mistakes before parsing - pm <- plyr::ddply(f.copm, 'coiid', .formatcoParentMaterialString, name.sep=' & ') - if(nrow(pm) > 0) - site(f.chorizon) <- pm - - # join-in cogeomorph strings - ## 2017-3-13: short-circuts need testing, consider pre-marking mistakes before parsing - lf <- plyr::ddply(f.cogeomorph, 'coiid', .formatcoLandformString, name.sep=' & ') - if(nrow(lf) > 0) - site(f.chorizon) <- lf - - # join-in ecosite string - ## 2017-3-06: short-circuts need testing, consider pre-marking mistakes before parsing - es <- plyr::ddply(f.ecosite, 'coiid', .formatEcositeString, name.sep=' & ') - if(nrow(es) > 0) - site(f.chorizon) <- es - - # join-in othervegclass string - ## 2017-3-06: short-circuts need testing, consider pre-marking mistakes before parsing - ov <- plyr::ddply(f.otherveg, 'coiid', .formatOtherVegString, name.sep=' & ') - if(nrow(ov) > 0) - site(f.chorizon) <- ov - - # add diagnostic features to SPC - diagnostic_hz(f.chorizon) <- f.diaghz - - # add restrictions to SPC - # required new setter in aqp SPC object (AGB added 2019/12/23) - restrictions(f.chorizon) <- f.restrict - - # print any messages on possible data quality problems: - if(exists('component.hz.problems', envir=soilDB.env)) - if(length(get("component.hz.problems", envir = soilDB.env)) > 0) - message("-> QC: horizon errors detected, use `get('component.hz.problems', envir=soilDB.env)` for related coiid values") - - # set NASIS component specific horizon identifier - if(!fill & length(filled.ids) == 0) { - res <- try(hzidname(f.chorizon) <- 'chiid') - if(inherits(res, 'try-error')) { - if(!rmHzErrors) { - warning("cannot set `chiid` as unique component horizon key -- duplicate horizons present with rmHzErrors=FALSE") - } else { - warning("cannot set `chiid` as unique component horizon key -- defaulting to `hzID`") - } - } - } else { - warning("cannot set `chiid` as unique component horizon key - `NA` introduced by fill=TRUE", call.=F) - } - - # set metadata - m <- metadata(f.chorizon) - m$origin <- 'NASIS components' - metadata(f.chorizon) <- m - - # set optional hz designation and texture slots - hzdesgnname(f.chorizon) <- "hzname" - hztexclname(f.chorizon) <- "texture" - - # done, return SPC - return(f.chorizon) - -} +## TODO: better documentation for "fill" argument +# https://github.com/ncss-tech/soilDB/issues/50 +## TODO: this will not ID horizons with no depths +## TODO: better error checking / reporting is needed: coiid, dmu id, component name +.fetchNASIS_components <- function(SS=TRUE, rmHzErrors=TRUE, fill = FALSE, stringsAsFactors = default.stringsAsFactors()) { + + # ensure that any old hz errors are cleared + if(exists('component.hz.problems', envir=soilDB.env)) + assign('component.hz.problems', value=character(0), envir=soilDB.env) + + # load data in pieces + f.comp <- get_component_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) + f.chorizon <- get_component_horizon_data_from_NASIS_db(SS=SS, fill=fill) + f.copm <- get_component_copm_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) + f.cogeomorph <- get_component_cogeomorph_data_from_NASIS_db(SS=SS) + f.otherveg <- get_component_otherveg_data_from_NASIS_db(SS=SS) + f.ecosite <- get_component_esd_data_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) + f.diaghz <- get_component_diaghz_from_NASIS_db(SS=SS) + f.restrict <- get_component_restrictions_from_NASIS_db(SS=SS) + + filled.ids <- character(0) + + # optionally test for bad horizonation... flag, and remove + if(rmHzErrors & nrow(f.chorizon) > 0) { + # TODO: mirror fetchNASIS_pedons + f.chorizon.test <- plyr::ddply(f.chorizon, 'coiid', function(d) { + res <- aqp::hzDepthTests(top=d[['hzdept_r']], bottom=d[['hzdepb_r']]) + return(data.frame(hz_logic_pass=all(!res))) + }) + + # fill=TRUE adds horizons with NA chiid will have NA depths -- will not pass hzDepthTests + # therefore, only way to use fill effectively was with rmHzErrors=FALSE + # which runs the risk of duplication in the case of data entry errors or other many:1 issues in comp + filled.idx <- which(is.na(f.chorizon$chiid)) + if(length(filled.idx) > 0) { + filled.ids <- as.character(f.chorizon$coiid[filled.idx]) + #print(dput(filled.ids)) + } + + # which are the good (valid) ones? + good.ids <- as.character(f.chorizon.test$coiid[which(f.chorizon.test$hz_logic_pass)]) + bad.ids <- as.character(f.chorizon.test$coiid[which(!f.chorizon.test$hz_logic_pass)]) + + if(length(filled.ids) > 0) { + good.ids <- unique(c(good.ids, filled.ids)) + bad.ids <- unique(bad.ids[!bad.ids %in% filled.ids]) + } + + # keep the good ones + f.chorizon <- f.chorizon[which(f.chorizon$coiid %in% good.ids), ] + + # keep track of those components with horizonation errors + #if(length(bad.ids) > 0) # AGB removed this line of code b/c it prevents update of 'component.hz.problems' on subsequent error-free calls + assign('component.hz.problems', value=bad.ids, envir=soilDB.env) + } + + if(nrow(f.chorizon) > 0) { + # upgrade to SoilProfilecollection + depths(f.chorizon) <- coiid ~ hzdept_r + hzdepb_r + } else { + stop("No horizon data in NASIS component query result.", call.=FALSE) + } + + # add site data to object + site(f.chorizon) <- f.comp # left-join via coiid + + ## TODO: convert all ddply() calls into split() -> lapply() -> do.call('rbind') + + # join-in copm strings + ## 2017-3-13: short-circuts need testing, consider pre-marking mistakes before parsing + pm <- plyr::ddply(f.copm, 'coiid', .formatcoParentMaterialString, name.sep=' & ') + if(nrow(pm) > 0) + site(f.chorizon) <- pm + + # join-in cogeomorph strings + ## 2017-3-13: short-circuts need testing, consider pre-marking mistakes before parsing + lf <- plyr::ddply(f.cogeomorph, 'coiid', .formatcoLandformString, name.sep=' & ') + if(nrow(lf) > 0) + site(f.chorizon) <- lf + + # join-in ecosite string + ## 2017-3-06: short-circuts need testing, consider pre-marking mistakes before parsing + es <- plyr::ddply(f.ecosite, 'coiid', .formatEcositeString, name.sep=' & ') + if(nrow(es) > 0) + site(f.chorizon) <- es + + # join-in othervegclass string + ## 2017-3-06: short-circuts need testing, consider pre-marking mistakes before parsing + ov <- plyr::ddply(f.otherveg, 'coiid', .formatOtherVegString, name.sep=' & ') + if(nrow(ov) > 0) + site(f.chorizon) <- ov + + # add diagnostic features to SPC + diagnostic_hz(f.chorizon) <- f.diaghz + + # add restrictions to SPC + # required new setter in aqp SPC object (AGB added 2019/12/23) + restrictions(f.chorizon) <- f.restrict + + # print any messages on possible data quality problems: + if(exists('component.hz.problems', envir=soilDB.env)) + if(length(get("component.hz.problems", envir = soilDB.env)) > 0) + message("-> QC: horizon errors detected, use `get('component.hz.problems', envir=soilDB.env)` for related coiid values") + + # set NASIS component specific horizon identifier + if(!fill & length(filled.ids) == 0) { + res <- try(hzidname(f.chorizon) <- 'chiid') + if(inherits(res, 'try-error')) { + if(!rmHzErrors) { + warning("cannot set `chiid` as unique component horizon key -- duplicate horizons present with rmHzErrors=FALSE") + } else { + warning("cannot set `chiid` as unique component horizon key -- defaulting to `hzID`") + } + } + } else { + warning("cannot set `chiid` as unique component horizon key - `NA` introduced by fill=TRUE", call.=F) + } + + # set metadata + m <- metadata(f.chorizon) + m$origin <- 'NASIS components' + metadata(f.chorizon) <- m + + # set optional hz designation and texture slots + hzdesgnname(f.chorizon) <- "hzname" + hztexclname(f.chorizon) <- "texture" + + # done, return SPC + return(f.chorizon) + +} diff --git a/R/fetchNASIS_pedons.R b/R/fetchNASIS_pedons.R index 441d453e..cdc082c7 100644 --- a/R/fetchNASIS_pedons.R +++ b/R/fetchNASIS_pedons.R @@ -1,94 +1,103 @@ # get NASIS site/pedon/horizon/diagnostic feature data -.fetchNASIS_pedons <- function(SS=TRUE, rmHzErrors=TRUE, nullFragsAreZero=TRUE, - soilColorState='moist', lab=FALSE, stringsAsFactors = default.stringsAsFactors()) { +.fetchNASIS_pedons <- function(SS = TRUE, + rmHzErrors = TRUE, + nullFragsAreZero = TRUE, + soilColorState = 'moist', + lab = FALSE, + stringsAsFactors = default.stringsAsFactors(), + dsn = NULL +) { # test connection - if(! 'nasis_local' %in% names(RODBC::odbcDataSources())) + if (!local_NASIS_defined(dsn)) stop('Local NASIS ODBC connection has not been setup. Please see `http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html`.') # sanity check - if(! soilColorState %in% c('dry', 'moist')) + if (!soilColorState %in% c('dry', 'moist')) stop('soilColorState must be either `dry` or `moist`', call. = FALSE) ## load data in pieces # these fail gracefully when no data in local DB | selected set - site_data <- get_site_data_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors) - hz_data <- get_hz_data_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors) - color_data <- get_colors_from_NASIS_db(SS = SS) + site_data <- get_site_data_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, + dsn = dsn) + hz_data <- get_hz_data_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, + dsn = dsn) + color_data <- get_colors_from_NASIS_db(SS = SS, dsn = dsn) - h <- hz_data ## ensure there are enough data to create an SPC object if (nrow(hz_data) == 0) { stop('No site/pedons objects in local NASIS DB or selected set.', call. = FALSE) } # data that cannot be effectively flattened in SQL + extended_data <- get_extended_data_from_NASIS_db(SS = SS, nullFragsAreZero = nullFragsAreZero, - stringsAsFactors = stringsAsFactors) + stringsAsFactors = stringsAsFactors, + dsn = dsn) ## fix some common problems # replace missing lower boundaries - missing.lower.depth.idx <- which(!is.na(h$hzdept) & is.na(h$hzdepb)) + missing.lower.depth.idx <- which(!is.na(hz_data$hzdept) & is.na(hz_data$hzdepb)) # keep track of affected pedon IDs (if none, this will have zero length) - assign('missing.bottom.depths', value=unique(h$pedon_id[missing.lower.depth.idx]), envir=soilDB.env) + assign('missing.bottom.depths', value = unique(hz_data$pedon_id[missing.lower.depth.idx]), envir = soilDB.env) - if(length(missing.lower.depth.idx) > 0) { - message(paste('replacing missing lower horizon depths with top depth + 1cm ... [', length(missing.lower.depth.idx), ' horizons]', sep='')) + if (length(missing.lower.depth.idx) > 0) { + message(paste0('replacing missing lower horizon depths with top depth + 1cm ... [', length(missing.lower.depth.idx), ' horizons]')) # make edit - h$hzdepb[missing.lower.depth.idx] <- h$hzdept[missing.lower.depth.idx] + 1 + hz_data$hzdepb[missing.lower.depth.idx] <- hz_data$hzdept[missing.lower.depth.idx] + 1 } # top == bottom ? bottom <- bottom + 1 - top.eq.bottom.idx <- which(h$hzdept == h$hzdepb) + top.eq.bottom.idx <- which(hz_data$hzdept == hz_data$hzdepb) # keep track of affected pedon IDs (if none, this will have zero length) - assign('top.bottom.equal', value=unique(h$pedon_id[ top.eq.bottom.idx]), envir=soilDB.env) + assign('top.bottom.equal', value = unique(hz_data$pedon_id[ top.eq.bottom.idx]), envir = soilDB.env) - if(length(top.eq.bottom.idx) > 0) { - message(paste('top/bottom depths equal, adding 1cm to bottom depth ... [', length(top.eq.bottom.idx), ' horizons]', sep='')) + if (length(top.eq.bottom.idx) > 0) { + message(paste0('top/bottom depths equal, adding 1cm to bottom depth ... [', length(top.eq.bottom.idx), ' horizons]')) # make the edit - h$hzdepb[top.eq.bottom.idx] <- h$hzdepb[top.eq.bottom.idx] + 1 + hz_data$hzdepb[top.eq.bottom.idx] <- hz_data$hzdepb[top.eq.bottom.idx] + 1 } - ## test for horizonation inconsistencies... flag, and optionally remove - # ~ 1.3 seconds / ~ 4k pedons - h.test <- do.call('rbind', lapply(split(h, h$peiid), function(d) { - res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) - # print(res) - return(data.frame(peiid = d$peiid, hz_logic_pass=all(!res))) - })) - - # which are the good (valid) ones? - good.ids <- as.character(h.test$peiid[which(h.test$hz_logic_pass)]) - bad.ids <- as.character(h.test$peiid[which(!h.test$hz_logic_pass)]) - bad.horizons <- h[which(!h.test$hz_logic_pass), c(1:4,6,7)] - bad.pedon.ids <- site_data$pedon_id[which(site_data$peiid %in% bad.ids)] - - # optionally filter pedons WITH NO horizonation inconsistencies - if(rmHzErrors) - h <- h[which(h$peiid %in% good.ids), ] - - # keep track of those pedons with horizonation errors - assign('bad.pedon.ids', value=bad.pedon.ids, envir=soilDB.env) - assign("bad.horizons", value = data.frame(bad.horizons), envir = soilDB.env) - + # aqp uses data.table for efficient logic checking + if (rmHzErrors) { + + # get overall validity (combination of 4 logic tests applied to each peiid) + h.test <- aqp::checkHzDepthLogic(hz_data, c("hzdept","hzdepb"), "peiid", fast = TRUE) + + # which are the good (valid) ones? + good.ids <- as.character(h.test$peiid[which(h.test$valid)]) + bad.ids <- as.character(h.test$peiid[which(!h.test$valid)]) + bad.horizons <- hz_data[which(!h.test$valid), c("peiid", "phiid", + "pedon_id", "hzname", + "hzdept", "hzdepb")] + bad.pedon.ids <- site_data$pedon_id[which(site_data$peiid %in% bad.ids)] + + # optionally filter pedons WITH NO horizonation inconsistencies + if (rmHzErrors) + hz_data <- hz_data[which(hz_data$peiid %in% good.ids), ] + + # keep track of those pedons with horizonation errors + assign('bad.pedon.ids', value = bad.pedon.ids, envir = soilDB.env) + assign("bad.horizons", value = data.frame(bad.horizons), envir = soilDB.env) + } + # convert pedon and horizon unique ID to character - h$peiid <- as.character(h$peiid) - h$phiid <- as.character(h$phiid) + hz_data$peiid <- as.character(hz_data$peiid) + hz_data$phiid <- as.character(hz_data$phiid) # upgrade to SoilProfilecollection - depths(h) <- peiid ~ hzdept + hzdepb + depths(hz_data) <- peiid ~ hzdept + hzdepb # move pedon_id into @site - # 1 second for ~ 4k pedons - site(h) <- ~ pedon_id + site(hz_data) <- ~ pedon_id ## copy pre-computed colors into a convenience field for plotting # moist colors @@ -99,21 +108,21 @@ if(soilColorState == 'dry') color_data$soil_color <- color_data$dry_soil_color - horizons(h) <- color_data + horizons(hz_data) <- color_data # check for empty fragment summary and nullFragsAreZero if(nullFragsAreZero & all(is.na(unique(extended_data$frag_summary$phiid)))) - extended_data$frag_summary <- cbind(phiid = unique(h$phiid), extended_data$frag_summary[,-1]) + extended_data$frag_summary <- cbind(phiid = unique(hz_data$phiid), extended_data$frag_summary[,-1]) ## join hz + fragment summary - horizons(h) <- extended_data$frag_summary + horizons(hz_data) <- extended_data$frag_summary # check for empty artifact summary and nullFragsAreZerod if(nullFragsAreZero & all(is.na(unique(extended_data$art_summary$phiid)))) - extended_data$art_summary <- cbind(phiid = unique(h$phiid), extended_data$art_summary[,-1]) + extended_data$art_summary <- cbind(phiid = unique(hz_data$phiid), extended_data$art_summary[,-1]) # join hz + artifact summary - horizons(h) <- extended_data$art_summary + horizons(hz_data) <- extended_data$art_summary ## TODO: this will fail in the presence of duplicates # add site data to object @@ -122,96 +131,106 @@ # left-join via peiid # < 0.1 second for ~ 4k pedons - site(h) <- site_data + site(hz_data) <- site_data - ### TODO: consider moving this into the extended data function ### # load best-guess optimal records from taxhistory # method is added to the new field called 'selection_method' # assumes that classdate is a datetime class object! # 2019-01-31: converting to base functions - ed.tax <- split(extended_data$taxhistory, extended_data$taxhistory$peiid) - best.tax.data <- do.call('rbind', lapply(ed.tax, .pickBestTaxHistory)) - site(h) <- best.tax.data + # 2020-02-17: converting to data.table + + # define data.table globals for R CMD CHECK + .BY <- NULL + .SD <- NULL + + ed.tax <- data.table::as.data.table(extended_data$taxhistory) + best.tax.data <- ed.tax[, .pickBestTaxHistory(.SD), + by = list(peiid = ed.tax$peiid)] + site(hz_data) <- as.data.frame(best.tax.data) + # load best-guess optimal records from ecositehistory # method is added to the new field called 'es_selection_method' - ed.es <- split(extended_data$ecositehistory, extended_data$ecositehistory$siteiid) - best.ecosite.data <- do.call('rbind', lapply(ed.es, .pickBestEcosite)) - site(h) <- best.ecosite.data + ed.es <- data.table::as.data.table(extended_data$ecositehistory) + best.ecosite.data <- ed.es[, .pickBestEcosite(.SD), + by = list(siteiid = ed.es$siteiid)] + site(hz_data) <- as.data.frame(best.ecosite.data) ## TODO: NA in diagnostic boolean columns are related to pedons with no diagnostic features ## https://github.com/ncss-tech/soilDB/issues/59 # add diagnostic boolean data into @site - site(h) <- extended_data$diagHzBoolean + site(hz_data) <- extended_data$diagHzBoolean ## optionally convert NA fragvol to 0 if(nullFragsAreZero) { # this is the "total fragment volume" per NASIS calculation - h$fragvoltot <- ifelse(is.na(h$fragvoltot), 0, h$fragvoltot) + hz_data$fragvoltot <- ifelse(is.na(hz_data$fragvoltot), 0, hz_data$fragvoltot) # this is computed by soilDB::simplifyFragmentData() - h$total_frags_pct <- ifelse(is.na(h$total_frags_pct), 0, h$total_frags_pct) + hz_data$total_frags_pct <- ifelse(is.na(hz_data$total_frags_pct), 0, hz_data$total_frags_pct) # this is computed by soilDB::simplifyFragmentData() # no para-frags - h$total_frags_pct_nopf <- ifelse(is.na(h$total_frags_pct_nopf), 0, h$total_frags_pct_nopf) + hz_data$total_frags_pct_nopf <- ifelse(is.na(hz_data$total_frags_pct_nopf), 0, hz_data$total_frags_pct_nopf) # this is computed by soilDB::simplifyArtifactData() - h$total_art_pct <- ifelse(is.na(h$total_art_pct), 0, h$total_art_pct) + hz_data$total_art_pct <- ifelse(is.na(hz_data$total_art_pct), 0, hz_data$total_art_pct) } ## TODO: convert this to simplifyFragmentData # add surface frag summary sfs <- extended_data$surf_frag_summary + # optionally convert NA fragvol to 0 - if(nullFragsAreZero) { + if (nullFragsAreZero) { sfs <- as.data.frame( - cbind(sfs[, 1, drop=FALSE], + cbind(sfs[, 1, drop = FALSE], lapply(sfs[, -1], function(i) ifelse(is.na(i), 0, i)) - ), stringsAsFactors=FALSE) + ), stringsAsFactors = FALSE) } # add surf. frag summary to @site - site(h) <- sfs + site(hz_data) <- sfs # load diagnostic horizons into @diagnostic: # supress warnings: diagnostic_hz() <- is noisy when not all profiles have diagnostic hz data - suppressWarnings(diagnostic_hz(h) <- extended_data$diagnostic) + suppressWarnings(diagnostic_hz(hz_data) <- extended_data$diagnostic) # add restrictions to SPC # required new setter in aqp SPC object (AGB added 2019/12/23) - suppressWarnings(restrictions(h) <- extended_data$restriction) + suppressWarnings(restrictions(hz_data) <- extended_data$restriction) - # join-in landform string - ed.lf <- split(extended_data$geomorph, extended_data$geomorph$peiid) - lf <- do.call('rbind', lapply(ed.lf, .formatLandformString, name.sep=' & ')) - site(h) <- lf + # join-in landform string w/ ampersand as separator for hierarchy + ed.lf <- data.table::as.data.table(extended_data$geomorph) + lf <- ed.lf[, .formatLandformString(.SD, uid = .BY$peiid, name.sep = ' & '), + by = list(peiid = ed.lf$peiid)] + site(hz_data) <- as.data.frame(lf[,c("peiid","landform_string")]) - # join-in parent material strings - ed.pm <- split(extended_data$pm, extended_data$pm$siteiid) - pm <- do.call('rbind', lapply(ed.pm, .formatParentMaterialString, name.sep=' & ')) - site(h) <- pm + ed.pm <- data.table::as.data.table(extended_data$pm) + pm <- ed.pm[, .formatParentMaterialString(.SD, uid = .BY$siteiid, name.sep = ' & '), + by = list(siteiid = ed.pm$siteiid)] + site(hz_data) <- as.data.frame(pm[,c("siteiid","pmkind","pmorigin")]) - # set metadata - m <- metadata(h) +# set metadata + m <- metadata(hz_data) m$origin <- 'NASIS pedons' - metadata(h) <- m + metadata(hz_data) <- m # print any messages on possible data quality problems: - if(exists('sites.missing.pedons', envir=soilDB.env)) - if(length(get('sites.missing.pedons', envir=soilDB.env)) > 0) - message("-> QC: sites without pedons: use `get('sites.missing.pedons', envir=soilDB.env)` for related usersiteid values") + if (exists('sites.missing.pedons', envir = soilDB.env)) + if (length(get('sites.missing.pedons', envir = soilDB.env)) > 0) + message("-> QC: sites without pedons: see `get('sites.missing.pedons', envir=soilDB.env)`") - if(exists('dup.pedon.ids', envir=soilDB.env)) - if(length(get('dup.pedon.ids', envir=soilDB.env)) > 0) - message("-> QC: duplicate pedons: use `get('dup.pedon.ids', envir=soilDB.env)` for related peiid values") + if (exists('dup.pedon.ids', envir = soilDB.env)) + if (length(get('dup.pedon.ids', envir = soilDB.env)) > 0) + message("-> QC: duplicate pedons: see `get('dup.pedon.ids', envir=soilDB.env)`") # set NASIS-specific horizon identifier - tryCatch(hzidname(h) <- 'phiid', error = function(e) { - if(grepl(e$message, pattern="not unique$")) { - if(!rmHzErrors) { + tryCatch(hzidname(hz_data) <- 'phiid', error = function(e) { + if (grepl(e$message, pattern = "not unique$")) { + if (!rmHzErrors) { # if rmHzErrors = FALSE, keep unique integer assigned ID to all records automatically - message("-> QC: duplicate horizons are present with rmHzErrors=FALSE! defaulting to `hzID` as unique horizon ID.") + message("-> QC: duplicated horizons found! defaulting to `hzID` as unique horizon ID.") } else { stop(e) } @@ -220,29 +239,28 @@ # set hz designation and texture fields -- NB: chose to use calculated texture -- more versatile # functions designed to use hztexclname() should handle presence of in-lieu, modifiers, etc. - hzdesgnname(h) <- "hzname" - hztexclname(h) <- "texture" + hzdesgnname(hz_data) <- "hzname" + hztexclname(hz_data) <- "texture" - if(exists('bad.pedon.ids', envir=soilDB.env)) - if(length(get('bad.pedon.ids', envir=soilDB.env)) > 0) + if (exists('bad.pedon.ids', envir = soilDB.env)) + if (length(get('bad.pedon.ids', envir = soilDB.env)) > 0) message("-> QC: horizon errors detected, use `get('bad.pedon.ids', envir=soilDB.env)` for related userpedonid values or `get('bad.horizons', envir=soilDB.env)` for related horizon designations") - if(exists('missing.bottom.depths', envir=soilDB.env)) - if(length(get('missing.bottom.depths', envir=soilDB.env)) > 0) + if (exists('missing.bottom.depths', envir = soilDB.env)) + if (length(get('missing.bottom.depths', envir = soilDB.env)) > 0) message("-> QC: pedons missing bottom hz depths: use `get('missing.bottom.depths', envir=soilDB.env)` for related pedon IDs") - if(exists('top.bottom.equal', envir=soilDB.env)) - if(length(get('top.bottom.equal', envir=soilDB.env)) > 0) + if (exists('top.bottom.equal', envir = soilDB.env)) + if (length(get('top.bottom.equal', envir = soilDB.env)) > 0) message("-> QC: equal hz top and bottom depths: use `get('top.bottom.equal', envir=soilDB.env)` for related pedon IDs") ## https://github.com/ncss-tech/soilDB/issues/44 # optionally load phlabresults table if (lab) { - phlabresults <- .get_phlabresults_data_from_NASIS_db(SS=SS) - horizons(h) <- phlabresults - #h <- join(h, phlabresults, by = "phiid", type = "left") + phlabresults <- .get_phlabresults_data_from_NASIS_db(SS = SS) + horizons(hz_data) <- phlabresults } # done - return(h) + return(hz_data) } diff --git a/R/fetchNOAA.R b/R/fetchNOAA.R index c50216b4..3d8b9511 100644 --- a/R/fetchNOAA.R +++ b/R/fetchNOAA.R @@ -7,7 +7,7 @@ #' Query the NOAA API to get station data near a given latitude and longitude #' -#' @description Query the NOAA API to get station data (limit 1000 records) near a point. Default extent is plus or minus 0.5 degrees (bounding box) (with \code{bbox = 1}) around the specified point [lat, lng]. +#' @description Query the NOAA API to get station data (limit 1000 records) near a point. Default extent is plus or minus 0.5 degrees (bounding box) (with \code{bbox = 1}) around the specified point \[lat, lng]. #' #' In order to use this function, you must obtain an API token from this website: https://www.ncdc.noaa.gov/cdo-web/token #' diff --git a/R/fetchPedonPC.R b/R/fetchPedonPC.R index d86dc370..f84d7bfd 100644 --- a/R/fetchPedonPC.R +++ b/R/fetchPedonPC.R @@ -1,85 +1,109 @@ -# horizon checking may be too strict - -fetchPedonPC <- function(dsn) { - - # not in parity with NASIS functions - warning("Loading data from PedonPC will return slightly different data structures than fetchNASIS().", call. = FALSE) - - # load data in pieces - site_data <- get_site_data_from_pedon_db(dsn) - hz_data <- get_hz_data_from_pedon_db(dsn) - color_data <- get_colors_from_pedon_db(dsn) - extended_data <- get_extended_data_from_pedon_db(dsn) - - # join pieces - # horizon + hz color: all horizons - h <- join(hz_data, color_data, by='phiid', type='left') - - # convert colors... in the presence of missing color data - h$soil_color <- NA - idx <- complete.cases(h$m_r) - h$soil_color[idx] <- with(h[idx, ], rgb(m_r, m_g, m_b)) # moist colors - - # replace horizons with hz + fragment summary - h <- join(h, extended_data$frag_summary, by='phiid', type='left') - - # fix some common problems - # replace missing lower boundaries - message('replacing missing lower horizon boundaries ...') - missing.lower.depth.idx <- which(!is.na(h$hzdept) & is.na(h$hzdepb)) - h$hzdepb[missing.lower.depth.idx] <- h$hzdept[missing.lower.depth.idx] + 1 - - # test for bad horizonation... flag, and remove - cat('finding horizonation errors ...\n') - h.test <- ddply(h, 'peiid', function(d) { - res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) - return(data.frame(hz_logic_pass=all(!res))) - }) - - # which are the good (valid) ones? - good.pedon.ids <- as.character(h.test$peiid[which(h.test$hz_logic_pass)]) - bad.pedon.ids <- as.character(h.test$pedon_id[which(!h.test$hz_logic_pass)]) - - # keep the good ones - h <- h[which(h$peiid %in% good.pedon.ids), ] - - # upgrade to SoilProfilecollection - depths(h) <- peiid ~ hzdept + hzdepb - - ## TODO: this is slow - # move pedon_id into @site, this will be used to join full table of site data - site(h) <- ~ pedon_id - - ## TODO: this will fail in the presence of duplicates - # add site data to object - site_data$pedon_id <- NULL # remove 'pedon_id' column from site_data - site(h) <- site_data # left-join via peiid - - # load diagnostic horizons into @diagnostic - diagnostic_hz(h) <- extended_data$diagnostic - - # add diagnostic boolean data into @site - site(h) <- extended_data$diagHzBoolean - - ### TODO: consider moving this into the extended data function ### - # load best-guess optimal records from taxhistory - # method is added to the new field called 'selection_method' - best.tax.data <- ddply(extended_data$taxhistory, 'peiid', .pickBestTaxHistory) - site(h) <- best.tax.data - - # join-in landform string - lf <- ddply(extended_data$geomorph, 'peiid', .formatLandformString, name.sep='|') - if(nrow(lf) > 0) - site(h) <- lf - - # set PedonPC/NASIS-specific horizon identifier - hzidname(h) <- 'phiid' - - # 7. save and mention bad pedons - assign('bad.pedon.ids', value=bad.pedon.ids, envir=soilDB.env) - if(length(bad.pedon.ids) > 0) - message("horizon errors detected, use `get('bad.pedon.ids', envir=soilDB.env)` for a list of pedon IDs") - - # done - return(h) -} +# horizon checking may be too strict + + + +#' Fetch commonly used site/horizon data from a PedonPC v.5 database. +#' +#' Fetch commonly used site/horizon data from a version 5.x PedonPC database, +#' return as a SoilProfileCollection object. +#' +#' This function currently works only on Windows. +#' +#' @aliases fetchPedonPC getHzErrorsPedonPC +#' @param dsn The path to a PedonPC version 5.x database +#' @return a SoilProfileCollection class object +#' @note This function attempts to do most of the boilerplate work when +#' extracting site/horizon data from a PedonPC or local NASIS database. Pedons +#' that have errors in their horizonation are excluded from the returned +#' object, however, their IDs are printed on the console. See +#' \code{\link{getHzErrorsPedonPC}} for a simple approach to identifying pedons +#' with problematic horizonation. Records from the 'taxhistory' table are +#' selected based on 1) most recent record, or 2) record with the least amount +#' of missing data. +#' @author D. E. Beaudette and J. M. Skovlin +#' @seealso \code{\link{get_hz_data_from_pedon_db}} +#' @keywords manip +#' @export fetchPedonPC +fetchPedonPC <- function(dsn) { + + # not in parity with NASIS functions + warning("Loading data from PedonPC will return slightly different data structures than fetchNASIS().", call. = FALSE) + + # load data in pieces + site_data <- get_site_data_from_pedon_db(dsn) + hz_data <- get_hz_data_from_pedon_db(dsn) + color_data <- get_colors_from_pedon_db(dsn) + extended_data <- get_extended_data_from_pedon_db(dsn) + + # join pieces + # horizon + hz color: all horizons + h <- join(hz_data, color_data, by='phiid', type='left') + + # convert colors... in the presence of missing color data + h$soil_color <- NA + idx <- complete.cases(h$m_r) + h$soil_color[idx] <- with(h[idx, ], rgb(m_r, m_g, m_b)) # moist colors + + # replace horizons with hz + fragment summary + h <- join(h, extended_data$frag_summary, by='phiid', type='left') + + # fix some common problems + # replace missing lower boundaries + message('replacing missing lower horizon boundaries ...') + missing.lower.depth.idx <- which(!is.na(h$hzdept) & is.na(h$hzdepb)) + h$hzdepb[missing.lower.depth.idx] <- h$hzdept[missing.lower.depth.idx] + 1 + + # test for bad horizonation... flag, and remove + cat('finding horizonation errors ...\n') + h.test <- ddply(h, 'peiid', function(d) { + res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) + return(data.frame(hz_logic_pass=all(!res))) + }) + + # which are the good (valid) ones? + good.pedon.ids <- as.character(h.test$peiid[which(h.test$hz_logic_pass)]) + bad.pedon.ids <- as.character(h.test$pedon_id[which(!h.test$hz_logic_pass)]) + + # keep the good ones + h <- h[which(h$peiid %in% good.pedon.ids), ] + + # upgrade to SoilProfilecollection + depths(h) <- peiid ~ hzdept + hzdepb + + ## TODO: this is slow + # move pedon_id into @site, this will be used to join full table of site data + site(h) <- ~ pedon_id + + ## TODO: this will fail in the presence of duplicates + # add site data to object + site_data$pedon_id <- NULL # remove 'pedon_id' column from site_data + site(h) <- site_data # left-join via peiid + + # load diagnostic horizons into @diagnostic + diagnostic_hz(h) <- extended_data$diagnostic + + # add diagnostic boolean data into @site + site(h) <- extended_data$diagHzBoolean + + ### TODO: consider moving this into the extended data function ### + # load best-guess optimal records from taxhistory + # method is added to the new field called 'selection_method' + best.tax.data <- ddply(extended_data$taxhistory, 'peiid', .pickBestTaxHistory) + site(h) <- best.tax.data + + # join-in landform string + lf <- ddply(extended_data$geomorph, 'peiid', .formatLandformString, name.sep='|') + if(nrow(lf) > 0) + site(h) <- lf + + # set PedonPC/NASIS-specific horizon identifier + hzidname(h) <- 'phiid' + + # 7. save and mention bad pedons + assign('bad.pedon.ids', value=bad.pedon.ids, envir=soilDB.env) + if(length(bad.pedon.ids) > 0) + message("horizon errors detected, use `get('bad.pedon.ids', envir=soilDB.env)` for a list of pedon IDs") + + # done + return(h) +} diff --git a/R/fetchRaCA.R b/R/fetchRaCA.R index 19cb7b07..54b530ed 100644 --- a/R/fetchRaCA.R +++ b/R/fetchRaCA.R @@ -8,7 +8,7 @@ #' @param state a two-letter US state abbreviation; case-insensitive #' @param rcasiteid a RaCA site id (e.g. 'C1609C01') #' @param get.vnir logical, should associated VNIR spectra be downloaded? (see details) -#' @details The VNIR spectra associated with RaCA data are quite large [each gzip-compressed VNIR spectra record is about 6.6kb], so requests for these data are disabled by default. Note that VNIR spectra can only be queried by soil series or geographic BBOX. +#' @details The VNIR spectra associated with RaCA data are quite large \[each gzip-compressed VNIR spectra record is about 6.6kb], so requests for these data are disabled by default. Note that VNIR spectra can only be queried by soil series or geographic BBOX. #' @return { #' \describe{ #' \item{\code{pedons}:}{a \code{SoilProfileCollection} object containing site/pedon/horizon data} @@ -31,51 +31,51 @@ #' \donttest{ #' if(requireNamespace("curl") & #' curl::has_internet()) { -#' +#' #' if(require(aqp)) { -#' +#' #' # search by series name #' s <- fetchRaCA(series='auburn') -#' +#' #' # search by bounding-box #' # s <- fetchRaCA(bbox=c(-120, 37, -122, 38)) -#' +#' #' # check structure #' str(s, 1) -#' +#' #' # extract pedons #' p <- s$pedons -#' +#' #' # how many pedons #' length(p) -#' -#' # plot +#' +#' # plot #' par(mar=c(0,0,0,0)) #' plot(p, name='hzn_desgn', max.depth=150) #' } #' } #' } fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vnir=FALSE) { - + # important: change the default behavior of data.frame opt.original <- options(stringsAsFactors = FALSE) - + # sanity-check: user must supply some kind of criteria if(missing(series) & missing(state) & missing(bbox) & missing(rcasiteid)) stop('you must provide some filtering criteria', call.=FALSE) - + # sanity-check: cannot request VNIR by state if(!missing(state) & get.vnir) stop('VNIR spectra cannot be requested for an entire state', call.=FALSE) - - + + ## 2015-09-23 ## releasing point data for privates lands may be a problem, coordinates are truncated to 2 decimal places message('Site coordinates have been truncated to 2 decimal places, contact the National Soil Survey Center for more detailed coordinates.') - + # init empty filter f <- vector() - + # init empty pieces s <- NULL h <- NULL @@ -85,28 +85,28 @@ fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vn sample <- NULL vnir <- NULL spectra <- NULL - + # process filter components if(!missing(series)) { f <- c(f, paste('&series=', series, sep='')) } - + if(!missing(bbox)) { bbox <- paste(bbox, collapse=',') f <- c(f, paste('&bbox=', bbox, sep='')) } - + if(!missing(state)) { f <- c(f, paste('&state=', state, sep='')) } - + if(!missing(rcasiteid)) { f <- c(f, paste('&rcasiteid=', rcasiteid, sep='')) } - + # combine filters f <- paste(f, collapse='') - + # build URLs site.url <- URLencode(paste('https://casoilresource.lawr.ucdavis.edu/soil_web/rca/rca_query.php?what=site', f, sep='')) hz.url <- URLencode(paste('https://casoilresource.lawr.ucdavis.edu/soil_web/rca/rca_query.php?what=horizon', f, sep='')) @@ -115,7 +115,7 @@ fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vn stock.url <- URLencode(paste('https://casoilresource.lawr.ucdavis.edu/soil_web/rca/rca_query.php?what=stock', f, sep='')) sample.url <- URLencode(paste('https://casoilresource.lawr.ucdavis.edu/soil_web/rca/rca_query.php?what=sample', f, sep='')) vnir.url <- URLencode(paste('https://casoilresource.lawr.ucdavis.edu/soil_web/rca/rca_query.php?what=vnir', f, sep='')) - + # init temp files tf.site <- tempfile() tf.hz <- tempfile() @@ -124,7 +124,7 @@ fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vn tf.stock <- tempfile() tf.sample <- tempfile() tf.vnir <- tempfile() - + # download pieces download.file(url=site.url, destfile=tf.site, mode='wb', quiet=TRUE) download.file(url=hz.url, destfile=tf.hz, mode='wb', quiet=TRUE) @@ -132,32 +132,32 @@ fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vn download.file(url=veg.url, destfile=tf.veg, mode='wb', quiet=TRUE) download.file(url=stock.url, destfile=tf.stock, mode='wb', quiet=TRUE) download.file(url=sample.url, destfile=tf.sample, mode='wb', quiet=TRUE) - + # load pieces try(s <- read.table(gzfile(tf.site), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) try(h <- read.table(gzfile(tf.hz), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) try(trees <- read.table(gzfile(tf.trees), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) try(veg <- read.table(gzfile(tf.veg), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) - + ### 2014-01-16: data need to be re-generated, offline for now: message('Carbon concentration and stock values are probably wrong, or at least suspect. USE WITH CAUTION.') try(stock <- read.table(gzfile(tf.stock), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) try(sample <- read.table(gzfile(tf.sample), header=TRUE, sep='|', quote='', comment.char=''), silent=TRUE) - + # optionally load spectra if(get.vnir) { message('spectra are large, download may take some time...', appendLF=TRUE) - + # save the file locally download.file(url=vnir.url, destfile=tf.vnir, mode='wb', quiet=TRUE) # try to open try(vnir <- read.table(gzfile(tf.vnir), header=TRUE, sep='|'), silent=TRUE) - + # test for missing data if(!is.null(vnir)) { # extract and parse the serialized spectra as matrix spectra <- as.matrix(read.table(textConnection(vnir$spectra), header=FALSE, sep=',')) - + # since order is preserved (row-wise and col-wise), we can assign: # rownames = sample_id # colnames = wavenumber @@ -165,45 +165,45 @@ fetchRaCA <- function(series=NULL, bbox=NULL, state=NULL, rcasiteid=NULL, get.vn dimnames(spectra)[[2]] <- 350:2500 } } - + # report missing data if(all(c(is.null(s), is.null(h)))) { stop('query returned no data', call.=FALSE) } - + # upgrade to SoilProfileCollection depths(h) <- rcapid ~ hzdept + hzdepb - + # extract landuse, region, soilgroup as characters s$landuse <- substr(s$rcasiteid, 6, 6) s$region <- substr(s$rcasiteid, 2, 3) s$soilgroup <- substr(s$rcasiteid, 2, 5) - + # set NASIS-specific horizon identifier hzidname(h) <- 'phiid' - + # set optional hz designation and texture slots hzdesgnname(h) <- "hzname" hztexclname(h) <- "texture_class" - + # merge-in site data site(h) <- s - + # don't init coordinates, as it would be hard for the user to update later # coordinates(h) <- ~ x + y # proj4string(h) <- '+proj=longlat +datum=WGS84' - + # reset options: options(opt.original) - + # pack into a list for the user res <- list(pedons=h, trees=trees, veg=veg, stock=stock, sample=sample, spectra=spectra) res.size <- round(object.size(res) / 1024 / 1024, 2) - + # some feedback via message: message(paste(length(unique(h$rcasiteid)), ' RaCA sites loaded (', res.size, ' Mb transferred)', sep='')) - + # done return(res) - + } diff --git a/R/fetchSCAN.R b/R/fetchSCAN.R index 3cde8119..99599288 100644 --- a/R/fetchSCAN.R +++ b/R/fetchSCAN.R @@ -22,8 +22,6 @@ ### "STO.I-1:-2", "STO.I-1:-4", "STO.I-1:-8", "STO.I-1:-20", "STO.I-1:-40", ### "STO.I-2:-2", "STO.I-2:-4", "STO.I-2:-8", "STO.I-2:-20", "STO.I-2:-40" - - ## ## ideas: ## https://github.com/gunnarleffler/getSnotel @@ -31,125 +29,154 @@ ## site images: ## https://www.wcc.nrcs.usda.gov/siteimages/462.jpg -## -## site notes: +## +## site notes: ## https://wcc.sc.egov.usda.gov/nwcc/sitenotes?sitenum=462 ## - ## TODO: this crashes on 32bit R / libraries # helper function for getting a single table of SCAN metadata # site.code: a single SCAN site code .get_single_SCAN_metadata <- function(site.code) { # base URL to service uri <- 'https://wcc.sc.egov.usda.gov/nwcc/sensors' - + # note: the SCAN form processor checks the refering page and user-agent new.headers <- c( "Referer"="https://wcc.sc.egov.usda.gov/nwcc/sensors", "User-Agent" = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13" ) - + # enable follow-location # http://stackoverflow.com/questions/25538957/suppressing-302-error-returned-by-httr-post # cf <- httr::config(followlocation = 1L, verbose=1L) # debugging cf <- httr::config(followlocation = 1L) - + req <- list(sitenum=site.code, report='ALL', interval='DAY', timeseries=" View Daily Sensor Descriptions ") - + # submit request r <- httr::POST(uri, body=req, encode='form', config = cf, httr::add_headers(new.headers)) httr::stop_for_status(r) - + # parsed XML r.content <- httr::content(r, as='parsed') # get tables n.tables <- rvest::html_nodes(r.content, "table") - + ## TODO: this line crashes on 32bit R / libraries # the metadata table we want is 5th m <- rvest::html_table(n.tables[[5]], header=FALSE) - + # clean-up table # 1st row is header h <- make.names(m[1, ]) # second row is junk m <- m[-c(1:2), ] names(m) <- h - + return(m) } - - - # iterate over a vector of SCAN site codes, returning basic metadata # site.code: vector of SCAN site codes SCAN_sensor_metadata <- function(site.code) { - + # check for 64bit mode if(.Machine$sizeof.pointer != 8) stop("Sorry! For some reason this function crashes in 32bit mode, I don't know why!", call. = FALSE) - + # check for required packages if(!requireNamespace('httr', quietly = TRUE) | !requireNamespace('rvest', quietly = TRUE)) stop('please install the `httr` and `rvest` packages', call.=FALSE) - + # iterate over site codes, returning DF + site.code res <- ddply(data.frame(site.code=site.code), 'site.code', .get_single_SCAN_metadata) - + return(res) } - - ## https://github.com/ncss-tech/soilDB/issues/61 # site.code: vector of SCAN site codes SCAN_site_metadata <- function(site.code) { # hack to please R CMD check SCAN_SNOTEL_metadata <- NULL - + # cached copy available in soilDB::SCAN_SNOTEL_metadata load(system.file("data/SCAN_SNOTEL_metadata.rda", package="soilDB")[1]) - + # subset requested codes res <- SCAN_SNOTEL_metadata[which(SCAN_SNOTEL_metadata$Site %in% site.code), ] - + return(res) } - - # site.code: vector of site codes # year: vector of years # report: single report type # req: for backwards compatibility + + +#' Fetch SCAN Data +#' +#' Query soil/climate data from USDA-NRCS SCAN Stations (experimental) +#' +#' See \href{http://ncss-tech.github.io/AQP/soilDB/fetchSCAN-demo.html}{the fetchSCAN tutorial for details}. These functions require the `httr` and `rvest` libraries. +#' +#' @aliases fetchSCAN SCAN_sensor_metadata SCAN_site_metadata +#' @param site.code a vector of site codes +#' @param year a vector of years +#' @param report report name, single value only +#' @param req list of SCAN request parameters, for backwards-compatibility only +#' @return a \code{data.frame} object +#' @note \code{SCAN_sensor_metadata()} is known to crash on 32bit R / libraries (Windows). +#' @author D.E. Beaudette +#' @references https://www.wcc.nrcs.usda.gov/index.html +#' @keywords manip +#' @examples +#' +#' \donttest{ +#' if(requireNamespace("curl") & +#' curl::has_internet()) { +#' +#' # get data: new interface +#' x <- fetchSCAN(site.code=c(356, 2072), year=c(2015, 2016)) +#' str(x) +#' +#' # get sensor metadata +#' m <- SCAN_sensor_metadata(site.code=c(356, 2072)) +#' +#' # get site metadata +#' m <- SCAN_site_metadata(site.code=c(356, 2072)) +#' } +#' } +#' +#' @export fetchSCAN fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { - + ## backwards compatibility: if(!missing(req)) { message('using old-style interface...') return(.get_SCAN_data(req)) } - + # check for required packages if(!requireNamespace('httr', quietly = TRUE)) stop('please install the `httr` package', call.=FALSE) - + # init list to store results res <- list() - + # add metadata from cached table in soilDB m <- SCAN_site_metadata(site.code) res[['metadata']] <- m - + # all possible combinations of site codes and year | single report type g <- expand.grid(s=site.code, y=year, r=report) - + # get a list of request lists req.list <- mapply(.make_SCAN_req, s=g$s, y=g$y, r=g$r, SIMPLIFY = FALSE) - + # format raw data into a list of lists: # sensor suite -> site number -> year d.list <- list() @@ -157,18 +184,18 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { # raw data is messy, reformat # when there are no data, result is NULL d <- .get_SCAN_data(i) - + ## TODO: sometimes these labels will match multiple sensors ## TODO: this is wasteful as then entire year's worth of data is passed around for each sensor code - + # save: sensor suite -> site number -> year sensors <- c('SMS', 'STO', 'SAL', 'TAVG', 'TMIN', 'TMAX', 'PRCP', 'PREC', 'SNWD', 'WTEQ', 'WDIRV', 'WSPDV', 'LRADT') for(sensor.i in sensors) { d.list[[sensor.i]][[as.character(i$sitenum)]][[as.character(i$year)]] <- .formatSCAN_soil_sensor_suites(d, code=sensor.i) } - + } - + # iterate over sensors for(sensor.i in sensors) { # flatten individual sensors over years, by site number @@ -176,12 +203,12 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { r.i$.id <- NULL res[[sensor.i]] <- r.i } - + # report object size res.size <- round(object.size(res) / 1024 / 1024, 2) res.rows <- sum(sapply(res, nrow), na.rm=TRUE) message(paste(res.rows, ' records (', res.size, ' Mb transferred)', sep='')) - + return(res) } @@ -190,16 +217,16 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { # combine soil sensor suites into stackable format .formatSCAN_soil_sensor_suites <- function(d, code) { - + # hacks to make R CMD check --as-cran happy: value <- NULL - + # locate named columns d.cols <- grep(code, names(d)) # return NULL if no data if(length(d.cols) == 0) return(NULL) - + ## https://github.com/ncss-tech/soilDB/issues/14 ## temporary hack to inform users that there are multiple sensors / label ## this is (usually) only a problem for above-ground sensors @@ -208,8 +235,8 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { # use only the first sensor d.cols <- d.cols[1] } - - + + # convert to long format d.long <- melt(d, id.vars = c('Site', 'Date'), measure.vars = names(d)[d.cols]) # extract depths @@ -219,8 +246,8 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { d.long$depth <- round(d.long$depth * 2.54) # change 'variable' to 'sensor.id' names(d.long)[which(names(d.long) == 'variable')] <- 'sensor.id' - - + + ## NOTE: this doesn't work when applied to above-ground sensors ## https://github.com/ncss-tech/soilDB/issues/14 ## there can also be multiple sensors per below-ground label @@ -228,31 +255,31 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { most.data <- ddply(sensors.per.depth, 'depth', .fun=function(i) { return(as.character(i$sensor.id[which.max(i$no.na)])) }) - + # check for multiple sensors per depth tab <- table(sensors.per.depth$depth) > 1 if(any(tab)) { multiple.sensor.ids <- as.character(sensors.per.depth$sensor.id[which(sensors.per.depth$depth %in% names(tab))]) message(paste0('multiple below-ground sensors per depth: ', paste(multiple.sensor.ids, collapse = ', '))) } - - + + ## BUG in the data output from SCAN! # mulitple rows / day, second row in set has NA in sensor values # this is related to site visits # https://github.com/ncss-tech/soilDB/issues/26 - # + # # solution is simple remove NAs idx <- which(!is.na(d.long$value)) d.long <- d.long[idx, ] - + # water year/day: October 1st -- September 30th w <- waterDayYear(d.long$Date) - + # row-order is preserved d.long$water_year <- w$wy d.long$water_day <- w$wd - + # format and return return(d.long[, c('Site', 'Date', 'water_year', 'water_day', 'value', 'depth', 'sensor.id')]) } @@ -270,73 +297,73 @@ fetchSCAN <- function(site.code, year, report='SCAN', req=NULL) { ## this is an internally used function # req is a named vector or list .get_SCAN_data <- function(req) { - + # convert to list as needed if( ! inherits(req, 'list')) req <- as.list(req) - + # base URL to service uri <- 'https://wcc.sc.egov.usda.gov/nwcc/view' - + # note: the SCAN form processor checks the refering page and user-agent new.headers <- c( "Referer"="https://wcc.sc.egov.usda.gov/nwcc/", "User-Agent" = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13" ) - + # enable follow-location # http://stackoverflow.com/questions/25538957/suppressing-302-error-returned-by-httr-post # cf <- httr::config(followlocation = 1L, verbose=1L) # debugging cf <- httr::config(followlocation = 1L) - + # submit request r <- httr::POST(uri, body=req, encode='form', config = cf, httr::add_headers(new.headers)) httr::stop_for_status(r) - + # extract content as text, cannot be directly read-in r.content <- httr::content(r, as='text') - + # connect to the text as a standard file tc <- textConnection(r.content) - + # attempt to read column headers, after skipping the first two lines of data # note: this moves the text connection cursor forward 3 lines # 2018-03-06 DEB: results have an extra line up top, now need to skip 3 lines h <- unlist(read.table(tc, nrows=1, skip=3, header=FALSE, stringsAsFactors=FALSE, sep=',', quote='', strip.white=TRUE, na.strings='-99.9', comment.char='')) - + # the last header is junk (NA) h <- as.vector(na.omit(h)) - + # split colunmn names on white space and keep the first element h <- sapply(strsplit(h, split=' '), function(i) i[[1]]) - + # clean some more junk h <- gsub('-1', '', fixed=TRUE, h) h <- gsub(':-', '_', h) - + # NOTE: we have already read-in the first 3 lines above, therefore we don't need to skip lines here # read as CSV, skipping junk + headers, accomodating white-space and NA values encoded as -99.9 x <- try(read.table(tc, header=FALSE, stringsAsFactors=FALSE, sep=',', quote='', strip.white=TRUE, na.strings='-99.9', comment.char=''), silent = TRUE) - + # catch errors if(class(x) == 'try-error') { close.connection(tc) x <- NULL return(x) } - + # the last column is always junk x[[names(x)[length(x)]]] <- NULL - + # apply truncated column names: names(x) <- h - + # clean-up connections close.connection(tc) - + # convert date to Date class x$Date <- as.Date(x$Date) - + # done return(x) } diff --git a/R/fetchSDA_spatial.R b/R/fetchSDA_spatial.R index 0b101c98..016049fd 100644 --- a/R/fetchSDA_spatial.R +++ b/R/fetchSDA_spatial.R @@ -6,7 +6,7 @@ #' #' This function automatically "chunks" the input vector (using \code{soilDB::makeChunks}) of mapunit identifiers to minimize the likelihood of exceeding the SDA data request size. The number of chunks varies with the \code{chunk.size} setting and the length of your input vector. If you are working with many mapunits and/or large extents, you may need to decrease this number in order to have more chunks. #' -#' Querying regions with complex mapping may require smaller \code{chunk.size}. Numerically adjacent IDs in the input vector may share common qualities (say, all from same soil survey area or region) which could cause specific chunks to perform "poorly" [slow or error] no matter what the chunk size is. Shuffling the order of the inputs using \code{sample} may help to eliminate problems related to this, depending on how you obtained your set of MUKEY/nationalmusym to query. One could feasibly use \code{muacres} as a heuristic to adjust for total acreage within chunks. +#' Querying regions with complex mapping may require smaller \code{chunk.size}. Numerically adjacent IDs in the input vector may share common qualities (say, all from same soil survey area or region) which could cause specific chunks to perform "poorly" (slow or error) no matter what the chunk size is. Shuffling the order of the inputs using \code{sample} may help to eliminate problems related to this, depending on how you obtained your set of MUKEY/nationalmusym to query. One could feasibly use \code{muacres} as a heuristic to adjust for total acreage within chunks. #' #' @param x A vector of MUKEYs / national mapunit symbols (for mupolygon geometry); OR legend keys (LKEY) / area symbols (for sapolygon geometry) #' @@ -25,7 +25,7 @@ #' @param verbose Print messages? #' #' @return A Spatial*DataFrame corresponding to SDA spatial data for all symbols requested. Default result contains geometry with attribute table containing unique feature ID, symbol and area symbol plus additional fields in result specified with `add.fields`. -#' +#' #' @details Note that STATSGO data are fetched using \code{CLIPAREASYMBOL = 'US'} to avoid duplicating state and national subsets of the geometry. #' #' @author Andrew G. Brown @@ -167,7 +167,7 @@ fetchSDA_spatial <- function(x, if (inherits(sub.res$result, 'try-error')) { # explicit handling for a hypothetical unqueryable single mukey - warning("Symbol ", xx, " dropped from result due to error! May exceed the JSON serialization limit or have other topologic problems.", + warning("Symbol ", xx, " dropped from result due to error! May exceed the JSON serialization limit or have other topologic problems.", call. = FALSE) return(NULL) } @@ -209,7 +209,7 @@ fetchSDA_spatial <- function(x, attr(s, "mukey.mean") <- mukey.mean attr(s, "chunk.mean") <- chunk.mean } - + return(s) } diff --git a/R/fetchVegdata.R b/R/fetchVegdata.R index 4ea3a2eb..30ed0eb2 100644 --- a/R/fetchVegdata.R +++ b/R/fetchVegdata.R @@ -1,36 +1,62 @@ # updated to NASIS 6.2 # convenience function for loading most commonly used vegplot information from local NASIS database -fetchVegdata <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) { +#' Load most common vegplot data from local NASIS database +#' @aliases get_vegplot_from_NASIS_db +#' get_vegplot_location_from_NASIS_db get_vegplot_species_from_NASIS_db +#' get_vegplot_textnote_from_NASIS_db get_vegplot_transect_from_NASIS_db +#' get_vegplot_transpecies_from_NASIS_db +#' get_vegplot_tree_si_details_from_NASIS_db +#' get_vegplot_tree_si_summary_from_NASIS_db get_vegplot_trhi_from_NASIS_db +#' get_legend_from_NASIS get_lmuaoverlap_from_NASIS +#' @param SS fetch data from the currently loaded selected set in NASIS or from the entire local database (default: `TRUE`) +#' +#' @param stringsAsFactors logical: should character vectors be converted to +#' factors? This argument is passed to the `uncode()` function. It does not +#' convert those vectors that have been set outside of `uncode()` (i.e. hard +#' coded). +#' +#' @param dsn Optional: path to local SQLite database containing NASIS +#' table structure; default: `NULL` +#' +#' @return A named list containing: "vegplot", "vegplotlocation", "vegplotrhi", "vegplotspecies", "vegtransect", "vegtransplantsum", 'vegsiteindexsum', "vegsiteindexdet", and "vegplottext" tables +#' +#' @export +#' +fetchVegdata <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) { # test connection - if(! 'nasis_local' %in% names(RODBC::odbcDataSources())) + if (!local_NASIS_defined(dsn)) stop('Local NASIS ODBC connection has not been setup. Please see `http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html`.') # 1. load data in pieces - vegplot <- get_vegplot_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegplotlocation <- get_vegplot_location_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegplotrhi <- get_vegplot_trhi_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegplotspecies <- get_vegplot_species_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegtransect <- get_vegplot_transect_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegtransplantsum <- get_vegplot_transpecies_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegsiteindexsum <- get_vegplot_tree_si_summary_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegsiteindexdet <- get_vegplot_tree_si_details_from_NASIS_db(SS=SS, stringsAsFactors = stringsAsFactors) - vegplottext <- get_vegplot_textnote_from_NASIS_db(SS=SS, fixLineEndings=TRUE, stringsAsFactors = stringsAsFactors) + vegplot <- get_vegplot_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegplotlocation <-get_vegplot_location_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegplotrhi <- get_vegplot_trhi_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegplotspecies <- get_vegplot_species_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegtransect <- get_vegplot_transect_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegtransplantsum <- get_vegplot_transpecies_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegsiteindexsum <- get_vegplot_tree_si_summary_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegsiteindexdet <- get_vegplot_tree_si_details_from_NASIS_db(SS = SS, stringsAsFactors = stringsAsFactors, dsn = dsn) + vegplottext <- get_vegplot_textnote_from_NASIS_db(SS = SS, fixLineEndings = TRUE, + stringsAsFactors = stringsAsFactors, dsn = dsn) # test to see if the selected set is loaded if (nrow(vegplot) == 0) message('your selected set is missing either the vegplot, pedon or site table, please load and try again :)') - + + # done - return(list(vegplot=vegplot, - vegplotlocation=vegplotlocation, - vegplotrhi=vegplotrhi, - vegplotspecies=vegplotspecies, - vegtransect=vegtransect, - vegtransplantsum=vegtransplantsum, - vegsiteindexsum=vegsiteindexsum, - vegsiteindexdet=vegsiteindexdet, - vegplottext=vegplottext)) + return(list( + vegplot = vegplot, + vegplotlocation = vegplotlocation, + vegplotrhi = vegplotrhi, + vegplotspecies = vegplotspecies, + vegtransect = vegtransect, + vegtransplantsum = vegtransplantsum, + vegsiteindexsum = vegsiteindexsum, + vegsiteindexdet = vegsiteindexdet, + vegplottext = vegplottext + )) } diff --git a/R/getHzErrorsNASIS.R b/R/getHzErrorsNASIS.R index 728dce0e..3ff8ccef 100644 --- a/R/getHzErrorsNASIS.R +++ b/R/getHzErrorsNASIS.R @@ -1,25 +1,53 @@ -## TODO: this isn't really needed any more -getHzErrorsNASIS <- function(strict=TRUE) { - - # get data - site_data <- get_site_data_from_NASIS_db() - hz_data <- get_hz_data_from_NASIS_db() - - # combine pieces - f <- join(hz_data, site_data, by='peiid', type='inner') - - # ignore missing lower boundary - f.test <- ddply(f, 'pedon_id', function(d) { - res <- aqp::hzDepthTests(top=d[['hzdept']], bottom=d[['hzdepb']]) - return(data.frame(hz_logic_pass=all(!res))) - }) - - # find bad ones - bad.pedon.ids <- as.character(f.test$pedon_id[which(f.test$hz_logic_pass == FALSE)]) - - # now describe the problems - b <- f[which(f$pedon_id %in% bad.pedon.ids), c('peiid', 'pedon_id','hzdept','hzdepb','hzname')] - - return(b) - -} +#' Check pedon horizon table for logic errors +#' +#' Check pedon horizon table for logic errors +#' +#' +#' @param strict how strict should horizon boundaries be checked for +#' consistency: TRUE=more | FALSE=less +#' @param SS fetch data from the currently loaded selected set in NASIS or from +#' the entire local database (default: TRUE) +#' @param dsn Optional: path to local SQLite database containing NASIS +#' table structure; default: NULL +#' @return A data.frame containing problematic records with columns: +#' 'peiid','pedon_id','hzdept','hzdepb','hzname' +#' @export getHzErrorsNASIS +getHzErrorsNASIS <- function(strict = TRUE, SS = TRUE, dsn = NULL) { + + if (!local_NASIS_defined(dsn)) + stop('Local NASIS ODBC connection has not been setup. Please see `http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html`.') + + # get data + site_data <- get_site_data_from_NASIS_db(SS = SS, dsn = dsn) + site_data$pedon_id <- NULL + hz_data <- get_hz_data_from_NASIS_db(SS = SS, dsn = dsn) + + if (nrow(site_data) == 0) { + message("No Site records in NASIS database") + return(data.frame(pedon_id = character(0), hz_logic_pass = logical(0))) + } + + if (nrow(hz_data) == 0) { + message("No Pedon Horizon records in NASIS database") + return(data.frame(pedon_id = character(0), hz_logic_pass = logical(0))) + } + + # combine pieces + f <- merge(hz_data, site_data, by = "peiid", all.x = TRUE, sort = FALSE) + + f.test <- do.call('rbind', lapply(split(f, f$peiid), function(d) { + res <- aqp::hzDepthTests(top = d[['hzdept']], bottom = d[['hzdepb']]) + if (length(res) > 0) + return(data.frame(pedon_id = d$pedon_id, hz_logic_pass = all(!res))) + return(data.frame(pedon_id = character(0), hz_logic_pass = logical(0))) + })) + + # find bad ones + bad.pedon.ids <- as.character(f.test$pedon_id[which(f.test$hz_logic_pass == FALSE)]) + + # now describe the problems + b <- f[which(f$pedon_id %in% bad.pedon.ids), c('peiid', 'pedon_id','hzdept','hzdepb','hzname')] + + return(b) + +} diff --git a/R/get_RMF_from_NASIS_db.R b/R/get_RMF_from_NASIS_db.R index a0b415dc..0d4af0ae 100644 --- a/R/get_RMF_from_NASIS_db.R +++ b/R/get_RMF_from_NASIS_db.R @@ -1,7 +1,4 @@ -get_RMF_from_NASIS_db <- function(SS=TRUE) { - # must have RODBC installed - if(!requireNamespace('RODBC')) - stop('please install the `RODBC` package', call.=FALSE) +get_RMF_from_NASIS_db <- function(SS=TRUE, dsn = NULL) { # RMF # unique-ness enforced via peiid (pedon-level) and phiid (horizon-level) @@ -19,36 +16,34 @@ get_RMF_from_NASIS_db <- function(SS=TRUE) { FROM phredoxfcolor_View_1 ORDER BY phrdxfiidref, colormoistst;" - channel <- .openNASISchannel() - if (channel == -1) + channel <- dbConnectNASIS(dsn) + + if (inherits(channel, 'try-error')) return(data.frame()) # toggle selected set vs. local DB - if(SS == FALSE) { + if (SS == FALSE) { q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE) q.c <- gsub(pattern = '_View_1', replacement = '', x = q.c, fixed = TRUE) } # exec queries - d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) - d.c <- RODBC::sqlQuery(channel, q.c, stringsAsFactors=FALSE) - - # close connection - RODBC::odbcClose(channel) + d <- dbQueryNASIS(channel, q, close = FALSE) + d.c <- dbQueryNASIS(channel, q.c) # uncode domained columns - d <- uncode(d) - d.c <- uncode(d.c) + d <- uncode(d, dsn = dsn) + d.c <- uncode(d.c, dsn = dsn) # convert back to characters / numeric d.c$colormoistst <- as.character(d.c$colormoistst) d.c$colorhue <- as.character(d.c$colorhue) - # careful! + # uncode creates factors, so we have to convert to character first d.c$colorvalue <- as.numeric(as.character(d.c$colorvalue)) d.c$colorchroma <- as.numeric(as.character(d.c$colorchroma)) # done - return(list(RMF=d, RMF_colors=d.c)) + return(list(RMF = d, RMF_colors = d.c)) } diff --git a/R/get_colors_from_NASIS_db.R b/R/get_colors_from_NASIS_db.R index 84b525b4..9ec184ab 100644 --- a/R/get_colors_from_NASIS_db.R +++ b/R/get_colors_from_NASIS_db.R @@ -1,10 +1,26 @@ ## 2013-01-08: now much faster since we only mix/clean data with > 1 color / horizon # results can be referenced via phiid (horizon-level ID) -get_colors_from_NASIS_db <- function(SS=TRUE) { - # must have RODBC installed - if(!requireNamespace('RODBC')) - stop('please install the `RODBC` package', call.=FALSE) + + +#' Extract Soil Color Data from a local NASIS Database +#' +#' Get, format, mix, and return color data from a NASIS database. +#' +#' This function currently works only on Windows. +#' +#' @param SS fetch data from Selected Set in NASIS or from the entire local +#' database (default: `TRUE`) +#' @param dsn Optional: path to local SQLite database containing NASIS +#' table structure; default: `NULL` +#' @return A data.frame with the results. +#' @author Jay M. Skovlin and Dylan E. Beaudette +#' @seealso \code{\link{simplifyColorData}}, +#' \code{\link{get_hz_data_from_NASIS_db}}, +#' \code{\link{get_site_data_from_NASIS_db}} +#' @keywords manip +#' @export get_colors_from_NASIS_db +get_colors_from_NASIS_db <- function(SS = TRUE, dsn = NULL) { # unique-ness enforced via peiid (pedon-level) and phiid (horizon-level) q <- "SELECT peiid, phiid, colormoistst, colorpct as pct, colorhue, colorvalue, colorchroma @@ -14,23 +30,21 @@ get_colors_from_NASIS_db <- function(SS=TRUE) { INNER JOIN phcolor_View_1 ON phorizon_View_1.phiid = phcolor_View_1.phiidref ORDER BY phiid, colormoistst;" - channel <- .openNASISchannel() - if (channel == -1) + channel <- dbConnectNASIS(dsn) + + if (inherits(channel, 'try-error')) return(data.frame()) # toggle selected set vs. local DB - if(SS == FALSE) { + if (SS == FALSE) { q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE) } - # exec query - d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) - - # close connection - RODBC::odbcClose(channel) + # exec query + d <- dbQueryNASIS(channel, q) # uncode domained columns - d <- uncode(d) + d <- uncode(d, stringsAsFactors = FALSE, dsn = dsn) # convert back to characters / numeric d$colormoistst <- as.character(d$colormoistst) @@ -41,7 +55,7 @@ get_colors_from_NASIS_db <- function(SS=TRUE) { d$colorchroma <- as.numeric(as.character(d$colorchroma)) # sanity check, only attempt to simplify colors if there are > 1 rows - if(nrow(d) > 1) { + if (nrow(d) > 1) { # mix colors as-needed, mixing done in CIE LAB space d.final <- simplifyColorData(d, id.var = 'phiid', wt = 'pct') } else { diff --git a/R/get_colors_from_pedon_db.R b/R/get_colors_from_pedon_db.R index a00ed84d..0ac751f3 100644 --- a/R/get_colors_from_pedon_db.R +++ b/R/get_colors_from_pedon_db.R @@ -1,5 +1,20 @@ # 2013-01-08: now much faster since we only mix/clean data with > 1 color / horizon + + +#' Extract Soil Color Data from a PedonPC Database +#' +#' Get, format, mix, and return color data from a PedonPC database. +#' +#' This function currently works only on Windows. +#' +#' @param dsn The path to a 'pedon.mdb' database. +#' @return A data.frame with the results. +#' @author Dylan E. Beaudette and Jay M. Skovlin +#' @seealso \code{\link{get_hz_data_from_pedon_db}}, +#' \code{\link{get_site_data_from_pedon_db}} +#' @keywords manip +#' @export get_colors_from_pedon_db get_colors_from_pedon_db <- function(dsn) { # must have RODBC installed if(!requireNamespace('RODBC')) diff --git a/R/get_component_data_from_NASIS_db.R b/R/get_component_data_from_NASIS_db.R index 4180f1d2..d89aa1f0 100644 --- a/R/get_component_data_from_NASIS_db.R +++ b/R/get_component_data_from_NASIS_db.R @@ -7,35 +7,35 @@ ## component diagnostic features -get_component_diaghz_from_NASIS_db <- function(SS=TRUE) { +get_component_diaghz_from_NASIS_db <- function(SS=TRUE, dsn = NULL) { - channel <- .openNASISchannel() - if (channel == -1) + channel <- dbConnectNASIS(dsn) + + if (inherits(channel, 'try-error')) return(data.frame()) # query diagnostic horizons, usually a 1:many relationship with pedons q <- "SELECT coiidref as coiid, featkind, featdept_l, featdept_r, featdept_h, featdepb_l, featdepb_r, featdepb_h, featthick_l, featthick_r, featthick_h FROM codiagfeatures_View_1 AS cdf ORDER BY cdf.coiidref, cdf.featdept_r;" # toggle selected set vs. local DB - if(SS == FALSE) { + if (SS == FALSE) { q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE) } # exec query - d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) - - # close connection - RODBC::odbcClose(channel) + d <- dbQueryNASIS(channel, q) # convert codes - d <- uncode(d) + d <- uncode(d, dsn = dsn) + } ## component diagnostic features -get_component_restrictions_from_NASIS_db <- function(SS = TRUE) { +get_component_restrictions_from_NASIS_db <- function(SS = TRUE, dsn = NULL) { + + channel <- dbConnectNASIS(dsn) - channel <- .openNASISchannel() - if (channel == -1) + if (inherits(channel, 'try-error')) return(data.frame()) # query restrictions, can be 1:many relationship with pedons @@ -47,17 +47,15 @@ get_component_restrictions_from_NASIS_db <- function(SS = TRUE) { } # exec query - d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) - - # close connection - RODBC::odbcClose(channel) + d <- dbQueryNASIS(channel, q) # convert codes - return(uncode(d)) + return(uncode(d, dsn = dsn) +) } ## get map unit text from local NASIS -get_mutext_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE) { +get_mutext_from_NASIS_db <- function(SS = TRUE, fixLineEndings = TRUE, dsn = NULL) { q <- "SELECT mu.muiid, mu.mukind, mu.mutype, mu.muname, mu.nationalmusym, mut.seqnum, mut.recdate, mut.recauthor, mut.mapunittextkind, mut.textcat, mut.textsubcat, CAST(mut.textentry AS ntext) AS textentry @@ -66,23 +64,21 @@ get_mutext_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE) { mapunit_View_1 AS mu INNER JOIN mutext_View_1 AS mut ON mu.muiid = mut.muiidref;" - channel <- .openNASISchannel() - if (channel == -1) + channel <- dbConnectNASIS(dsn) + + if (inherits(channel, 'try-error')) return(data.frame()) # toggle selected set vs. local DB - if(SS == FALSE) { + if (SS == FALSE) { q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE) } # exec query - d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE) - - # close connection - RODBC::odbcClose(channel) + d <- dbQueryNASIS(channel, q) # convert codes - d <- uncode(d) + d <- uncode(d, dsn = dsn) # replace tabs with spaces # tabs at the beginning of a line will confuse the MD parser, generating
 blocks
@@ -100,44 +96,42 @@ get_mutext_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE) {
 
 
 ## get component text from local NASIS
-get_cotext_from_NASIS_db <- function(SS = TRUE, fixLineEndings = TRUE) {
-  
+get_cotext_from_NASIS_db <- function(SS = TRUE, fixLineEndings = TRUE, dsn = NULL) {
+
   q <- "SELECT co.coiid,
-  cot.seqnum, cot.recdate, cot.recauthor, cot.comptextkind, cot.textcat, cot.textsubcat, 
+  cot.seqnum, cot.recdate, cot.recauthor, cot.comptextkind, cot.textcat, cot.textsubcat,
   CAST(cot.textentry AS ntext) AS textentry
 
   FROM
   component_View_1 AS co
   INNER JOIN cotext_View_1 AS cot ON co.coiid = cot.coiidref;"
-  
-  channel <- .openNASISchannel()
-  if (channel == -1)
-    return(data.frame())
-  
+
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
-  
+
+  # connect to NASIS
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
+
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-  
-  # close connection
-  RODBC::odbcClose(channel)
-  
+  d <- dbQueryNASIS(channel, q)
+
   # convert codes
-  d <- uncode(d)
-  
+  d <- uncode(d, dsn = dsn)
+
   # replace tabs with spaces
   # tabs at the beginning of a line will confuse the MD parser, generating 
 blocks
   d$textentry <- gsub(d$textentry, pattern = '\t', replacement = ' ', fixed = TRUE)
-  
+
   # optionally convert \r\n -> \n
   if(fixLineEndings){
     d$textentry <- gsub(d$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
   }
-  
-  
+
   # done
   return(d)
 }
@@ -145,10 +139,44 @@ get_cotext_from_NASIS_db <- function(SS = TRUE, fixLineEndings = TRUE) {
 
 
 ## just the component records, nothing above or below
-get_component_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+
+
+#' Extract component data from a local NASIS Database
+#'
+#' This function currently works only on Windows.
+#'
+#' @aliases get_component_data_from_NASIS_db get_component_restrictions_from_NASIS_db
+#'
+#' @param SS fetch data from the currently loaded selected set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have set outside of `uncode()` (i.e. hard coded).
+#' The 'factory-fresh' default is TRUE, but this can be changed by setting
+#' options(`stringsAsFactors = FALSE`)
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A list with the results.
+#' @author Dylan E. Beaudette, Stephen Roecker, and Jay M. Skovlin
+#' @seealso \code{\link{fetchNASIS}}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#' if(local_NASIS_defined()) {
+#'  # query text note data
+#'  fc <- try(get_component_data_from_NASIS_db())
+#'
+#'  # show structure of component data returned
+#'  str(fc)
+#' }
+#' }
+#'
+#' @export get_component_data_from_NASIS_db
+get_component_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn= NULL) {
 
   q <- "SELECT dmudesc, compname, comppct_r, compkind, majcompflag, localphase, drainagecl, hydricrating, elev_l, elev_r, elev_h, slope_l, slope_r, slope_h, aspectccwise, aspectrep, aspectcwise, map_l, map_r, map_h, airtempa_l as maat_l, airtempa_r as maat_r, airtempa_h as maat_h, soiltempa_r as mast_r, reannualprecip_r, ffd_l, ffd_r, ffd_h, tfact, wei, weg, nirrcapcl, nirrcapscl, nirrcapunit, irrcapcl, irrcapscl, irrcapunit, frostact, hydricrating, hydgrp, corcon, corsteel, taxclname, taxorder, taxsuborder, taxgrtgroup, taxsubgrp, taxpartsize, taxpartsizemod, taxceactcl, taxreaction, taxtempcl, taxmoistscl, taxtempregime, soiltaxedition, coiid, dmuiid
 
@@ -158,32 +186,30 @@ get_component_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default
 
   ORDER BY dmudesc, comppct_r DESC, compname ASC;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # test for duplicate coiids
   idx <- which(table(d$coiid) > 1)
-  if(length(idx) > 0) {
+  if (length(idx) > 0) {
     dupes <- names(idx)
     assign('dupe.coiids', value=dupes, envir=soilDB.env)
     message("-> QC: duplicate coiids, this should not happen. Use `get('dupe.coiids', envir=soilDB.env)` for related coiid values.")
   }
 
   # uncode metadata domains
-  if(nrow(d) > 0) {
-    d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  if (nrow(d) > 0) {
+    d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
   }
 
   # done
@@ -191,10 +217,7 @@ get_component_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default
 }
 
 
-get_legend_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_legend_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q.legend  <- paste("
                      SELECT
@@ -226,22 +249,20 @@ get_legend_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors
     q.legend <- gsub(pattern = '_View_1', replacement = '', x = q.legend, fixed = TRUE)
   }
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # exec query
-  d.legend <- RODBC::sqlQuery(channel, q.legend, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d.legend <- dbQueryNASIS(channel, q.legend)
 
   # recode metadata domains
   d.legend <- uncode(d.legend,
                      db = "NASIS",
                      droplevels = droplevels,
-                     stringsAsFactors = stringsAsFactors
-                     )
+                     stringsAsFactors = stringsAsFactors,
+                     dsn = dsn)
 
 
   # done
@@ -250,10 +271,7 @@ get_legend_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors
 
 
 
-get_lmuaoverlap_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_lmuaoverlap_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- paste("SELECT
              a.areasymbol, a.areaname, a.areaacres,
@@ -288,19 +306,16 @@ get_lmuaoverlap_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFa
   )
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
-    return(data.frame())
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   d$musym <- as.character(d$musym)
 
@@ -308,8 +323,8 @@ get_lmuaoverlap_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFa
   d <- uncode(d,
               db = "NASIS",
               droplevels = droplevels,
-              stringsAsFactors = stringsAsFactors
-  )
+              stringsAsFactors = stringsAsFactors,
+              dsn = dsn)
 
   # done
   return(d)
@@ -317,10 +332,7 @@ get_lmuaoverlap_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFa
 
 
 
-get_mapunit_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_mapunit_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q.mapunit <- paste("
                      SELECT
@@ -365,26 +377,24 @@ get_mapunit_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactor
                      ;")
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.mapunit <- gsub(pattern = '_View_1', replacement = '', x = q.mapunit, fixed = TRUE)
   }
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # exec query
-  d.mapunit <- RODBC::sqlQuery(channel, q.mapunit, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d.mapunit <- dbQueryNASIS(channel, q.mapunit)
 
   # recode metadata domains
   d.mapunit <- uncode(d.mapunit,
                       db = "NASIS",
                       droplevels = droplevels,
-                      stringsAsFactors = stringsAsFactors
-  )
+                      stringsAsFactors = stringsAsFactors,
+                      dsn = dsn)
 
   # hacks to make R CMD check --as-cran happy:
   metadata <- NULL
@@ -417,10 +427,7 @@ get_mapunit_from_NASIS <- function(SS = TRUE, droplevels = TRUE, stringsAsFactor
 
 # return all rows from correlation -- map unit -- legend map unit -- dmu / legend -- area
 # note that all of these "target tables" have to be selected
-get_component_correlation_data_from_NASIS_db <- function(SS=TRUE, dropAdditional=TRUE, dropNotRepresentative=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_component_correlation_data_from_NASIS_db <- function(SS=TRUE, dropAdditional=TRUE, dropNotRepresentative=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- "SELECT lmapunitiid, mu.muiid, musym, nationalmusym, mu.muname, mukind, mutype, mustatus, muacres, farmlndcl, repdmu, dmuiid, areasymbol, areaname, ssastatus, cordate
 
@@ -434,20 +441,17 @@ get_component_correlation_data_from_NASIS_db <- function(SS=TRUE, dropAdditional
 
   ORDER BY nationalmusym, dmuiid;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   ## TODO: is this a good idea?
   # test for no data
@@ -455,7 +459,7 @@ get_component_correlation_data_from_NASIS_db <- function(SS=TRUE, dropAdditional
     warning('there are no records in your selected set!', call. = FALSE)
 
   # recode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # optionally drop additional | NA mustatus
   if(dropAdditional) {
@@ -493,11 +497,7 @@ get_component_correlation_data_from_NASIS_db <- function(SS=TRUE, dropAdditional
 }
 
 # get geomorphic desc for each component
-get_component_cogeomorph_data_from_NASIS_db <- function(SS=TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
-
+get_component_cogeomorph_data_from_NASIS_db <- function(SS = TRUE, dsn = NULL) {
 
   q <- "SELECT cogeo.coiidref as coiid, cogeo.geomfmod, geomorfeat.geomfname, cogeo.geomfeatid, cogeo.existsonfeat, cogeo.geomfiidref, lower(geomorfeattype.geomftname) as geomftname
 
@@ -509,20 +509,17 @@ get_component_cogeomorph_data_from_NASIS_db <- function(SS=TRUE) {
 
   ORDER BY coiid, geomfeatid ASC;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # done
   return(d)
@@ -530,11 +527,7 @@ get_component_cogeomorph_data_from_NASIS_db <- function(SS=TRUE) {
 
 
 # get copm for each component
-get_component_copm_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
-
+get_component_copm_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- "SELECT cpmg.coiidref as coiid, cpm.seqnum as seqnum, pmorder, pmdept_r, pmdepb_r, pmmodifier, pmgenmod, pmkind, pmorigin
 
@@ -545,35 +538,27 @@ get_component_copm_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = de
 
   ORDER BY coiidref, seqnum, pmorder, copmgrpiid ASC;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
-
-
 # get ESD information for each component
-get_component_esd_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_component_esd_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- "SELECT coiidref as coiid, ecositeid, ecositenm,
   ecositeorigin, ecositetype, ecositemlra, ecositelru, ecositenumber, ecositestate
@@ -584,31 +569,28 @@ get_component_esd_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = def
 
   ORDER BY coiid;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # check for more than 1 record / coiid
   idx <- which(table(d$coiid) > 1)
   dupes <- names(idx)
   assign('multiple.ecosite.per.coiid', value=dupes, envir=soilDB.env)
-  if(length(idx) > 0) {
+  if (length(idx) > 0) {
     message("-> QC: multiple ecosites / component. Use `get('multiple.ecosite.per.coiid', envir=soilDB.env)` for related coiid values.")
   }
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
@@ -616,10 +598,7 @@ get_component_esd_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = def
 
 ## TODO: convert any multiple entries into a comma delimited string
 # get OtherVeg information for each component
-get_component_otherveg_data_from_NASIS_db <- function(SS=TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_component_otherveg_data_from_NASIS_db <- function(SS=TRUE, dsn = NULL) {
 
   q <- "SELECT coiidref as coiid, ovegclid, ovegclname, coothvegcl.recwlupdated
   FROM coothvegclass_View_1 coothvegcl
@@ -627,80 +606,113 @@ get_component_otherveg_data_from_NASIS_db <- function(SS=TRUE) {
   ORDER BY coiid;"
 
   # setup connection local NASIS
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # check for more than 1 record / coiid
   idx <- which(table(d$coiid) > 1)
-  if(length(idx) > 0) {
+  if (length(idx) > 0) {
     dupes <- names(idx)
     assign('multiple.otherveg.per.coiid', value=dupes, envir=soilDB.env)
     message("-> QC: multiple othervegclasses / component. Use `get('multiple.otherveg.per.coiid', envir=soilDB.env)` for related coiid values.")
   }
 
   # uncode metadata domains
-  #d <- uncode(d)
+  #d <- uncode(d, dsn = dsn)
 
   # done
   return(d)
 }
 
-get_comonth_from_NASIS_db <- function(SS=TRUE, fill=FALSE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+
+
+#' Extract component month data from a local NASIS Database
+#'
+#' Extract component month data from a local NASIS Database.
+#'
+#' This function currently works only on Windows.
+#'
+#' @param SS get data from the currently loaded Selected Set in NASIS or from
+#' the entire local database (default: TRUE)
+#' @param fill should missing "month" rows in the comonth table be filled with
+#' NA (FALSE)
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the uncode() function. It does not
+#' convert those vectors that have set outside of uncode() (i.e. hard coded).
+#' The 'factory-fresh' default is TRUE, but this can be changed by setting
+#' options(stringsAsFactors = FALSE)
+#' @return A list with the results.
+#' @author Stephen Roecker
+#' @seealso \code{\link{fetchNASIS}}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#' if(local_NASIS_defined()) {
+#'   # query text note data
+#'   cm <- try(get_comonth_from_NASIS_db())
+#'
+#'   # show structure of component month data
+#'   str(cm)
+#' }
+#' }
+#'
+#' @export get_comonth_from_NASIS_db
+get_comonth_from_NASIS_db <- function(SS = TRUE, fill = FALSE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- "SELECT coiidref AS coiid, month, flodfreqcl, floddurcl, pondfreqcl, ponddurcl, ponddep_l, ponddep_r, ponddep_h, dlyavgprecip_l, dlyavgprecip_r, dlyavgprecip_h, comonthiid
   FROM comonth_View_1 AS comonth;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
+  d <- dbQueryNASIS(channel, q)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # optionally fill missing coiids
-  if(fill) {
+  if (fill) {
     q <- "SELECT coiid
     FROM component_View_1
     ORDER BY coiid;"
 
+    channel <- dbConnectNASIS(dsn)
+
+    if (inherits(channel, 'try-error'))
+      return(data.frame())
+
     # toggle selected set vs. local DB
-    if(SS == FALSE) {
+    if (SS == FALSE) {
       q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
     }
 
     # exec query
-    d.coiid <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
+    d.coiid <- dbQueryNASIS(channel, q)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
   # re-format month names
   # only if > 0 rows of data
-  if(nrow(d) > 0) {
+  if (nrow(d) > 0) {
     # using 3-letter month names
     d$month <- months(as.Date(paste0("2016-", d$month, "-01"), format="%Y-%B-%d"), abbreviate = TRUE)
   }
@@ -718,14 +730,14 @@ get_comonth_from_NASIS_db <- function(SS=TRUE, fill=FALSE, stringsAsFactors = de
 
 
   # optionally fill missing coiids
-  if(fill) {
+  if (fill) {
     # make a new DF with all coiids and months
-    nd <- expand.grid(coiid=d.coiid$coiid, month=levels(d$month))
-    nd$month <- factor(nd$month, levels=levels(d$month))
+    nd <- expand.grid(coiid = d.coiid$coiid, month = levels(d$month))
+    nd$month <- factor(nd$month, levels = levels(d$month))
 
     # join full version to comonth records
     # nd contains the full set of component records IDs
-    d <- join(nd, d, by=c('coiid', 'month'), type='left')
+    d <- join(nd, d, by=c('coiid', 'month'), type = 'left')
 
     ## this isn't likely needed, will re-visit after some testing
 
@@ -756,10 +768,7 @@ get_comonth_from_NASIS_db <- function(SS=TRUE, fill=FALSE, stringsAsFactors = de
 
 # get linked pedons by peiid and user pedon ID
 # note that there may be >=1 pedons / coiid
-get_copedon_from_NASIS_db <- function(SS=TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_copedon_from_NASIS_db <- function(SS=TRUE, dsn = NULL) {
 
   q <- "SELECT coiidref as coiid, peiidref as peiid, upedonid as pedon_id, rvindicator as representative
 
@@ -767,20 +776,19 @@ get_copedon_from_NASIS_db <- function(SS=TRUE) {
 
   LEFT OUTER JOIN pedon_View_1 p ON p.peiid = copedon.peiidref;
   "
-  channel <- .openNASISchannel()
-  if (channel == -1)
+
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   # missing pedon ID suggests records not in the selected set or local database
   if(nrow(d) > 0 & any(is.na(d$pedon_id))) {
@@ -794,10 +802,7 @@ get_copedon_from_NASIS_db <- function(SS=TRUE) {
 
 ## TODO: better documentation for "fill" argument
 # https://github.com/ncss-tech/soilDB/issues/50
-get_component_horizon_data_from_NASIS_db <- function(SS=TRUE, fill = FALSE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_component_horizon_data_from_NASIS_db <- function(SS=TRUE, fill = FALSE, dsn = NULL) {
 
   q <- "SELECT coiid, chiid, hzname, hzdept_r, hzdepb_r, texture, fragvoltot_l, fragvoltot_r, fragvoltot_h, sandtotal_l, sandtotal_r, sandtotal_h, silttotal_l, silttotal_r, silttotal_h, claytotal_l, claytotal_r, claytotal_h, om_l, om_r, om_h, structgrpname, dbthirdbar_l, dbthirdbar_r, dbthirdbar_h, ksat_l, ksat_r, ksat_h, awc_l, awc_r, awc_h, lep_l, lep_r, lep_h, ll_l, ll_r, ll_h, pi_l, pi_r, pi_h, sieveno4_l, sieveno4_r, sieveno4_h, sieveno10_l, sieveno10_r, sieveno10_h, sieveno40_l, sieveno40_r, sieveno40_h, sieveno200_l, sieveno200_r, sieveno200_h, sar_l, sar_r, sar_h, ec_l, ec_r, ec_h, cec7_l, cec7_r, cec7_h, sumbases_l, sumbases_r, sumbases_h, ecec_l, ecec_r, ecec_h, ph1to1h2o_l, ph1to1h2o_r, ph1to1h2o_h, ph01mcacl2_l, ph01mcacl2_r, ph01mcacl2_h, caco3_l, caco3_r, caco3_h, kffact, kwfact, aashind_l, aashind_r, aashind_h
 
@@ -812,20 +817,18 @@ get_component_horizon_data_from_NASIS_db <- function(SS=TRUE, fill = FALSE) {
 
   ORDER BY dmudesc, comppct_r DESC, compname ASC, hzdept_r ASC;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q)
 
   ## TODO: better documentation for "fill" argument
   # https://github.com/ncss-tech/soilDB/issues/50
diff --git a/R/get_component_from_GDB.R b/R/get_component_from_GDB.R
index f2198e25..c40522d5 100644
--- a/R/get_component_from_GDB.R
+++ b/R/get_component_from_GDB.R
@@ -1,4 +1,3 @@
-
 get_component_from_GDB <- function(dsn = "gNATSGO_CONUS.gdb", WHERE = NULL, childs = FALSE, droplevels = TRUE, stringsAsFactors = TRUE) {
 
   # check
@@ -396,6 +395,65 @@ get_mapunit_from_GDB <- function(dsn = "gNATSGO_CONUS.gdb", WHERE = NULL, drople
 }
 
 
+
+
+#' Load and Flatten Data from SSURGO file geodatabases
+#' 
+#' Functions to load and flatten commonly used tables and from SSURGO file
+#' geodatabases, and create soil profile collection objects (SPC).
+#' 
+#' These functions return data from SSURGO file geodatabases with the use of a
+#' simple text string that formatted as an SQL WHERE clause (e.g. \code{WHERE =
+#' "areasymbol = 'IN001'"}. Any columns within the target table can be
+#' specified (except for fetchGDB() currently, which only targets the legend
+#' with the WHERE clause).
+#' 
+#' @aliases fetchGDB get_legend_from_GDB get_mapunit_from_GDB
+#' get_component_from_GDB
+#' @param dsn data source name (interpretation varies by driver - for some
+#' drivers, dsn is a file name, but may also be a folder, or contain the name
+#' and access credentials of a database); in case of GeoJSON, dsn may be the
+#' character string holding the geojson data. It can also be an open database
+#' connection.
+#' @param WHERE text string formatted as an SQL WHERE clause (default: FALSE)
+#' @param childs logical; if FALSE parent material and geomorphic child tables
+#' are not flattened and appended
+#' @param droplevels logical: indicating whether to drop unused levels in
+#' classifying factors. This is useful when a class has large number of unused
+#' classes, which can waste space in tables and figures.
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the uncode() function. It does not
+#' convert those vectors that have set outside of uncode() (i.e. hard coded).
+#' The 'factory-fresh' default is TRUE, but this can be changed by setting
+#' options(stringsAsFactors = FALSE)
+#' @return A \code{data.frame} or \code{SoilProfileCollection} object.
+#' @author Stephen Roecker
+#' @keywords manip
+#' @examples
+#' 
+#' \donttest{
+#' 
+#' ## replace `dsn` with path to your own geodatabase (SSURGO OR gNATSGO)
+#' ##
+#' ##
+#' ##  download CONUS gNATSGO from here:
+#' ##    https://www.nrcs.usda.gov/wps/portal/nrcs/detail/soils/survey/geo/?cid=nrcseprd1464625
+#' ##
+#' ##
+#' # dsn <- "D:/geodata/soils/gNATSGO_CONUS.gdb"
+#' 
+#' # le <- get_legend_from_GDB(dsn = dsn, WHERE = "areasymbol LIKE '%'")
+#' 
+#' # mu <- get_mapunit_from_GDB(dsn = dsn, WHERE = "muname LIKE 'Miami%'")
+#' 
+#' # co <- get_component_from_GDB(dsn, WHERE = "compname = 'Miami'
+#' #                              AND majcompflag = 'Yes'", childs = FALSE)
+#' 
+#' # f_in_GDB <- fetchGDB(WHERE = "areasymbol LIKE 'IN%'")
+#' 
+#' }
+#' 
+#' @export fetchGDB
 fetchGDB <- function(dsn = "gNATSGO_CONUS.gdb",
                      WHERE = NULL,
                      childs = TRUE,
diff --git a/R/get_component_from_LIMS.R b/R/get_component_from_LIMS.R
index 7caa1bdc..e69de29b 100644
--- a/R/get_component_from_LIMS.R
+++ b/R/get_component_from_LIMS.R
@@ -1,320 +0,0 @@
-get_component_from_NASISWebReport <- function(projectname, stringsAsFactors = default.stringsAsFactors()) {
-  
-  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_component_from_NASISWebReport"
-  
-  d.component <- lapply(projectname, function(x) {
-    message("getting project '", x, "' from NasisReportsWebSite \n", sep = "")
-    args = list(p_projectname = x)
-    d    =  tryCatch(parseWebReport(url, args), 
-                     error = function(e) {
-                                message(e)
-                                return(NULL)
-                              })
-  })
-  
-  d.component <- do.call("rbind", d.component)
-  
-  if(is.null(d.component))
-    return(NULL)  
-  
-  # set factor levels according to metadata domains
-  d.component <- uncode(d.component, db = "LIMS", stringsAsFactors = stringsAsFactors)
-  
-  # prep
-  d.component <- .cogmd_prep(d.component, db = "LIMS")
-  
-  
-  # return data.frame
-  return(d.component)
-  
-}
-
-
-get_chorizon_from_NASISWebReport <- function(projectname, fill = FALSE, stringsAsFactors = default.stringsAsFactors()) {
-  
-  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_chorizon_from_NASISWebReport"
-  
-  d.chorizon <- lapply(projectname, function(x) {
-    args = list(p_projectname = x)
-    d    =  parseWebReport(url, args)
-  })
-  d.chorizon <- do.call("rbind", d.chorizon)
-  
-  ## TODO: might be nice to abstract this into a new function
-  # hacks to make R CMD check --as-cran happy:
-  metadata <- NULL
-  # load local copy of metadata
-  load(system.file("data/metadata.rda", package="soilDB")[1])
-  
-  # transform variables and metadata
-  if (!all(is.na(d.chorizon$chiid))) {
-    d.chorizon <- within(d.chorizon, {
-      texture = tolower(texture)
-      if (stringsAsFactors == TRUE) {
-        texcl = factor(texcl,
-                       levels = metadata[metadata$ColumnPhysicalName == "texcl", "ChoiceValue"],
-                       labels = metadata[metadata$ColumnPhysicalName == "texcl", "ChoiceName"]
-        )
-      }
-    })
-  }
-  
-  # fill
-  if (fill == FALSE) {
-    d.chorizon <- d.chorizon[!is.na(d.chorizon$chiid), ]
-  }
-  
-  # return data.frame
-  return(d.chorizon)
-  
-}
-
-
-
-get_legend_from_NASISWebReport <- function(mlraoffice, areasymbol, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  
-  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_legend_from_NASISWebReport"
-  
-  args <- list(p_mlraoffice = mlraoffice, p_areasymbol = areasymbol)
-  
-  d.legend <- parseWebReport(url, args)
-  
-  
-  # set factor levels according to metadata domains
-  # data is coming back uncoded from LIMS so db is set to "SDA"
-  d.legend <- uncode(d.legend, 
-                     db = "SDA",
-                     droplevels = droplevels,
-                     stringsAsFactors = stringsAsFactors
-  )
-  
-  # date
-  d.legend$cordate <- as.Date(d.legend$cordate)
-  
-  # return data.frame
-  return(d.legend)
-  
-}
-
-
-
-get_lmuaoverlap_from_NASISWebReport <- function(areasymbol, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_lmuaoverlap_from_NASISWebReport"
-  
-  d <- lapply(areasymbol, function(x) {
-    message("getting legend for '", x, "' from NasisReportsWebSite \n", sep = "")
-    args = list(p_areasymbol = x)
-    d    =  parseWebReport(url, args)
-  })
-  d <- do.call("rbind", d)
-  
-  
-  # set factor levels according to metadata domains
-  # data is coming back uncoded from LIMS so db is set to "SDA"
-  d <- uncode(d, 
-              db = "SDA",
-              droplevels = droplevels,
-              stringsAsFactors = stringsAsFactors
-  )
-  
-  # return data.frame
-  return(d)
-  
-}
-
-
-
-get_mapunit_from_NASISWebReport <- function(areasymbol, droplevels = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_mapunit_from_NASISWebReport"
-  
-  d.mapunit <- lapply(areasymbol, function(x) {
-    message("getting map units for '", x, "' from NasisReportsWebSite \n", sep = "")
-    args = list(p_areasymbol = x)
-    d    =  parseWebReport(url, args)
-  })
-  d.mapunit <- do.call("rbind", d.mapunit)
-  
-  d.mapunit$musym = as.character(d.mapunit$musym)
-  
-  # set factor levels according to metadata domains
-  # data is coming back uncoded from LIMS so db is set to "SDA"
-  d.mapunit <- uncode(d.mapunit, 
-                      db = "SDA",
-                      droplevels = droplevels,
-                      stringsAsFactors = stringsAsFactors
-  )
-  
-  # return data.frame
-  return(d.mapunit)
-  
-}
-
-
-get_projectmapunit_from_NASISWebReport <- function(projectname, stringsAsFactors = default.stringsAsFactors()) {
-  
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_projectmapunit_from_NASISWebReport"
-  
-  
-  d.mapunit <- lapply(projectname, function(x) {
-    args = list(p_projectname = x)
-    d    =  parseWebReport(url, args)
-  })
-  d.mapunit <- do.call("rbind", d.mapunit)
-  
-  d.mapunit$musym = as.character(d.mapunit$musym)
-  
-  # set factor levels according to metadata domains
-  d.mapunit <- uncode(d.mapunit, db = "LIMS", stringsAsFactors = stringsAsFactors)
-  
-  # return data.frame
-  return(d.mapunit)
-  
-}
-
-
-get_projectmapunit2_from_NASISWebReport <- function(mlrassoarea, fiscalyear, projectname, stringsAsFactors = default.stringsAsFactors()) {
-  
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_projectmapunit2_from_NASISWebReport"
-  
-  args = list(p_mlrassoarea = mlrassoarea, p_fy = fiscalyear, p_projectname = projectname)
-  d.mapunit    =  parseWebReport(url, args)
-  
-  d.mapunit$musym = as.character(d.mapunit$musym)
-  
-  # set factor levels according to metadata domains
-  # data is coming back uncoded from LIMS so db is set to "SDA"
-  d.mapunit <- uncode(d.mapunit, db = "SDA", stringsAsFactors = stringsAsFactors)
-  
-  # return data.frame
-  return(d.mapunit)
-  
-}
-
-
-get_project_from_NASISWebReport <- function(mlrassoarea, fiscalyear) {
-  
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_project_from_NASISWebReport"
-  
-  args <- list(p_mlrassoarea = mlrassoarea, p_fy = fiscalyear)
-  
-  d.project <- parseWebReport(url, args)
-  
-  # prep
-  idx <- unlist(lapply(names(d.project), function(x) grepl("date_", x)))
-  if (any(idx)) {
-    d.project[idx] <- lapply(d.project[idx], function(x) as.Date(x, format = "%Y/%m/%d"))
-  }
-  
-  # return data.frame
-  return(d.project)
-  
-}
-
-
-get_progress_from_NASISWebReport <- function(mlrassoarea, fiscalyear, projecttypename) {
-  
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_progress_from_NASISWebReport"
-  
-  args <- list(p_mlrassoarea = mlrassoarea, p_fy = fiscalyear, p_projecttypename = projecttypename)
-  
-  d.progress <- parseWebReport(url, args)
-  
-  # return data.frame
-  return(d.progress)
-  
-}
-
-
-get_project_correlation_from_NASISWebReport <- function(mlrassoarea, fiscalyear, projectname) {
-  
-  # nasty hack to trick R CMD check
-  musym <- NULL
-  new_musym <- NULL
-  
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_project_correlation_from_NASISWebReport"
-  
-  args <- list(p_mlrassoarea = mlrassoarea, p_fy = fiscalyear, p_projectname = projectname)
-  
-  d.rcor <- parseWebReport(url, args)
-  
-  # compute musym_orig for additional lmapunits, necessary to catch changes to the original musym, due to a constraint on the lmapunit table that prevents duplicate musym for additional mapunits
-  if (! is.null(d.rcor)) {
-    
-    d.rcor <- within(d.rcor, {
-      n         = nchar(musym)
-      begin_1   = substr(musym, 2, n)
-      end_1     = substr(musym, 1, n - 1)
-      end_4     = substr(musym, 1, n - 4)
-    
-      idx       = musym != new_musym & !is.na(new_musym)
-      orig_musym = ifelse(idx & musym != begin_1 & (new_musym == begin_1 | substr(musym, 1, 1) %in% c("x", "z")), begin_1, musym)
-      # Joe recommended using |\\+${1}, but appears to be legit in some cases
-      orig_musym = ifelse(idx & musym != end_1   & new_musym == end_1 , end_1   , orig_musym)
-      orig_musym = ifelse(idx & musym != end_4   & new_musym == end_4 , end_4   , orig_musym)
-      })
-  }
-    
-  d.rcor[c("n", "begin_1", "end_1", "end_4", "idx")] <- NULL
-  
-  # return data.frame
-  return(d.rcor)
-  
-}
-
-
-fetchNASISWebReport <- function(projectname, rmHzErrors = FALSE, fill = FALSE,
-                                stringsAsFactors = default.stringsAsFactors()
-) {
-  
-  # load data in pieces
-  f.mapunit   <- get_projectmapunit_from_NASISWebReport(projectname, stringsAsFactors = stringsAsFactors)
-  f.component <- get_component_from_NASISWebReport(projectname, stringsAsFactors = stringsAsFactors)
-  f.chorizon  <- get_chorizon_from_NASISWebReport(projectname, fill, stringsAsFactors = stringsAsFactors)
-  
-  # return NULL if one of the required pieces is missing
-  if(is.null(f.mapunit) | is.null(f.component) | is.null(f.chorizon)) {
-    message("One or more inputs for fetchNASISWebReport (mapunit, component, or horizon) is NULL, returning NULL.")
-    return(NULL)
-  }
-     
-  
-  # optionally test for bad horizonation... flag, and remove
-  if (rmHzErrors) {
-    f.chorizon.test <- plyr::ddply(f.chorizon, 'coiid', function(d) {
-      res <- aqp::hzDepthTests(top=d[['hzdept_r']], bottom=d[['hzdepb_r']])
-      return(data.frame(hz_logic_pass=all(!res)))
-    })
-    
-    # which are the good (valid) ones?
-    good.ids <- as.character(f.chorizon.test$coiid[which(f.chorizon.test$hz_logic_pass)])
-    bad.ids  <- as.character(f.chorizon.test$coiid[which(! f.chorizon.test$hz_logic_pass)])
-    
-    # keep the good ones
-    f.chorizon <- f.chorizon[which(f.chorizon$coiid %in% good.ids), ]
-    
-    # keep track of those components with horizonation errors
-    if(length(bad.ids) > 0)
-      assign('component.hz.problems', value=bad.ids, envir=soilDB.env)
-  }
-  
-  # upgrade to SoilProfilecollection
-  depths(f.chorizon) <- coiid ~ hzdept_r + hzdepb_r
-  
-  
-  ## TODO: this will fail in the presence of duplicates
-  ## TODO: make this error more informative
-  # add site data to object
-  site(f.chorizon) <- f.component # left-join via coiid
-  
-  # set NASIS-specific horizon identifier
-  hzidname(f.chorizon) <- 'chiid'
-  
-  # print any messages on possible data quality problems:
-  if (exists('component.hz.problems', envir=soilDB.env))
-    message("-> QC: horizon errors detected, use `get('component.hz.problems', envir=soilDB.env)` for related cokey values")
-  
-  # done, return SPC
-  return(list(spc = f.chorizon, mapunit = f.mapunit))
-  
-}
-
diff --git a/R/get_component_from_SDA.R b/R/get_component_from_SDA.R
index f86b1538..8ec1dc20 100644
--- a/R/get_component_from_SDA.R
+++ b/R/get_component_from_SDA.R
@@ -705,6 +705,174 @@ get_chorizon_from_SDA <- function(WHERE = NULL, duplicates = FALSE,
 }
 
 
+
+
+#' Download and Flatten Data from Soil Data Access
+#'
+#' Functions to download and flatten commonly used tables and from Soil Data
+#' Access, and create soil profile collection objects (SPC).
+#'
+#' These functions return data from Soil Data Access with the use of a simple
+#' text string that formatted as an SQL WHERE clause (e.g. \code{WHERE =
+#' "areasymbol = 'IN001'"}. All functions are SQL queries that wrap around
+#' \code{SDAquery()} and format the data for analysis.
+#'
+#' Beware SDA includes the data for both SSURGO and STATSGO2. The
+#' \code{areasymbol} for STATSGO2 is \code{US}. For just SSURGO, include
+#' \code{WHERE = "areareasymbol != 'US'"}.
+#'
+#' If the duplicates argument is set to TRUE, duplicate components are
+#' returned. This is not necessary with data returned from NASIS, which has one
+#' unique national map unit. SDA has duplicate map national map units, one for
+#' each legend it exists in.
+#'
+#' The value of \code{nullFragsAreZero} will have a significant impact on the
+#' rock fragment fractions returned by \code{fetchSDA}. Set
+#' \code{nullFragsAreZero = FALSE} in those cases where there are many
+#' data-gaps and NULL rock fragment values should be interpreted as NULLs. Set
+#' \code{nullFragsAreZero = TRUE} in those cases where NULL rock fragment
+#' values should be interpreted as 0.
+#'
+#' @aliases fetchSDA get_legend_from_SDA get_lmuaoverlap_from_SDA
+#' get_mapunit_from_SDA get_component_from_SDA get_chorizon_from_SDA
+#' get_cosoilmoist_from_SDA get_cointerp_from_SDA
+#' @param WHERE text string formatted as an SQL WHERE clause (default: FALSE)
+#' @param duplicates logical; if TRUE a record is returned for each unique
+#' mukey (may be many per nationalmusym)
+#' @param childs logical; if FALSE parent material and geomorphic child tables
+#' are not flattened and appended
+#' @param nullFragsAreZero should fragment volumes of NULL be interpreted as 0?
+#' (default: TRUE), see details
+#' @param rmHzErrors should pedons with horizonation errors be removed from the
+#' results? (default: FALSE)
+#' @param droplevels logical: indicating whether to drop unused levels in
+#' classifying factors. This is useful when a class has large number of unused
+#' classes, which can waste space in tables and figures.
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the uncode() function. It does not
+#' convert those vectors that have set outside of uncode() (i.e. hard coded).
+#' The 'factory-fresh' default is TRUE, but this can be changed by setting
+#' options(stringsAsFactors = FALSE)
+#' @return A data.frame or SoilProfileCollection object.
+#' @author Stephen Roecker
+#' @seealso \link{SDA_query}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#'
+#'
+#' if (requireNamespace("curl") &
+#'   curl::has_internet() &
+#'   require(aqp) &
+#'   require("ggplot2") &
+#'   require("gridExtra") &
+#'   require("viridisLite")
+#' ) {
+#'
+#'   # query soil components by areasymbol and musym
+#'   test = fetchSDA(WHERE = "areasymbol = 'IN005' AND musym = 'MnpB2'")
+#'
+#'
+#'   # profile plot
+#'   plot(test)
+#'
+#'
+#'   # convert the data for depth plot
+#'   clay_slice = horizons(slice(test, 0:200 ~ claytotal_l + claytotal_r + claytotal_h))
+#'   names(clay_slice) <- gsub("claytotal_", "", names(clay_slice))
+#'
+#'   om_slice = horizons(slice(test, 0:200 ~ om_l + om_r + om_h))
+#'   names(om_slice) = gsub("om_", "", names(om_slice))
+#'
+#'   test2 = rbind(data.frame(clay_slice, var = "clay"),
+#'                 data.frame(om_slice, var = "om")
+#'   )
+#'
+#'   h = merge(test2, site(test)[c("nationalmusym", "cokey", "compname", "comppct_r")],
+#'             by = "cokey",
+#'             all.x = TRUE
+#'   )
+#'
+#'   # depth plot of clay content by soil component
+#'   gg_comp <- function(x) {
+#'     ggplot(x) +
+#'       geom_line(aes(y = r, x = hzdept_r)) +
+#'       geom_line(aes(y = r, x = hzdept_r)) +
+#'       geom_ribbon(aes(ymin = l, ymax = h, x = hzdept_r), alpha = 0.2) +
+#'       xlim(200, 0) +
+#'       xlab("depth (cm)") +
+#'       facet_grid(var ~ nationalmusym + paste(compname, comppct_r)) +
+#'       coord_flip()
+#'   }
+#'   g1 <- gg_comp(subset(h, var == "clay"))
+#'   g2 <- gg_comp(subset(h, var == "om"))
+#'
+#'   grid.arrange(g1, g2)
+#'
+#'
+#'   # query cosoilmoist (e.g. water table data) by mukey
+#'   x <- get_cosoilmoist_from_SDA(WHERE = "mukey = '1395352'")
+#'
+#'   ggplot(x, aes(x = as.integer(month), y = dept_r, lty = status)) +
+#'     geom_rect(aes(xmin = as.integer(month), xmax = as.integer(month) + 1,
+#'                   ymin = 0, ymax = max(x$depb_r),
+#'                   fill = flodfreqcl)) +
+#'     geom_line(cex = 1) +
+#'     geom_point() +
+#'     geom_ribbon(aes(ymin = dept_l, ymax = dept_h), alpha = 0.2) +
+#'     ylim(max(x$depb_r), 0) +
+#'     xlab("month") + ylab("depth (cm)") +
+#'     scale_x_continuous(breaks = 1:12, labels = month.abb, name="Month") +
+#'     facet_wrap(~ paste0(compname, ' (', comppct_r , ')')) +
+#'     ggtitle(paste0(x$nationalmusym[1],
+#'                    ': Water Table Levels from Component Soil Moisture Month Data'))
+#'
+#'
+#'
+#'   # query all Miami major components
+#'   s <- get_component_from_SDA(WHERE = "compname = 'Miami' \n
+#'                 AND majcompflag = 'Yes' AND areasymbol != 'US'")
+#'
+#'
+#'   # landform vs 3-D morphometry
+#'   test <- {
+#'     subset(s, ! is.na(landform) | ! is.na(geompos)) ->.;
+#'     split(., .$drainagecl, drop = TRUE) ->.;
+#'     lapply(., function(x) {
+#'       test = data.frame()
+#'       test = as.data.frame(table(x$landform, x$geompos))
+#'       test$compname   = x$compname[1]
+#'       test$drainagecl = x$drainagecl[1]
+#'       names(test)[1:2] <- c("landform", "geompos")
+#'       return(test)
+#'     }) ->.;
+#'     do.call("rbind", .) ->.;
+#'     .[.$Freq > 0, ] ->.;
+#'     within(., {
+#'       landform = reorder(factor(landform), Freq, max)
+#'       geompos  = reorder(factor(geompos),  Freq, max)
+#'       geompos  = factor(geompos, levels = rev(levels(geompos)))
+#'     }) ->.;
+#'   }
+#'   test$Freq2 <- cut(test$Freq,
+#'                     breaks = c(0, 5, 10, 25, 50, 100, 150),
+#'                     labels = c("<5", "5-10", "10-25", "25-50", "50-100", "100-150")
+#'   )
+#'   ggplot(test, aes(x = geompos, y = landform, fill = Freq2)) +
+#'     geom_tile(alpha = 0.5) + facet_wrap(~ paste0(compname, "\n", drainagecl)) +
+#'     discrete_scale("colour", "viridis", function(n) viridisLite::viridis(n)) +
+#'     theme(aspect.ratio = 1, axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) +
+#'     ggtitle("Landform vs 3-D Morphometry for Miami Major Components on SDA")
+#'
+#'
+#' }
+#'
+#'
+#'
+#' }
+#'
+#' @export fetchSDA
 fetchSDA <- function(WHERE = NULL, duplicates = FALSE, childs = TRUE,
                      nullFragsAreZero = TRUE,
                      rmHzErrors = FALSE,
@@ -772,7 +940,7 @@ fetchSDA <- function(WHERE = NULL, duplicates = FALSE, childs = TRUE,
   if(is.data.frame(f.diag)) {
     diagnostic_hz(f.chorizon) <- f.diag
   }
-  
+
   # add restrictions
   if(is.data.frame(f.restr)) {
     restrictions(f.chorizon) <- f.restr
diff --git a/R/get_concentrations_from_NASIS_db.R b/R/get_concentrations_from_NASIS_db.R
index c8995ba1..2587559f 100644
--- a/R/get_concentrations_from_NASIS_db.R
+++ b/R/get_concentrations_from_NASIS_db.R
@@ -1,7 +1,4 @@
-get_concentrations_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_concentrations_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # concentrations
   # unique-ness enforced via peiid (pedon-level) and phiid (horizon-level)
@@ -19,26 +16,24 @@ get_concentrations_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default
   FROM phconccolor_View_1
   ORDER BY phconceniidref, colormoistst;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
     q.c <- gsub(pattern = '_View_1', replacement = '', x = q.c, fixed = TRUE)
   }
 
   # exec queries
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-  d.c <- RODBC::sqlQuery(channel, q.c, stringsAsFactors=FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d <- dbQueryNASIS(channel, q, close = FALSE)
+  d.c <- dbQueryNASIS(channel, q.c)
 
   # uncode domained columns
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
-  d.c <- uncode(d.c, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
+  d.c <- uncode(d.c, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # convert back to characters / numeric
   d.c$colormoistst <- as.character(d.c$colormoistst)
@@ -50,5 +45,5 @@ get_concentrations_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default
   d.c$colorchroma <- as.numeric(as.character(d.c$colorchroma))
 
   # done
-  return(list(conc=d, conc_colors=d.c))
+  return(list(conc = d, conc_colors = d.c))
 }
diff --git a/R/get_cosoilmoist_from_NASIS.R b/R/get_cosoilmoist_from_NASIS.R
index 1185ce54..02c1c6ad 100644
--- a/R/get_cosoilmoist_from_NASIS.R
+++ b/R/get_cosoilmoist_from_NASIS.R
@@ -1,6 +1,50 @@
-get_cosoilmoist_from_NASIS <- function(impute = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if (!requireNamespace('RODBC')) stop('please install the `RODBC` package', call.=FALSE)
+#' Read and Flatten the Component Soil Moisture Tables
+#'
+#' Read and flatten the component soil moisture month tables from a local NASIS
+#' Database.
+#'
+#' The component soil moisture tables within NASIS house monthly data on
+#' flooding, ponding, and soil moisture status. The soil moisture status is
+#' used to specify the water table depth for components (e.g. \code{status ==
+#' "Moist"}).
+#'
+#' @param SS fetch data from the currently loaded selected set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#' @param impute replace missing (i.e. `NULL`) values with `"Not_Populated"` for
+#' categorical data, or the "RV" for numeric data or `201` cm if the "RV" is also
+#' `NULL` (default: `TRUE`)
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have set outside of `uncode()` (i.e. hard coded).
+#' The 'factory-fresh' default is TRUE, but this can be changed by setting
+#' options(`stringsAsFactors = FALSE`)
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#' @return A data.frame.
+#' @note This function currently works only on Windows.
+#' @author S.M. Roecker
+#' @seealso \link{fetchNASIS}, \link{get_cosoilmoist_from_NASISWebReport},
+#' \link{get_cosoilmoist_from_SDA}, \code{get_comonth_from_SDA}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#' if(local_NASIS_defined()) {
+#'  # load cosoilmoist (e.g. water table data)
+#'  test <- try(get_cosoilmoist_from_NASIS())
+#'
+#'  # inspect
+#'  if(!inherits(test, 'try-error')) {
+#'    head(test)
+#'  }
+#' }
+#' }
+#' @export get_cosoilmoist_from_NASIS
+get_cosoilmoist_from_NASIS <- function(SS = TRUE,
+                                       impute = TRUE,
+                                       stringsAsFactors = default.stringsAsFactors(),
+                                       dsn = NULL) {
+
 
   q.cosoilmoist <- "SELECT dmuiidref AS dmuiid, coiid, compname, comppct_r, drainagecl, month, flodfreqcl, floddurcl, pondfreqcl, ponddurcl, cosoilmoistiid, soimoistdept_l, soimoistdept_r, soimoistdept_h, soimoistdepb_l, soimoistdepb_r, soimoistdepb_h, soimoiststat
 
@@ -11,18 +55,21 @@ get_cosoilmoist_from_NASIS <- function(impute = TRUE, stringsAsFactors = default
   ORDER BY dmuiid, comppct_r DESC, compname, month, soimoistdept_r
   ;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
-  # exec query
-  d.cosoilmoist <- RODBC::sqlQuery(channel, q.cosoilmoist, stringsAsFactors = FALSE)
+  # toggle selected set vs. local DB
+  if (SS == FALSE) {
+    q.cosoilmoist <- gsub(pattern = '_View_1', replacement = '', x = q.cosoilmoist, fixed = TRUE)
+  }
 
-  # close connection
-  RODBC::odbcClose(channel)
+  # exec query
+  d.cosoilmoist <- dbQueryNASIS(channel, q.cosoilmoist)
 
   # recode metadata domains
-  d.cosoilmoist <- uncode(d.cosoilmoist, stringsAsFactors = stringsAsFactors)
+  d.cosoilmoist <- uncode(d.cosoilmoist, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # prep dataset: rename columns, impute empty values, stringsAsFactors
   d.cosoilmoist <- .cosoilmoist_prep(d.cosoilmoist, impute = impute, stringsAsFactors = stringsAsFactors)
diff --git a/R/get_extended_data_from_NASIS_db.R b/R/get_extended_data_from_NASIS_db.R
index 3940221f..38526934 100644
--- a/R/get_extended_data_from_NASIS_db.R
+++ b/R/get_extended_data_from_NASIS_db.R
@@ -4,10 +4,49 @@
 
 ## TODO_JS: incorporated the use of uncode() into all except the fragment queries, which I think are best left as they are.
 
-get_extended_data_from_NASIS_db <- function(SS=TRUE, nullFragsAreZero=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+
+
+#' Extract accessory tables and summaries from a local NASIS Database
+#'
+#' @param SS get data from the currently loaded Selected Set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#'
+#' @param nullFragsAreZero should fragment volumes of NULL be interpreted as 0?
+#' (default: TRUE), see details
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have been set outside of `uncode()` (i.e. hard
+#' coded).
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A list with the results.
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @seealso \code{\link{get_hz_data_from_NASIS_db}},
+#' \code{\link{get_site_data_from_NASIS_db}}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#'
+#' if(local_NASIS_defined()) {
+#'  # query extended data
+#'  e <- try(get_extended_data_from_NASIS_db())
+#'
+#'  # show contents of extended data
+#'  str(e)
+#' }
+#'
+#' }
+#'
+#' @export get_extended_data_from_NASIS_db
+get_extended_data_from_NASIS_db <- function(SS = TRUE,
+                                            nullFragsAreZero = TRUE,
+                                            stringsAsFactors = default.stringsAsFactors(),
+                                            dsn = NULL) {
+
 
   # photo links from PedonPC stored as sitetext notes
   q.photolink <- "SELECT so.siteiidref AS siteiid, sot.recdate, sot.textcat,  CAST(sot.textentry AS ntext) AS imagepath
@@ -18,7 +57,7 @@ get_extended_data_from_NASIS_db <- function(SS=TRUE, nullFragsAreZero=TRUE, stri
   ORDER BY sot.siteobstextkind;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.photolink <- gsub(pattern = '_View_1', replacement = '', x = q.photolink, fixed = TRUE)
   }
 
@@ -30,7 +69,7 @@ get_extended_data_from_NASIS_db <- function(SS=TRUE, nullFragsAreZero=TRUE, stri
 	ORDER BY phiidref, structid ASC;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.structure <- gsub(pattern = '_View_1', replacement = '', x = q.structure, fixed = TRUE)
   }
 
@@ -43,7 +82,7 @@ get_extended_data_from_NASIS_db <- function(SS=TRUE, nullFragsAreZero=TRUE, stri
   ORDER BY 'siteiid';"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.ecosite <- gsub(pattern = '_View_1', replacement = '', x = q.ecosite, fixed = TRUE)
   }
 
@@ -57,7 +96,7 @@ get_extended_data_from_NASIS_db <- function(SS=TRUE, nullFragsAreZero=TRUE, stri
 	ORDER BY pdf.peiidref, pdf.featdept;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.diagnostic <- gsub(pattern = '_View_1', replacement = '', x = q.diagnostic, fixed = TRUE)
   }
 
@@ -66,7 +105,7 @@ FROM perestrictions_View_1 As prf
 	ORDER BY prf.peiidref, prf.resdept;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.restriction <- gsub(pattern = '_View_1', replacement = '', x = q.restriction, fixed = TRUE)
   }
 
@@ -177,7 +216,7 @@ LEFT OUTER JOIN (
 
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.surf.rf.summary <- gsub(pattern = '_View_1', replacement = '', x = q.surf.rf.summary, fixed = TRUE)
   }
 
@@ -190,7 +229,7 @@ LEFT OUTER JOIN (
   LEFT OUTER JOIN phfrags_View_1 ON p.phiid = phfrags_View_1.phiidref;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.rf.data <- gsub(pattern = '_View_1', replacement = '', x = q.rf.data, fixed = TRUE)
   }
 
@@ -201,6 +240,9 @@ LEFT OUTER JOIN (
                        SELECT DISTINCT phiid FROM phorizon_View_1
                        ) as p LEFT OUTER JOIN phhuarts ", ifelse(SS, "_View_1","") ,
                        " ON p.phiid = phiidref;")
+  if (SS == FALSE) {
+    q.art.data <- gsub(pattern = '_View_1', replacement = '', x = q.art.data, fixed = TRUE)
+  }
 
   # new phfrags summary SQL
   q.rf.data.v2 <- "
@@ -303,7 +345,7 @@ LEFT OUTER JOIN (
   DROP TABLE #RF2;
   "
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.rf.data.v2 <- gsub(pattern = '_View_1', replacement = '', x = q.rf.data.v2, fixed = TRUE)
   }
 
@@ -316,28 +358,26 @@ LEFT OUTER JOIN (
 	LEFT OUTER JOIN phtexturemod_View_1 AS phtm ON pht.phtiid = phtm.phtiidref;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.hz.texmod <- gsub(pattern = '_View_1', replacement = '', x = q.hz.texmod, fixed = TRUE)
   }
 
-
-  ## TODO: joins without a join condition!
-  # https://github.com/ncss-tech/soilDB/issues/48
-  # get geomorphic features
-  q.geomorph <- "SELECT pedon_View_1.peiid, sitegeomordesc_View_1.geomfmod, geomorfeat.geomfname, sitegeomordesc_View_1.geomfeatid, sitegeomordesc_View_1.existsonfeat, sitegeomordesc_View_1.geomfiidref, lower(geomorfeattype.geomftname) as geomftname
-
-  FROM geomorfeattype
-  RIGHT JOIN geomorfeat
-  RIGHT JOIN site_View_1 INNER JOIN sitegeomordesc_View_1 ON site_View_1.siteiid = sitegeomordesc_View_1.siteiidref
-  INNER JOIN siteobs_View_1 INNER JOIN pedon_View_1 ON siteobs_View_1.siteobsiid = pedon_View_1.siteobsiidref
-  ON site_View_1.siteiid = siteobs_View_1.siteiidref
-  ON geomorfeat.geomfiid = sitegeomordesc_View_1.geomfiidref
-  ON geomorfeattype.geomftiid = geomorfeat.geomftiidref
-  ORDER BY peiid, geomfeatid ASC;"
+  # get geomorphic features (sqlite safe -- no RIGHT JOIN 2020/12/02)
+  q.geomorph <- "SELECT pedon_View_1.peiid, sitegeomordesc_View_1.geomfmod,
+                         geomorfeat.geomfname, sitegeomordesc_View_1.geomfeatid,
+                         sitegeomordesc_View_1.existsonfeat, sitegeomordesc_View_1.geomfiidref,
+                         lower(geomorfeattype.geomftname) as geomftname
+                  FROM pedon_View_1
+                    INNER JOIN siteobs_View_1 ON siteobs_View_1.siteobsiid = pedon_View_1.siteobsiidref
+                    INNER JOIN site_View_1 ON site_View_1.siteiid = siteobs_View_1.siteiidref
+                    INNER JOIN sitegeomordesc_View_1 ON site_View_1.siteiid = sitegeomordesc_View_1.siteiidref
+                    INNER JOIN geomorfeat ON  geomorfeat.geomfiid = sitegeomordesc_View_1.geomfiidref
+                    INNER JOIN geomorfeattype ON geomorfeattype.geomftiid = geomorfeat.geomftiidref
+                  ORDER BY peiid, geomfeatid ASC;"
 
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.geomorph <- gsub(pattern = '_View_1', replacement = '', x = q.geomorph, fixed = TRUE)
   }
 
@@ -350,7 +390,7 @@ LEFT OUTER JOIN (
     ORDER BY pth.peiidref;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.taxhistory <- gsub(pattern = '_View_1', replacement = '', x = q.taxhistory, fixed = TRUE)
   }
 
@@ -361,7 +401,7 @@ LEFT OUTER JOIN (
   INNER JOIN site_View_1 AS s ON spm.siteiidref = s.siteiid;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.sitepm <- gsub(pattern = '_View_1', replacement = '', x = q.sitepm, fixed = TRUE)
   }
 
@@ -370,7 +410,7 @@ LEFT OUTER JOIN (
   ORDER BY phiid;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.hz.desgn <- gsub(pattern = '_View_1', replacement = '', x = q.hz.desgn, fixed = TRUE)
   }
 
@@ -379,82 +419,91 @@ LEFT OUTER JOIN (
   ORDER BY phiidref, seqnum ASC;"
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q.hz.dessuf <- gsub(pattern = '_View_1', replacement = '', x = q.hz.dessuf, fixed = TRUE)
   }
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
 	# exec queries
-	d.ecosite <- RODBC::sqlQuery(channel, q.ecosite, stringsAsFactors=FALSE)
-	d.diagnostic <- RODBC::sqlQuery(channel, q.diagnostic, stringsAsFactors=FALSE)
-	d.restriction <- RODBC::sqlQuery(channel, q.restriction, stringsAsFactors=FALSE)
-
-	d.rf.data <- RODBC::sqlQuery(channel, q.rf.data, stringsAsFactors=FALSE)
-	d.rf.data.v2 <- RODBC::sqlQuery(channel, q.rf.data.v2, stringsAsFactors=FALSE)
-	d.art.data <- uncode(RODBC::sqlQuery(channel, q.art.data, stringsAsFactors=FALSE))
-
-	d.surf.rf.summary <- RODBC::sqlQuery(channel, q.surf.rf.summary, stringsAsFactors=FALSE)
-	d.hz.texmod <- RODBC::sqlQuery(channel, q.hz.texmod, stringsAsFactors=FALSE)
-	d.geomorph <- RODBC::sqlQuery(channel, q.geomorph, stringsAsFactors=FALSE)
-	d.taxhistory <- RODBC::sqlQuery(channel, q.taxhistory, stringsAsFactors=FALSE)
-	d.photolink <- RODBC::sqlQuery(channel, q.photolink, stringsAsFactors=FALSE)
-	d.sitepm <- RODBC::sqlQuery(channel, q.sitepm, stringsAsFactors=FALSE)
-	d.structure <- RODBC::sqlQuery(channel, q.structure, stringsAsFactors=FALSE)
-	d.hz.desgn <- RODBC::sqlQuery(channel, q.hz.desgn, stringsAsFactors=FALSE)
-  d.hz.dessuf <- RODBC::sqlQuery(channel, q.hz.dessuf, stringsAsFactors=FALSE)
+  d.ecosite <- dbQueryNASIS(channel, q.ecosite, close = FALSE)
+  d.diagnostic <- dbQueryNASIS(channel, q.diagnostic, close = FALSE)
+  d.restriction <- dbQueryNASIS(channel, q.restriction, close = FALSE)
+
+  d.rf.data <- dbQueryNASIS(channel, q.rf.data, close = FALSE)
+  # d.rf.data.v2 <- dbQueryNASIS(channel, q.rf.data.v2, close = FALSE)
+  d.art.data <- dbQueryNASIS(channel, q.art.data, close = FALSE)
+
+  d.surf.rf.summary <- dbQueryNASIS(channel, q.surf.rf.summary, close = FALSE)
+  d.hz.texmod <- dbQueryNASIS(channel, q.hz.texmod, close = FALSE)
+  d.geomorph <- dbQueryNASIS(channel, q.geomorph, close = FALSE)
+  d.taxhistory <- dbQueryNASIS(channel, q.taxhistory, close = FALSE)
+  d.photolink <- dbQueryNASIS(channel, q.photolink, close = FALSE)
+  d.sitepm <- dbQueryNASIS(channel, q.sitepm, close = FALSE)
+  d.structure <- dbQueryNASIS(channel, q.structure, close = FALSE)
+  d.hz.desgn <- dbQueryNASIS(channel, q.hz.desgn, close = FALSE)
+  d.hz.dessuf <- dbQueryNASIS(channel, q.hz.dessuf)
 
 	## uncode the ones that need that here
-	d.diagnostic <- uncode(d.diagnostic, stringsAsFactors = stringsAsFactors)
-	d.restriction <- uncode(d.restriction, stringsAsFactors = stringsAsFactors)
-	d.rf.data    <- uncode(d.rf.data, stringsAsFactors = stringsAsFactors)
-	d.hz.texmod  <- uncode(d.hz.texmod, stringsAsFactors = stringsAsFactors)
+	d.diagnostic <- uncode(d.diagnostic, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.restriction <- uncode(d.restriction, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.rf.data    <- uncode(d.rf.data, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.art.data  <-  uncode(d.art.data, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.hz.texmod  <- uncode(d.hz.texmod, stringsAsFactors = stringsAsFactors, dsn = dsn)
 	# https://github.com/ncss-tech/soilDB/issues/53
-	d.taxhistory <- uncode(d.taxhistory, stringsAsFactors = FALSE)
-	d.sitepm     <- uncode(d.sitepm, stringsAsFactors = stringsAsFactors)
-	d.structure  <- uncode(d.structure, stringsAsFactors = stringsAsFactors)
-	d.hz.desgn <- uncode(d.hz.desgn)
-	d.hz.dessuf <- uncode(d.hz.dessuf)
-
-	# close connection
-	RODBC::odbcClose(channel)
-
+	d.taxhistory <- uncode(d.taxhistory, stringsAsFactors = FALSE, dsn = dsn)
+	d.sitepm     <- uncode(d.sitepm, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.structure  <- uncode(d.structure, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.hz.desgn <- uncode(d.hz.desgn, stringsAsFactors = stringsAsFactors, dsn = dsn)
+	d.hz.dessuf <- uncode(d.hz.dessuf, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
 	## the following steps will not work when data are missing from local DB or SS
 	# return NULL in those cases
 
-	if(nrow(d.diagnostic) > 0) {
+	if (nrow(d.diagnostic) > 0) {
 	  # generate wide-formatted, diagnostic boolean summary
 	  d.diag.boolean <- .diagHzLongtoWide(d.diagnostic)
 	} else {
 	  d.diag.boolean <- NULL
 	}
 
-	if(nrow(d.hz.dessuf) > 0) {
+	if (nrow(d.hz.dessuf) > 0) {
 	  # generate wide-formatted, hz suffix boolean summary
 	  d.hzdesgnsuf.boolean <- .hzSuffixLongtoWide(d.hz.dessuf)
-	  d.hz.desgn <- join(d.hz.desgn, d.hzdesgnsuf.boolean, by = 'phiid', type = 'left')
+	  d.hz.desgn <- merge(d.hz.desgn, d.hzdesgnsuf.boolean, by = 'phiid',
+	                      sort = FALSE, all.x = TRUE, incomparables = NA)
 	} else {
 	  d.hz.desgn <- NULL
 	}
 
-	if(nrow(d.photolink) > 0) {
+	if (nrow(d.photolink) > 0) {
 	  # parse imagename and imagepath for photo links
-	  d.photolink$imagename <- basename(d.photolink$imagepath)
+	  ncharpath <- nchar(d.photolink$imagepath)
+
+	  # windows max path length is 260 (unless overridden in registry?)
+	  if (any(ncharpath > 260))
+	    warning("some image paths are too long (>260 characters) for basename()")
+
+	  bad.idx <- which(ncharpath > 260)
+	  d.photolink$imagepath[bad.idx] <- substr(d.photolink$imagepath[bad.idx],
+	                                           start = ncharpath[bad.idx] - 260,
+	                                           stop = ncharpath[bad.idx])
+	  d.photolink$imagename <- try(basename(d.photolink$imagepath))
 	}
 
 	d.rf.summary <- simplifyFragmentData(d.rf.data, id.var='phiid', nullFragsAreZero = nullFragsAreZero)
 
 	# summarize rock fragment data
-	if(nrow(d.rf.data) > 0) {
+	if (nrow(d.rf.data) > 0) {
 	  # keep track of UNIQUE original phiids so that we can optionally fill NA with 0 in a second pass
 	  all.ids <- unique(d.rf.data[, 'phiid', drop=FALSE])
 
 	  # left join
-	  d.rf.summary <- join(all.ids, d.rf.summary, by='phiid', type='left')
-
+	  d.rf.summary <- merge(all.ids, d.rf.summary, by = 'phiid',
+	                       sort = FALSE, all.x = TRUE, incomparables = NA)
 	  ## basic checks for problematic data
 
 	  # recent NSSH changes to gravel/cobble threshold 76mm -> 75mm
@@ -466,13 +515,13 @@ LEFT OUTER JOIN (
 
 	}
 
-	if(nullFragsAreZero) {
+	if (nullFragsAreZero) {
 	    # iterate over every column except for the ID
 	    nm <- names(d.rf.summary)
 	    nm <- nm[grep('phiid', nm, fixed = TRUE, invert = TRUE)]
 
 	    # a for-loop seems fine
-	    for(v in nm) {
+	    for (v in nm) {
 	      d.rf.summary[[v]] <- ifelse(is.na(d.rf.summary[[v]]), 0, d.rf.summary[[v]])
 	    }
 	}
@@ -480,51 +529,51 @@ LEFT OUTER JOIN (
 	# artifact summary
 	d.art.summary <- simplifyArtifactData(d.art.data, id.var='phiid', nullFragsAreZero = nullFragsAreZero)
 
-	if(nrow(d.art.data) > 0) {
+	if (nrow(d.art.data) > 0) {
 
 	  art.all.ids <- unique(d.art.data[, 'phiid', drop=FALSE])
-	  d.art.summary <- join(art.all.ids, d.art.summary, by='phiid', type='left')
-
+	  d.art.summary <- merge(art.all.ids, d.art.summary, by = 'phiid',
+	                        sort = FALSE, all.x = TRUE, incomparables = NA)
 	  # recent NSSH changes to gravel/cobble threshold 76mm -> 75mm
 	  qc.idx <- which(d.art.data$huartsize_h == 76)
-	  if(length(qc.idx) > 0) {
+	  if (length(qc.idx) > 0) {
 	    msg <- sprintf('-> QC: some huartsize_h values == 76mm, may be mis-classified as cobbles [%i / %i records]', length(qc.idx), nrow(d.art.data))
 	    message(msg)
 	  }
   }
 
-	if(nullFragsAreZero) {
+	if (nullFragsAreZero) {
 	  nm <- names(d.art.summary)
 	  nm <- nm[grep("phiid", nm, fixed = TRUE, invert = TRUE)]
 
 	  # a for-loop seems fine
-	  for(v in nm) {
+	  for (v in nm) {
 	    d.art.summary[[v]] <- ifelse(is.na(d.art.summary[[v]]), 0, d.art.summary[[v]])
 	  }
 	}
 
 	# r.rf.data.v2 nullFragsAreZero = TRUE
-	idx <- !names(d.rf.data.v2) %in% "phiid"
-	if (nullFragsAreZero == TRUE) {
-	  d.rf.data.v2[idx] <- lapply(d.rf.data.v2[idx], function(x) ifelse(is.na(x), 0, x))
-	}
+	# idx <- !names(d.rf.data.v2) %in% "phiid"
+	# if (nullFragsAreZero == TRUE) {
+	#   d.rf.data.v2[idx] <- lapply(d.rf.data.v2[idx], function(x) ifelse(is.na(x), 0, x))
+	# }
 
 
 	# return a list of results
-	return(list(ecositehistory=d.ecosite,
-							diagnostic=d.diagnostic,
-							diagHzBoolean=d.diag.boolean,
-							restriction=d.restriction,
-							frag_summary=d.rf.summary,
-							frag_summary_v2 = d.rf.data.v2,
-							art_summary=d.art.summary,
-							surf_frag_summary=d.surf.rf.summary,
-							texmodifier=d.hz.texmod,
-							geomorph=d.geomorph,
-							taxhistory=d.taxhistory,
-						  photo=d.photolink,
-							pm=d.sitepm,
-              struct=d.structure,
-							hzdesgn=d.hz.desgn))
+	return(list(ecositehistory = d.ecosite,
+							diagnostic = d.diagnostic,
+							diagHzBoolean = d.diag.boolean,
+							restriction = d.restriction,
+							frag_summary = d.rf.summary,
+							# frag_summary_v2 = d.rf.data.v2,
+							art_summary = d.art.summary,
+							surf_frag_summary = d.surf.rf.summary,
+							texmodifier = d.hz.texmod,
+							geomorph = d.geomorph,
+							taxhistory = d.taxhistory,
+						  photo = d.photolink,
+							pm = d.sitepm,
+              struct = d.structure,
+							hzdesgn = d.hz.desgn))
 }
 
diff --git a/R/get_extended_data_from_pedon_db.R b/R/get_extended_data_from_pedon_db.R
index 5a7abe61..cac8ec5e 100644
--- a/R/get_extended_data_from_pedon_db.R
+++ b/R/get_extended_data_from_pedon_db.R
@@ -1,6 +1,21 @@
 # TODO: does not have parity with extended data function pulling from NASIS
 # missing queries for veg, ecosite, rf.data, surf.rf.summary, photolink, sitepm, structure
 
+
+
+#' Extract accessory tables and summaries from a local pedonPC Database
+#' 
+#' Extract accessory tables and summaries from a local pedonPC Database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param dsn The path to a 'pedon.mdb' database.
+#' @return A list with the results.
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @seealso \code{\link{get_hz_data_from_pedon_db}},
+#' \code{\link{get_site_data_from_pedon_db}}
+#' @keywords manip
+#' @export get_extended_data_from_pedon_db
 get_extended_data_from_pedon_db <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_hz_data_from_NASIS_db.R b/R/get_hz_data_from_NASIS_db.R
index cb72bb54..b533b958 100644
--- a/R/get_hz_data_from_NASIS_db.R
+++ b/R/get_hz_data_from_NASIS_db.R
@@ -1,9 +1,33 @@
 ## TODO: when multiple textures have been defined, only the first one is returned (alphabetical ?)
 #
-get_hz_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+
+
+#' Extract Horizon Data from a local NASIS Database
+#'
+#' Get horizon-level data from a local NASIS database.
+#'
+#' @param SS fetch data from Selected Set in NASIS or from the entire local database (default: `TRUE`)
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have been set outside of `uncode()` (i.e. hard
+#' coded).
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A data.frame.
+#'
+#' @note `NULL` total rock fragment values are assumed to represent an _absence_ of rock fragments, and set to 0.
+#'
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#'
+#' @seealso \code{\link{get_hz_data_from_NASIS_db}}, \code{\link{get_site_data_from_NASIS_db}}
+#' @keywords manip
+#' @export get_hz_data_from_NASIS_db
+get_hz_data_from_NASIS_db <- function(SS = TRUE,
+                                      stringsAsFactors = default.stringsAsFactors(),
+                                      dsn = NULL) {
 
   q <- "SELECT peiid, phiid, upedonid as pedon_id,
   hzname, dspcomplayerid as genhz, hzdept, hzdepb,
@@ -25,21 +49,21 @@ get_hz_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
 
   ORDER BY p.upedonid, ph.hzdept ASC;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-
   # exec query
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
+  d <- dbQueryNASIS(channel, q)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # re-implement texture_class column, with lieutex in cases where texcl is missing
   d$texture_class <- ifelse(is.na(d$texcl) & ! is.na(d$lieutex), as.character(d$lieutex), as.character(d$texcl))
@@ -53,12 +77,10 @@ get_hz_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   dupe.hz.pedon.ids <- d$pedon_id[d$phiid %in% dupe.hz.phiid]
 
   if (length(dupe.hz) > 0) {
-    message(paste('NOTICE: multiple `labsampnum` values / horizons; see pedon IDs:\n', paste(unique(dupe.hz.pedon.ids), collapse=','), sep=''))
+    message(paste0('NOTICE: multiple `labsampnum` values / horizons; see pedon IDs:\n',
+                   paste(unique(dupe.hz.pedon.ids), collapse = ',')))
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
   # done
   return(d)
 }
diff --git a/R/get_hz_data_from_pedon_db.R b/R/get_hz_data_from_pedon_db.R
index 221ab651..683eb59b 100644
--- a/R/get_hz_data_from_pedon_db.R
+++ b/R/get_hz_data_from_pedon_db.R
@@ -6,6 +6,23 @@
 #    - multiple textures defined for a single horizon-- currently texture is not returned, see NASIS version for 50% fix
 #    - multiple lab sample numbers in phsample
 
+
+
+#' Extract Horizon Data from a PedonPC Database
+#' 
+#' Get horizon-level data from a PedonPC database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param dsn The path to a 'pedon.mdb' database.
+#' @return A data.frame.
+#' @note NULL total rock fragment values are assumed to represent an _absence_
+#' of rock fragments, and set to 0.
+#' @author Dylan E. Beaudette and Jay M. Skovlin
+#' @seealso \code{\link{get_colors_from_pedon_db}},
+#' \code{\link{get_site_data_from_pedon_db}}
+#' @keywords manip
+#' @export get_hz_data_from_pedon_db
 get_hz_data_from_pedon_db <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_lablayer_data_from_NASIS_db.R b/R/get_lablayer_data_from_NASIS_db.R
index 78c590d1..52454228 100644
--- a/R/get_lablayer_data_from_NASIS_db.R
+++ b/R/get_lablayer_data_from_NASIS_db.R
@@ -1,4 +1,22 @@
-get_lablayer_data_from_NASIS_db <- function(SS = TRUE) {
+#' Extract lab pedon layer data from a local NASIS Database
+#'
+#' Get lab pedon layer-level (horizon-level) data from a local NASIS database.
+#'
+#' @param SS fetch data from the currently loaded selected set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A data.frame.
+#' @note This function queries KSSL laboratory site/horizon data from a local
+#' NASIS database from the lab layer data table.
+#'
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @seealso \code{\link{get_labpedon_data_from_NASIS_db}}
+#' @keywords manip
+#' @export get_lablayer_data_from_NASIS_db
+get_lablayer_data_from_NASIS_db <- function(SS = TRUE, dsn = NULL) {
 
   # hacks to make R CMD check --as-cran happy:
   cec7 <- NULL
@@ -6,31 +24,26 @@ get_lablayer_data_from_NASIS_db <- function(SS = TRUE) {
   claycarb <- NULL
   carbonorganicpct <- NULL
 
-  # must have RODBC installed
-  if(!requireNamespace('RODBC')) {
-    stop('please install the `RODBC` package', call.=FALSE)
-    }
   q.ncsslablayer <- paste0("SELECT ncsspedonlabdataiidref AS labpeiid, ncsslayerlabdataiid AS labphiid, labsampnum, layerseqnum, hzdept, hzdepb, layertype, hzname, hznameoriginal, stratextsflag, moistprepstate, texcl, sandvcmeasured, sandcomeasured, sandmedmeasured, sandfinemeasured, sandvfmeasured, sandtotmeasured, siltcomeasured, siltfinemeasured, silttotmeasured, claycarbmeasured, clayfinemeasured, claytotmeasured, carbonorganicpctmeasured, carbontotalpctmeasured, ompctest, fiberrubbedpct, fiberunrubbedpct, fragwt25, fragwt520, fragwt2075, fragwt275, wtpct0175, wtpctgt2ws, ph1to1h2o, ph01mcacl2, phnaf, phoxidized, resistivity, ecmeasured, esp, sar, cecsumcations, cec7, ecec, sumbases, basesatsumcations, basesatnh4oac, caco3equivmeasured, caco3lt20measured, gypsumequivmeasured, feoxalatemeasured, feextractable, fetotal, sioxalatemeasured, extracid, extral, aloxalatemeasured, altotal, pmehlich3, ph2osolublemeasured, poxalatemeasured, polsenmeasured, ptotalmeasured, nzpretention, dbthirdbar, dbovendry, aggstabpct, wtenthbarclod, wtenthbarsieve, wthirdbarclod, wthirdbarsieve, wfifteenbarmeasured, wretentiondiffws, wfifteenbartoclay, adod, lep, cole, liquidlimitmeasured, pi, recwlupdated, ncsslayerlabdataiid
 
 FROM ncsslayerlabdata_View_1
 
 ORDER BY labpeiid, hzdept ASC;")
 
+  channel <- dbConnectNASIS(dsn)
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
 	# handle Views/selected set argument
   if(!SS)
     q.ncsslablayer <- gsub(q.ncsslablayer, pattern = "_View_1", replacement = "")
 
-	# exec queries
-	d.lablayer <- RODBC::sqlQuery(channel, q.ncsslablayer, stringsAsFactors=FALSE)
-
+	# exec query
+  d.lablayer <- dbQueryNASIS(channel, q.ncsslablayer)
 
 	# recode metadata domains
-	d.lablayer <- uncode(d.lablayer)
+	d.lablayer <- uncode(d.lablayer, dsn = dsn)
 
 
 	# trim names
@@ -43,9 +56,6 @@ ORDER BY labpeiid, hzdept ASC;")
 	  organicmatpct = round(carbonorganicpct * 1.724, 2)
 	  })
 
-	# close connection
-	RODBC::odbcClose(channel)
-
 	# return a list of results
 	return(d.lablayer)
 }
diff --git a/R/get_labpedon_data_from_NASIS_db.R b/R/get_labpedon_data_from_NASIS_db.R
index 68168bb5..64587a7f 100644
--- a/R/get_labpedon_data_from_NASIS_db.R
+++ b/R/get_labpedon_data_from_NASIS_db.R
@@ -1,24 +1,40 @@
-get_labpedon_data_from_NASIS_db <- function(SS = TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+#' Extract lab pedon data from a local NASIS Database
+#' 
+#' Get lab pedon-level data from a local NASIS database.
+#' 
+#' This function currently works only on Windows, and requires a 'nasis_local'
+#' ODBC connection.
+#' 
+#' @param SS fetch data from the currently loaded selected set in NASIS or from
+#' the entire local database (default: TRUE)
+#' 
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#' 
+#' @return A data.frame.
+#' 
+#' @note This function queries KSSL laboratory site/horizon data from a local
+#' NASIS database from the lab pedon data table.
+#' 
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @seealso \code{\link{get_lablayer_data_from_NASIS_db}}
+#' @keywords manip
+#' @export get_labpedon_data_from_NASIS_db
+get_labpedon_data_from_NASIS_db <- function(SS = TRUE, dsn = NULL) {
 
   q.ncsslabpedon <- "SELECT peiidref AS peiid, upedonid, descname, taxonname, taxclname, ncsspedonlabdata_View_1.pedlabsampnum, psctopdepth, pscbotdepth, noncarbclaywtavg, claytotwtavg, le0to100, wf0175wtavgpsc, volfractgt2wtavg, cec7clayratiowtavg, labdatasheeturl, ncsspedonlabdataiid AS labpeiid
   FROM (ncsspedonlabdata_View_1 LEFT OUTER JOIN pedon_View_1 ON ncsspedonlabdata_View_1.peiidref = pedon_View_1.peiid);"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+  
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # handle Views/selected set argument
-  if(!SS)
+  if (!SS)
     q.ncsslabpedon <- gsub(q.ncsslabpedon, pattern = "_View_1", replacement = "")
 
-	# exec queries
-	d.labpedon <- RODBC::sqlQuery(channel, q.ncsslabpedon, stringsAsFactors=FALSE)
-
-	# close connection
-	RODBC::odbcClose(channel)
+  d.labpedon <- dbQueryNASIS(channel, q.ncsslabpedon)
 
 	# return a list of results
 	return(d.labpedon)
diff --git a/R/get_phfmp_from_NASIS_db.R b/R/get_phfmp_from_NASIS_db.R
index 6cfd4a8a..39771f07 100644
--- a/R/get_phfmp_from_NASIS_db.R
+++ b/R/get_phfmp_from_NASIS_db.R
@@ -1,7 +1,5 @@
-get_phfmp_from_NASIS_db <- function(SS = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_phfmp_from_NASIS_db <- function(SS = TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
+
 
   # because of alias with fetchNASIS cannot allow setting attr
   # also, attr is a free-form field, so not terribly useful -- consider SQL LIKE?
@@ -13,20 +11,19 @@ get_phfmp_from_NASIS_db <- function(SS = TRUE, stringsAsFactors = default.string
     q <- "SELECT * FROM phfmp_View_1;"
   #}
 
-    channel <- .openNASISchannel()
-    if (channel == -1)
-      return(data.frame())
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  # exec queries
-  d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-  RODBC::odbcClose(channel)
+  # exec query
+  d <- dbQueryNASIS(channel, q)
 
   # field measured properties, long format
-  return(uncode(d, stringsAsFactors = stringsAsFactors))
+  return(uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn))
 }
diff --git a/R/get_phlabresults_data_from_NASIS_db.R b/R/get_phlabresults_data_from_NASIS_db.R
index aaef8bc0..3adc84d0 100644
--- a/R/get_phlabresults_data_from_NASIS_db.R
+++ b/R/get_phlabresults_data_from_NASIS_db.R
@@ -1,4 +1,4 @@
-.get_phlabresults_data_from_NASIS_db <- function(SS=TRUE) {
+.get_phlabresults_data_from_NASIS_db <- function(SS=TRUE, dsn = NULL) {
 
   # hacks to make R CMD check --as-cran happy:
   sampledepthbottom <- NULL
@@ -6,18 +6,15 @@
   phiidref          <- NULL
   # test_ph <- NULL
 
-  # must have RODBC installed
-  if (!requireNamespace('RODBC')) stop('please install the `RODBC` package', call.=FALSE)
-
   q <- "SELECT peiidref AS peiid, phiid, phl.seqnum, phl.sampledepthtop, sampledepthbottom, sampleid, datacollector, claytotmeasured, claycarbmeasured, silttotmeasured, siltfinemeasured, siltcomeasured, sandtotmeasured, sandtotmethod, sandvcmeasured, sandcomeasured, sandmedmeasured, sandfinemeasured, sandvfmeasured, sandvfmethod, textureclfieldlab, fiberrubbedpct, fiberunrubbedpct, ph1to1h2o, ph01mcacl2, phnaf, phoxidized, phdeltah2o2, liquidlimitmeasured, plasticlimitmeasured, pi, atterbergsampcond, cole, esttotpotacidityetpa, camgmeh2, potassiummeh2, camgsatpaste, extractaciditykcl, basesatmeh2, cec7, cec82, ecec, phosphatephos, nitratenitrogen, ecmeasured, ecdeterminemeth, ec15, caco3equivmeasured, gypsumequiv, sodium, sar, gypsumreq, humiccolor, fulviccolor, humicfulviccolor, alummeasured, pyrophoshue, pyrophosvalue, pyrophoschroma, melanicindex
 FROM
 phorizon_View_1 ph
 LEFT OUTER JOIN phlabresults_View_1 phl on phl.phiidref = ph.phiid
   ORDER BY peiidref, phiid, sampledepthtop;"
 
+  channel <- dbConnectNASIS(dsn)
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # toggle selected set vs. local DB
@@ -27,16 +24,15 @@ LEFT OUTER JOIN phlabresults_View_1 phl on phl.phiidref = ph.phiid
 
 
   # exec query
-  d.phlabresults <- RODBC::sqlQuery(channel, q, stringsAsFactors = FALSE)
-
+  d.phlabresults <- dbQueryNASIS(channel, q)
 
   # recode metadata domains
-  d.phlabresults <- uncode(d.phlabresults)
+  d.phlabresults <- uncode(d.phlabresults, dsn = dsn)
 
 
   # compute thickness
   d.phlabresults <- within(d.phlabresults, {
-    hzthk = sampledepthbottom - sampledepthtop
+     hzthk = sampledepthbottom - sampledepthtop
     })
 
 
@@ -87,7 +83,6 @@ LEFT OUTER JOIN phlabresults_View_1 phl on phl.phiidref = ph.phiid
     #   sapply(x[2:ncol(x)], function(x2) x2[which.max(x$hzthk)])
     #   })
     d.dups_char$hzthk <- NULL
-    #d.dups_char <- uncode(d.dups_char) # only necessary when using plyr
 
     num_ph <- names(d.dups)[names(d.dups) %in% c("phiidref", "hzthk") |
                           grepl("ph1to1h2o|ph01mcacl2", names(d.dups))]
@@ -129,11 +124,6 @@ LEFT OUTER JOIN phlabresults_View_1 phl on phl.phiidref = ph.phiid
   # to eliminate extra rows with no data? Not sure what is causing this on the NASIS side
   d.phlabresults <-  d.phlabresults[rowSums(is.na(d.phlabresults))<(length(d.phlabresults)-1),]
 
-
-  # close connection
-  RODBC::odbcClose(channel)
-
-
   # done
   return(d.phlabresults)
 }
diff --git a/R/get_projectmapunit_from_NASIS.R b/R/get_projectmapunit_from_NASIS.R
index 9674a7e2..8b968fd4 100644
--- a/R/get_projectmapunit_from_NASIS.R
+++ b/R/get_projectmapunit_from_NASIS.R
@@ -1,7 +1,4 @@
-get_projectmapunit_from_NASIS <- function(SS = TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_projectmapunit_from_NASIS <- function(SS = TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q <- paste("SELECT p.projectiid, p.uprojectid, p.projectname, pmu.seqnum pmu_seqnum, a2.areasymbol, lmu.musym, lmu.lmapunitiid AS mukey, mu.nationalmusym, mutype, lmu.mustatus, muname, muacres
 
@@ -23,28 +20,24 @@ get_projectmapunit_from_NASIS <- function(SS = TRUE, stringsAsFactors = default.
   )
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
+  if (SS == FALSE) {
     q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
   }
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # exec query
-  d.project <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
+  d.project <- dbQueryNASIS(channel, q)
 
   # test is selected set is empty
   if (nrow(d.project) == 0) message("your selected set is missing the project table, please load it and try again")
 
   # uncode metadata domains
-  d.project <- uncode(d.project, stringsAsFactors = stringsAsFactors)
-
-
-  # close connection
-  RODBC::odbcClose(channel)
-
+  d.project <- uncode(d.project, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d.project)
-  }
+}
diff --git a/R/get_site_data_from_NASIS_db.R b/R/get_site_data_from_NASIS_db.R
index 365815d9..174cda60 100644
--- a/R/get_site_data_from_NASIS_db.R
+++ b/R/get_site_data_from_NASIS_db.R
@@ -18,10 +18,34 @@
 ## TODO: bug within RODBC - converts site_id == 056E916010 to an exponent
 
 
-get_site_data_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+#' Extract Site Data from a local NASIS Database
+#'
+#' Get site-level data from a local NASIS database.
+#'
+#' When multiple "site bedrock" entries are present, only the shallowest is
+#' returned by this function.
+#'
+#' @param SS fetch data from Selected Set in NASIS or from the entire local
+#' database (default: `TRUE`)
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have been set outside of `uncode()` (i.e. hard
+#' coded).
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A data.frame
+#'
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @seealso \code{\link{get_hz_data_from_NASIS_db}}
+#' @keywords manip
+#'
+#' @export get_site_data_from_NASIS_db
+get_site_data_from_NASIS_db <- function(SS = TRUE,
+                                        stringsAsFactors = default.stringsAsFactors(),
+                                        dsn = NULL) {
 
 	q <- "SELECT siteiid as siteiid, peiid, CAST(usiteid AS varchar(60)) as site_id, CAST(upedonid AS varchar(60)) as pedon_id, obsdate as obs_date,
 utmzone, utmeasting, utmnorthing, -(longdegrees + CASE WHEN longminutes IS NULL THEN 0.0 ELSE longminutes / 60.0 END + CASE WHEN longseconds IS NULL THEN 0.0 ELSE longseconds / 60.0 / 60.0 END) as x, latdegrees + CASE WHEN latminutes IS NULL THEN 0.0 ELSE latminutes / 60.0 END + CASE WHEN latseconds IS NULL THEN 0.0 ELSE latseconds / 60.0 / 60.0 END as y, horizdatnm, longstddecimaldegrees as x_std, latstddecimaldegrees as y_std,
@@ -45,50 +69,47 @@ WHERE sb.rn IS NULL OR sb.rn = 1
 
 ORDER BY pedon_View_1.peiid ;"
 
-      channel <- .openNASISchannel()
-      if (channel == -1)
-        return(data.frame())
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
 	# toggle selected set vs. local DB
-	if(SS == FALSE) {
+	if (SS == FALSE) {
 	  q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
 	}
 
-
 	# exec query
-	d <- RODBC::sqlQuery(channel, q, stringsAsFactors=FALSE)
-
-	# close connection
-	RODBC::odbcClose(channel)
+	d <- dbQueryNASIS(channel, q)
 
-	## this shouldn't happen, retain for debugging
-	# test for an error
-	if(inherits(d, 'character'))
+	# ## this shouldn't happen, retain for debugging
+	# # test for an error
+	if (inherits(d, 'try-error'))
 	  stop('error in SQL')
 
 	# uncode domain columns
-	d <- uncode(d, stringsAsFactors = stringsAsFactors)
+	d <- uncode(d, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
 	# short-circuit: 0 rows means nothing in the selected set and thus we stop here
-	if(nrow(d) == 0) {
+	if (nrow(d) == 0) {
 	  return(d)
 	}
 
 
   # https://github.com/ncss-tech/soilDB/issues/41
 	# warn if mixed datums
-	if(length(na.omit(unique(d$horizdatnm))) > 1)
+	if (length(na.omit(unique(d$horizdatnm))) > 1)
 		message('multiple horizontal datums present, consider using WGS84 coordinates (x_std, y_std)')
 
 	# are there any duplicate pedon IDs?
 	t.pedon_id <- table(d$pedon_id)
 	not.unique.pedon_id <- t.pedon_id > 1
-	if(any(not.unique.pedon_id))
+	if (any(not.unique.pedon_id))
 		assign('dup.pedon.ids', value=names(t.pedon_id[which(not.unique.pedon_id)]), envir=soilDB.env)
 
 	# warn about sites without a matching pedon (records missing peiid)
 	missing.pedon <- which(is.na(d$peiid))
-	if(length(missing.pedon)> 0)
+	if (length(missing.pedon) > 0)
 		assign('sites.missing.pedons', value=unique(d$site_id[missing.pedon]), envir=soilDB.env)
 
   ## set factor levels, when it makes sense
diff --git a/R/get_site_data_from_pedon_db.R b/R/get_site_data_from_pedon_db.R
index 6fa012e7..b082539f 100644
--- a/R/get_site_data_from_pedon_db.R
+++ b/R/get_site_data_from_pedon_db.R
@@ -5,6 +5,21 @@
 # siteiidref key removed from pedon table - use the pedon.siteobsiidref through the siteobs table (siteobs.siteobsiid) as the new linkage	
 
 
+
+
+#' Extract Site Data from a PedonPC Database
+#' 
+#' Get site-level data from a PedonPC database.
+#' 
+#' 
+#' @param dsn The path to a 'pedon.mdb' database.
+#' @return A data.frame.
+#' @note This function currently works only on Windows.
+#' @author Dylan E. Beaudette and Jay M. Skovlin
+#' @seealso \code{\link{get_hz_data_from_pedon_db}},
+#' \code{\link{get_veg_from_AK_Site}},
+#' @keywords manip
+#' @export get_site_data_from_pedon_db
 get_site_data_from_pedon_db <- function(dsn) {
   
   # must have RODBC installed
diff --git a/R/get_soilseries_from_NASIS.R b/R/get_soilseries_from_NASIS.R
index 921ff8f2..7b4a4c78 100644
--- a/R/get_soilseries_from_NASIS.R
+++ b/R/get_soilseries_from_NASIS.R
@@ -1,6 +1,27 @@
-get_soilseries_from_NASIS <- function(stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if (!requireNamespace('RODBC')) stop('please install the `RODBC` package', call.=FALSE)
+#' Get records from the Soil Classification (SC) database
+#'
+#' These functions return records from the Soil Classification database, either
+#' from the local NASIS database (all series) or via web report (named series
+#' only).
+#'
+#' @aliases get_soilseries_from_NASIS get_soilseries_from_NASISWebReport
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors? This argument is passed to the `uncode()` function. It does not
+#' convert those vectors that have set outside of `uncode()` (i.e. hard coded).
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A \code{data.frame}
+#'
+#' @author Stephen Roecker
+#'
+#' @keywords manip
+#'
+#' @export get_soilseries_from_NASIS
+get_soilseries_from_NASIS <- function(stringsAsFactors = default.stringsAsFactors(),
+                                      dsn = NULL) {
 
   q.soilseries <- "
   SELECT soilseriesname, soilseriesstatus, benchmarksoilflag, statsgoflag, mlraoffice, areasymbol, areatypename, taxclname, taxorder, taxsuborder, taxgrtgroup, taxsubgrp, taxpartsize, taxpartsizemod, taxceactcl, taxreaction, taxtempcl, originyear, establishedyear, soiltaxclasslastupdated, soilseriesiid
@@ -19,18 +40,16 @@ get_soilseries_from_NASIS <- function(stringsAsFactors = default.stringsAsFactor
   # LEFT OUTER JOIN
   #     soilseriestaxmineralogy sstm ON sstm.soilseriesiidref = ss.soilseriesiid
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
+  channel <- dbConnectNASIS(dsn)
+
+  if (inherits(channel, 'try-error'))
     return(data.frame())
 
   # exec query
-  d.soilseries <- RODBC::sqlQuery(channel, q.soilseries, stringsAsFactors = FALSE)
-
-  # close connection
-  RODBC::odbcClose(channel)
+  d.soilseries <- dbQueryNASIS(channel, q.soilseries)
 
   # recode metadata domains
-  d.soilseries <- uncode(d.soilseries, stringsAsFactors = stringsAsFactors)
+  d.soilseries <- uncode(d.soilseries, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # prep
   d.soilseries$soiltaxclasslastupdated <- format(d.soilseries$soiltaxclasslastupdated, "%Y")
@@ -39,11 +58,9 @@ get_soilseries_from_NASIS <- function(stringsAsFactors = default.stringsAsFactor
   return(d.soilseries)
 }
 
-
-
 get_soilseries_from_NASISWebReport <- function(soils, stringsAsFactors = default.stringsAsFactors()) {
 
-  url <-"https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_soilseries_from_NASISWebReport"
+  url <- "https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=get_soilseries_from_NASISWebReport"
 
   d.ss <- lapply(soils, function(x) {
     args = list(p_soilseriesname = x)
@@ -52,8 +69,11 @@ get_soilseries_from_NASISWebReport <- function(soils, stringsAsFactors = default
   d.ss <- do.call("rbind", d.ss)
 
   # set factor levels according to metadata domains
-  d.ss[! names(d.ss) %in% c("mlraoffice", "taxminalogy")] <- uncode(d.ss[! names(d.ss) %in% c("mlraoffice", "taxminalogy")], db = "SDA", stringsAsFactors = stringsAsFactors)
-  d.ss[names(d.ss) %in% c("mlraoffice")] <- uncode(d.ss[names(d.ss) %in% c("mlraoffice")], db = "LIMS", stringsAsFactors = stringsAsFactors)
+  d.ss[!names(d.ss) %in% c("mlraoffice", "taxminalogy")] <- uncode(d.ss[!names(d.ss) %in% c("mlraoffice", "taxminalogy")],
+                                                                   db = "SDA", stringsAsFactors = stringsAsFactors)
+
+  d.ss[names(d.ss) %in% c("mlraoffice")] <- uncode(d.ss[names(d.ss) %in% c("mlraoffice")],
+                                                   db = "LIMS", stringsAsFactors = stringsAsFactors)
 
   # return data.frame
   return(d.ss)
diff --git a/R/get_text_notes_from_NASIS_db.R b/R/get_text_notes_from_NASIS_db.R
index cc7ed356..b0765f4c 100644
--- a/R/get_text_notes_from_NASIS_db.R
+++ b/R/get_text_notes_from_NASIS_db.R
@@ -1,60 +1,97 @@
-get_text_notes_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+#' Extract text note data from a local NASIS Database
+#'
+#' @param SS get data from the currently loaded Selected Set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#'
+#' @param fixLineEndings convert line endings from `\r\n` to `\n`
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A `list` with the results.
+#' @author Dylan E. Beaudette and Jay M. Skovlin
+#' @seealso \code{\link{get_hz_data_from_pedon_db}},
+#' \code{\link{get_site_data_from_pedon_db}}
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#' if(local_NASIS_defined()) {
+#'  # query text note data
+#'  t <- try(get_text_notes_from_NASIS_db())
+#'
+#'  # show contents text note data, includes: siteobs, site, pedon, horizon level text notes data.
+#'  str(t)
+#'
+#'  # view text categories for site text notes
+#'  if(!inherits(t, 'try-error')) {
+#'   table(t$site_text$textcat)
+#'  }
+#' }
+#' }
+#'
+#' @export get_text_notes_from_NASIS_db
+get_text_notes_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE, dsn = NULL) {
 
 	# petext
-	q.petext <- "SELECT recdate, recauthor, pedontextkind, textcat, textsubcat, CAST(textentry AS ntext) AS textentry, peiidref AS peiid, petextiid FROM petext_View_1;"
+	q.petext <- "SELECT recdate, recauthor, pedontextkind, textcat, textsubcat, peiidref AS peiid, petextiid, CAST(textentry AS ntext) AS textentry FROM petext_View_1;"
 
 	# sitetext
-	q.sitetext <- "SELECT recdate, recauthor, sitetextkind, textcat, textsubcat, CAST(textentry AS ntext) AS textentry, siteiidref AS siteiid, sitetextiid FROM sitetext_View_1;"
+	q.sitetext <- "SELECT recdate, recauthor, sitetextkind, textcat, textsubcat, siteiidref AS siteiid, sitetextiid, CAST(textentry AS ntext) AS textentry FROM sitetext_View_1;"
 
 	# siteobstext
-	q.siteobstext <- "SELECT recdate, recauthor, siteobstextkind, textcat, textsubcat, CAST(textentry AS ntext) AS textentry, siteiidref AS site_id, siteobstextiid FROM (
+	q.siteobstext <- "SELECT recdate, recauthor, siteobstextkind, textcat, textsubcat, siteiidref AS site_id, siteobstextiid, CAST(textentry AS ntext) AS textentry FROM (
 siteobs_View_1 LEFT OUTER JOIN
 siteobstext_View_1 ON siteobs_View_1.siteobsiid = siteobstext_View_1.siteobsiidref);"
 
 	# phtext
-	q.phtext <- "SELECT recdate, recauthor, phorizontextkind, textcat, textsubcat, CAST(textentry AS ntext) AS textentry, phiidref AS phiid, phtextiid FROM phtext_View_1;"
+	q.phtext <- "SELECT recdate, recauthor, phorizontextkind, textcat, textsubcat, phiidref AS phiid, phtextiid, CAST(textentry AS ntext) AS textentry FROM phtext_View_1;"
 
 	# photo links
-	q.photos <- "SELECT recdate, recauthor, siteobstextkind, textcat, textsubcat, CAST(textentry AS ntext) AS textentry, siteiidref AS site_id, siteobstextiid FROM (siteobs_View_1 LEFT OUTER JOIN siteobstext_View_1 ON siteobs_View_1.siteobsiid = siteobstext_View_1.siteobsiidref) WHERE siteobstext_View_1.textcat LIKE 'Photo%' ORDER BY siteobstext_View_1.siteobstextkind;"
+	q.photos <- "SELECT recdate, recauthor, siteobstextkind, textcat, textsubcat, siteiidref AS site_id, siteobstextiid, CAST(textentry AS ntext) AS textentry FROM (siteobs_View_1 LEFT OUTER JOIN siteobstext_View_1 ON siteobs_View_1.siteobsiid = siteobstext_View_1.siteobsiidref) WHERE siteobstext_View_1.textcat LIKE 'Photo%' ORDER BY siteobstext_View_1.siteobstextkind;"
+
+	# toggle selected set vs. local DB
+	if (SS == FALSE) {
+	  q.petext <- gsub(pattern = '_View_1', replacement = '', x = q.petext, fixed = TRUE)
+	  q.sitetext <- gsub(pattern = '_View_1', replacement = '', x = q.sitetext, fixed = TRUE)
+	  q.siteobstext <- gsub(pattern = '_View_1', replacement = '', x = q.siteobstext, fixed = TRUE)
+	  q.phtext <- gsub(pattern = '_View_1', replacement = '', x = q.phtext, fixed = TRUE)
+	  q.photos <- gsub(pattern = '_View_1', replacement = '', x = q.photos, fixed = TRUE)
+	}
 
 	# check for RODBC, NASIS credential options, and successful connection
-	channel <- .openNASISchannel()
-	if (channel == -1)
+	channel <- dbConnectNASIS(dsn)
+
+	if (inherits(channel, 'try-error'))
 	  return(data.frame())
 
 	# run queries
-	d.petext <- RODBC::sqlQuery(channel, q.petext, stringsAsFactors=FALSE)
-	d.sitetext <- RODBC::sqlQuery(channel, q.sitetext, stringsAsFactors=FALSE)
-	d.siteobstext <- RODBC::sqlQuery(channel, q.siteobstext, stringsAsFactors=FALSE)
-	d.phtext <- RODBC::sqlQuery(channel, q.phtext, stringsAsFactors=FALSE)
-	d.photos <- RODBC::sqlQuery(channel, q.photos, stringsAsFactors=FALSE)
-
-	# close connection
-	RODBC::odbcClose(channel)
+	d.petext <- dbQueryNASIS(channel, q.petext, close = FALSE)
+	d.sitetext <- dbQueryNASIS(channel, q.sitetext, close = FALSE)
+	d.siteobstext <- dbQueryNASIS(channel, q.siteobstext, close = FALSE)
+	d.phtext <- dbQueryNASIS(channel, q.phtext, close = FALSE)
+	d.photos <- dbQueryNASIS(channel, q.photos)
 
 	# uncode domained columns
-	d.petext <- uncode(d.petext)
-	d.sitetext <- uncode(d.sitetext)
-	d.siteobstext <- uncode(d.siteobstext)
-	d.phtext <- uncode(d.phtext)
-	d.photos <- uncode(d.photos)
+	d.petext <- uncode(d.petext, dsn = dsn)
+	d.sitetext <- uncode(d.sitetext, dsn = dsn)
+	d.siteobstext <- uncode(d.siteobstext, dsn = dsn)
+	d.phtext <- uncode(d.phtext, dsn = dsn)
+	d.photos <- uncode(d.photos, dsn = dsn)
 
 	# optionally convert \r\n -> \n
- 	 if(fixLineEndings){
+ 	if (fixLineEndings) {
    	 d.petext$textentry <- gsub(d.petext$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
    	 d.sitetext$textentry <- gsub(d.sitetext$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
    	 d.siteobstext$textentry <- gsub(d.siteobstext$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
    	 d.phtext$textentry <- gsub(d.phtext$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
-  	}
+  }
 
 	# return a list of results
-	return(list(pedon_text=d.petext,
-							site_text=d.sitetext,
-							siteobs_text=d.siteobstext,
-							horizon_text=d.phtext,
-							photo_links=d.photos))
+	return(list(pedon_text = d.petext,
+							site_text = d.sitetext,
+							siteobs_text = d.siteobstext,
+							horizon_text = d.phtext,
+							photo_links = d.photos))
 
 }
diff --git a/R/get_veg_data_from_NASIS_db.R b/R/get_veg_data_from_NASIS_db.R
index 32d93a5e..a27d90cd 100644
--- a/R/get_veg_data_from_NASIS_db.R
+++ b/R/get_veg_data_from_NASIS_db.R
@@ -1,9 +1,36 @@
 ## TODO: merge with other vegplot functions
 
-get_veg_data_from_NASIS_db <- function(SS=TRUE) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+
+
+#' Extract veg data from a local NASIS Database
+#' 
+#' Extract veg data from a local NASIS Database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param SS get data from the currently loaded Selected Set in NASIS or from
+#' the entire local database (default: `TRUE`)
+#' 
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#' 
+#' @return A list with the results.
+#' @author Jay M. Skovlin and Dylan E. Beaudette
+#' @keywords manip
+#' @examples
+#' 
+#' \donttest{
+#' if(local_NASIS_defined()) {
+#'  # query text note data
+#'  v <- try(get_veg_from_NASIS_db())
+#' 
+#'  # show contents veg data returned
+#'  str(v)
+#' }
+#' }
+#' 
+#' @export get_veg_data_from_NASIS_db
+get_veg_data_from_NASIS_db <- function(SS=TRUE, dsn = NULL) {
 
 # warning to use NASIS query to load related vegplot data for this to work
 warning("In order to query this data you'll need to load all related vegplots to your sites and pedons in NASIS.", call. = FALSE)
@@ -26,47 +53,44 @@ warning("In order to query this data you'll need to load all related vegplots to
   }
 
   # existing veg
-#q.inventory <- "SELECT siteiid, vegplotid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, assocuserpedonid, #plotplantinventory.seqnum, plantsym, plantsciname, plantnatvernm, orderofdominance, speciescancovpct, speciescancovclass
-#
-#  FROM site_View_1 AS s
-#  INNER JOIN siteobs ON siteobs.siteiidref=s.siteiid
-#  LEFT JOIN vegplot_View_1 AS v on v.siteobsiidref=siteobs.siteobsiid
-#  LEFT JOIN plotplantinventory ON plotplantinventory.vegplotiidref=v.vegplotiid
-#  INNER JOIN plant ON plant.plantiid=plotplantinventory.plantiidref
-#  ORDER BY s.siteiid, plotplantinventory.seqnum;"
-
-# existing veg
-q.vegtransect <- "SELECT siteiid, vegplotid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, assocuserpedonid, vegtransectid, vegtransplantsummiid vtpsiid, transectlength, plantsym, plantsciname, plantnatvernm
-
-  FROM site_View_1 AS s
-  INNER JOIN siteobs ON siteobs.siteiidref=s.siteiid
-  INNER JOIN vegplot_View_1 AS v on v.siteobsiidref=siteobs.siteobsiid
-  LEFT JOIN vegtransect AS vt ON vt.vegplotiidref=v.vegplotiid
-  LEFT JOIN vegtransectplantsummary AS vtps ON vtps.vegtransectiidref=vt.vegtransectiid
-  INNER JOIN plant ON plant.plantiid=vtps.plantiidref
-  ORDER BY s.siteiid;"
-
-# toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q.veg <- gsub(pattern = '_View_1', replacement = '', x = q.veg, fixed = TRUE)
+  #q.inventory <- "SELECT siteiid, vegplotid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, assocuserpedonid, #plotplantinventory.seqnum, plantsym, plantsciname, plantnatvernm, orderofdominance, speciescancovpct, speciescancovclass
+  #
+  #  FROM site_View_1 AS s
+  #  INNER JOIN siteobs ON siteobs.siteiidref=s.siteiid
+  #  LEFT JOIN vegplot_View_1 AS v on v.siteobsiidref=siteobs.siteobsiid
+  #  LEFT JOIN plotplantinventory ON plotplantinventory.vegplotiidref=v.vegplotiid
+  #  INNER JOIN plant ON plant.plantiid=plotplantinventory.plantiidref
+  #  ORDER BY s.siteiid, plotplantinventory.seqnum;"
+  
+  # existing veg
+  q.vegtransect <- "SELECT siteiid, vegplotid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, assocuserpedonid, vegtransectid, vegtransplantsummiid vtpsiid, transectlength, plantsym, plantsciname, plantnatvernm
+  
+    FROM site_View_1 AS s
+    INNER JOIN siteobs ON siteobs.siteiidref=s.siteiid
+    INNER JOIN vegplot_View_1 AS v on v.siteobsiidref=siteobs.siteobsiid
+    LEFT JOIN vegtransect AS vt ON vt.vegplotiidref=v.vegplotiid
+    LEFT JOIN vegtransectplantsummary AS vtps ON vtps.vegtransectiidref=vt.vegtransectiid
+    INNER JOIN plant ON plant.plantiid=vtps.plantiidref
+    ORDER BY s.siteiid;"
+  
+  # toggle selected set vs. local DB
+  if (SS == FALSE) {
+    q.vegtransect <- gsub(pattern = '_View_1', replacement = '', x = q.vegtransect, fixed = TRUE)
   }
-
-#q.plant <- "SELECT plantiid, plantsym
-#  FROM plant_View_1;"
-
-channel <- .openNASISchannel()
-if (channel == -1)
-  return(data.frame())
-
-# exec queries
-d.veg <- RODBC::sqlQuery(channel, q.veg, stringsAsFactors=FALSE)
-d.vegtransect <- RODBC::sqlQuery(channel, q.vegtransect, stringsAsFactors=FALSE)
-
-# close connection
-RODBC::odbcClose(channel)
-
-
-# return a list of results
-return(list(veg=d.veg,
-            vegtransect=d.vegtransect))
+  
+  #q.plant <- "SELECT plantiid, plantsym
+  #  FROM plant_View_1;"
+  
+  channel <- dbConnectNASIS(dsn)
+  
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
+  
+  # exec queries
+  d.veg <-  dbQueryNASIS(channel, q.veg, close = FALSE)  
+  d.vegtransect <- dbQueryNASIS(channel, q.vegtransect) 
+  
+  # return a list of results
+  return(list(veg = d.veg,
+              vegtransect = d.vegtransect))
 }
diff --git a/R/get_veg_from_AK_Site.R b/R/get_veg_from_AK_Site.R
index 9dc3d244..d1fde50f 100644
--- a/R/get_veg_from_AK_Site.R
+++ b/R/get_veg_from_AK_Site.R
@@ -1,5 +1,19 @@
-
 # gets all veg records per site ID
+
+
+#' Retrieve Vegetation Data from an AK Site Database
+#' 
+#' Retrieve Vegetation Data from an AK Site Database
+#' 
+#' 
+#' @param dsn file path the the AK Site access database
+#' @return A data.frame with vegetation data in long format, linked to site ID.
+#' @note This function currently works only on Windows.
+#' @author Dylan E. Beaudette
+#' @seealso \code{\link{get_hz_data_from_pedon_db}},
+#' \code{\link{get_site_data_from_pedon_db}}
+#' @keywords manip
+#' @export get_veg_from_AK_Site
 get_veg_from_AK_Site <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_veg_from_MT_veg_db.R b/R/get_veg_from_MT_veg_db.R
index dbf4c2e2..cfc42251 100644
--- a/R/get_veg_from_MT_veg_db.R
+++ b/R/get_veg_from_MT_veg_db.R
@@ -1,3 +1,17 @@
+#' Extract Site and Plot-level Data from a Montana RangeDB database
+#' 
+#' Get Site and Plot-level data from a Montana RangeDB database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param dsn The name of the Montana RangeDB front-end database connection
+#' (see details).
+#' @return A data.frame.
+#' @author Jay M. Skovlin
+#' @seealso \code{\link{get_veg_species_from_MT_veg_db}},
+#' \code{\link{get_veg_other_from_MT_veg_db}}
+#' @keywords manip
+#' @export get_veg_from_MT_veg_db
 get_veg_from_MT_veg_db <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_veg_from_NPS_PLOTS_db.R b/R/get_veg_from_NPS_PLOTS_db.R
index 4343e191..82bf0b06 100644
--- a/R/get_veg_from_NPS_PLOTS_db.R
+++ b/R/get_veg_from_NPS_PLOTS_db.R
@@ -2,6 +2,22 @@
 # add as get_veg_from_NPS_PLOTS_db() to soilDB package
 # Jay Skovlin, 12/4/2013
 # dsn <- "H:/GNP_vegetation_data_MR/GlacierNP_vegdata/PLOTS_v32_BE.accdb"
+
+
+#' Retrieve Vegetation Data from an NPS PLOTS Database
+#' 
+#' Used to extract species, stratum, and cover vegetation data from a backend
+#' NPS PLOTS Database.  Currently works for any Microsoft Access database with
+#' an .mdb file format.
+#' 
+#' 
+#' @param dsn file path to the NPS PLOTS access database on your system.
+#' @return A data.frame with vegetation data in a long format with linkage to
+#' NRCS soil pedon data via the site_id key field.
+#' @note This function currently only works on Windows.
+#' @author Jay M. Skovlin
+#' @keywords manip
+#' @export get_veg_from_NPS_PLOTS_db
 get_veg_from_NPS_PLOTS_db <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_veg_other_from_MT_veg_db.R b/R/get_veg_other_from_MT_veg_db.R
index 52877f19..740a2cc0 100644
--- a/R/get_veg_other_from_MT_veg_db.R
+++ b/R/get_veg_other_from_MT_veg_db.R
@@ -1,3 +1,17 @@
+#' Extract cover composition data from a Montana RangeDB database
+#' 
+#' Get cover composition data from a Montana RangeDB database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param dsn The name of the Montana RangeDB front-end database connection
+#' (see details).
+#' @return A data.frame.
+#' @author Jay M. Skovlin
+#' @seealso \code{\link{get_veg_from_MT_veg_db}},
+#' \code{\link{get_veg_species_from_MT_veg_db}}
+#' @keywords manip
+#' @export get_veg_other_from_MT_veg_db
 get_veg_other_from_MT_veg_db <- function(dsn) {
   
   # must have RODBC installed
diff --git a/R/get_veg_species_from_MT_veg_db.R b/R/get_veg_species_from_MT_veg_db.R
index 87671c12..495eba79 100644
--- a/R/get_veg_species_from_MT_veg_db.R
+++ b/R/get_veg_species_from_MT_veg_db.R
@@ -1,3 +1,17 @@
+#' Extract species-level Data from a Montana RangeDB database
+#' 
+#' Get species-level data from a Montana RangeDB database.
+#' 
+#' This function currently works only on Windows.
+#' 
+#' @param dsn The name of the Montana RangeDB front-end database connection
+#' (see details).
+#' @return A data.frame.
+#' @author Jay M. Skovlin
+#' @seealso \code{\link{get_veg_from_MT_veg_db}},
+#' \code{\link{get_veg_other_from_MT_veg_db}}
+#' @keywords manip
+#' @export get_veg_species_from_MT_veg_db
 get_veg_species_from_MT_veg_db <- function(dsn) {
   # must have RODBC installed
   if(!requireNamespace('RODBC'))
diff --git a/R/get_vegplot_data_from_NASIS_db.R b/R/get_vegplot_data_from_NASIS_db.R
index efa98599..53a835a3 100644
--- a/R/get_vegplot_data_from_NASIS_db.R
+++ b/R/get_vegplot_data_from_NASIS_db.R
@@ -1,9 +1,6 @@
 ## lower level functions for fetchVegdata()
 
-get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q.vegplot <- "SELECT siteiid, p.peiid, usiteid as site_id, assocuserpedonid as pedon_id, v.vegplotid as vegplot_id, vegplotiid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, vegdataorigin, vegplotsize, soilprofileindicator, soil232idlegacy, ahorizondepth, alkalinesalineindicator, alkalineaffected, salinityclass, restrictivelayerdepthlegacy, legacysoilcompname, legacysoilphase, legacylocalsoilphase, legacysoilsurftext, legacysurftextmod, legacyterminlieu, erosionclasslegacy, landformgrouplegacy, cryptogamcovcllegacy, rangelandusehistory, cancovpctplotave, cancovtotalpct, cancovtotalclass, overstorycancontotalpct, overstorycancovtotalclass, dblsampannualprodave, compyieldproductionave, abovegroundbiomasstotave, understoryreprodabundance, woodyunderstoryabundance, herbundertoryabundance, lichensunderstoryabundance, crowncanclosurepct, crowncancloseassessmethod, crowncompfactorlpp, crowncomplppavedbh, basalcoverpctave, basalareaplottotal, basalareaassessmethod, constreeshrubgrp, windbreakrowonedirection, windbreaktrappedsoildepth, windbreaktrappedsoiltexture, understorydescindicator, mensurationdataindicator, vigorclasslegacy, siteconditionlegacy, overstoryspecieslegacy, plantmoiststate, currenttreedensity, currenttreespacing, currentdxspacing, currentplotavedbh, plotbasalareafactor, currentbasalarea, foreststandtype, foreststratainventoried, foreststandregen, foreststandquality, desiredtreedensity, desireddxspacing, desiredbasalarea, excessbasalarea, excesstreedensity, stockingchangepct, treepctgoodcondition, treepctfaircondition, treepctpoorcondition, treecounttotal, treesnagdensityhard, treesnagdensitysoft, pastureforagetype, pasturestanddensityave, pastureplanthtave, pastureprodave, pcidesirableplants, pciplantcover, pciplantdiversity, pcigroundcovresidue, pcistandingdeadforage, pciplantresiduecompscore, pciplantvigor, pcilegumepctclass, pciuseuniformity, pcilivestockconcareas, pcisoilcompaction, pcisheetrillerosion, pciwinderosion, pcistreamshoreerosion, pcigullyerosion, pcierosioncompscore, pcipastureconditionscore, refplantcommunity, repannualprod, totestannualprod, totallowableannualprod, totpalatableannualprod, similarityindex, annualuseableprod, harvesteffpct, takehalfleavehalf, acresperaum, aumperacre, audperacre, desirableplantvigor, desirableseedlingabundance, decadentplantabundance, plantresidueadequacy, undesirableinvadingspecies, majorinvadingspecies, invadingspeciescancovpct, soilsurferosion, soilcrusting, soilcompaction, baregroundpct, gullyrillpresence, soildegradationrating, rangetrendcurrent, rangetrendplanned, qcreviewperson, qcreviewdate, qareviewperson, qareviewdate, swcdlegacy, fieldofficelegacy, nrcsarealegacy, aktotallichencoverpct, aktotallitter1coverpct, aktotallitter2coverpct, aktotalmosscoverpct, aktotalrockcoverpct, aktotalsoilcoverpct, aktotalwatercoverpct, akecologicalsitestatus, aktotalbedrockcoverpct, akfieldecositeid
   FROM
@@ -13,75 +10,70 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   LEFT OUTER JOIN pedon_View_1 AS p ON p.siteobsiidref=so.siteobsiid
   ORDER BY s.siteiid;"
 
-  channel <- .openNASISchannel()
-  if (channel == -1)
-    return(data.frame())
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegplot <- RODBC::sqlQuery(channel, q.vegplot, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vegplot <- gsub(pattern = '_View_1', replacement = '', x = q.vegplot, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
+  # exec query
+  d.vegplot <- dbQueryNASIS(channel, q.vegplot)
 
-  d <- uncode(d.vegplot)
+  # toggle selected set vs. local DB
+  if (SS == FALSE) {
+    q.vegplot <- gsub(pattern = '_View_1', replacement = '', x = q.vegplot, fixed = TRUE)
+  }
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots in your selected set!')
+  if (nrow(d.vegplot) == 0)
+    stop('there are no NASIS vegplots in your selected set!')
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegplot, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
 
-  # get location data from the corresponding record in the site table
-  get_vegplot_location_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get location data from the corresponding record in the site table
+get_vegplot_location_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # query the coordinate, plss description, and site characteristics data for these records from the site table
-  q.plotlocation <- "SELECT s.siteiid, s.usiteid as site_id, v.vegplotid as vegplot_id, vegplotiid, so.obsdate, v.datacollectionpurpose, latdegrees, latminutes, latseconds, latdir, longdegrees, longminutes, longseconds, longdir, horizdatnm, locdesc, plsssdetails, plsssection, plsstownship, plssrange, plssmeridian, utmzone, utmnorthing, utmeasting, latstddecimaldegrees, longstddecimaldegrees, geocoordsource, elev, slope, aspect
+  q.plotlocation <- "SELECT s.siteiid, s.usiteid as site_id, v.vegplotid as vegplot_id, vegplotiid, so.obsdate, v.datacollectionpurpose, latdegrees, latminutes, latseconds, latdir, longdegrees, longminutes, longseconds, longdir, horizdatnm, plsssection, plsstownship, plssrange, plssmeridian, utmzone, utmnorthing, utmeasting, latstddecimaldegrees, longstddecimaldegrees, geocoordsource, elev, slope, aspect, CAST(plsssdetails AS ntext) AS plsssdetails, CAST(locdesc AS ntext) AS locdesc
   FROM
   site_View_1 AS s
   INNER JOIN siteobs_View_1 AS so ON so.siteiidref=s.siteiid
   INNER JOIN vegplot_View_1 AS v ON v.siteobsiidref=so.siteobsiid
   ORDER BY s.siteiid;"
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.plotlocation <- RODBC::sqlQuery(channel, q.plotlocation, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.plotlocation <- gsub(pattern = '_View_1', replacement = '', x = q.plotlocation, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.plotlocation)
-
-  # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots in your selected set!')
+  # exec query
+  d.plotlocation <- dbQueryNASIS(channel, q.plotlocation, stringsAsFactors = FALSE)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.plotlocation, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
+  # test for no data
+  if (nrow(d) == 0)
+    stop('there are no NASIS vegplots in your selected set!')
 
   # hack for CRAN check
   state_FIPS_codes <- NULL
+
   # load FIPS codes from local package data
   load(system.file("data/state_FIPS_codes.rda", package="soilDB"))
 
@@ -106,11 +98,8 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
 
 
 
-  # get Rangeland Health Indicator(RHI) associated fields in the vegplot table
-  get_vegplot_trhi_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get Rangeland Health Indicator(RHI) associated fields in the vegplot table
+get_vegplot_trhi_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q.vegplotrhi <- "SELECT siteiid, p.peiid, usiteid as site_id, assocuserpedonid as pedon_id, v.vegplotid as vegplot_id, vegplotiid, vegplotname, obsdate, rhiannualprod, rhibareground, rhicompactionlayer, rhifuncstructgroups, rhierosionresistance, rhigullies, rhirills, rhipedastalsterracettes, rhiinfilrunoff, rhilitteramount, rhilittermovement, rhiplantmortality, rhireprodcapability, rhiinvasiveplants, rhisoilsurfdegradation, rhiwaterflowpatterns, rhiwindscourareas, rhisoilsitestabsumm, rhibioticintegritysumm, rhihydrofunctionsumm
   FROM
@@ -120,39 +109,34 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   LEFT OUTER JOIN pedon_View_1 AS p ON p.siteobsiidref=so.siteobsiid
   ORDER BY s.siteiid;"
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegplotrhi <- RODBC::sqlQuery(channel, q.vegplotrhi, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vegplotrhi <- gsub(pattern = '_View_1', replacement = '', x = q.vegplotrhi, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
+  # exec query
+  d.vegplotrhi <- dbQueryNASIS(channel, q.vegplotrhi)
 
-  d <- uncode(d.vegplotrhi)
+  # uncode metadata domains
+  d <- uncode(d.vegplotrhi, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots in your selected set!')
-
-  # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  if (nrow(d) == 0) {
+    stop('there are no NASIS vegplots in your selected set!')
+  }
 
   # done
   return(d)
 }
 
 
-  # get vegplot species - this is a reconstruction of a site existing species list
-  get_vegplot_species_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get vegplot species - this is a reconstruction of a site existing species list
+get_vegplot_species_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   q.vegplotspecies <- "SELECT siteiid, vegplotid, vegplotname, obsdate, primarydatacollector, datacollectionpurpose, assocuserpedonid, ppi.seqnum, plantsym, plantsciname, plantnatvernm, orderofdominance, speciescancovpct, speciescancovclass
   FROM
@@ -163,42 +147,37 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   INNER JOIN plant ON plant.plantiid=ppi.plantiidref
   ORDER BY s.siteiid, ppi.orderofdominance, ppi.seqnum;"
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegplotspecies <- RODBC::sqlQuery(channel, q.vegplotspecies, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vegplotspecies <- gsub(pattern = '_View_1', replacement = '', x = q.vegplotspecies, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
+  # exec query
+  d.vegplotspecies <- dbQueryNASIS(channel, q.vegplotspecies)
 
-  d <- uncode(d.vegplotspecies)
+  # uncode metadata domains
+  d <- uncode(d.vegplotspecies, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots in your selected set!')
-
-  # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  if (nrow(d) == 0) {
+    stop('there are no NASIS vegplots in your selected set!', call. = FALSE)
+  }
 
   # done
   return(d)
 }
 
 
-  # get vegplot transect data
-  get_vegplot_transect_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get vegplot transect data
+get_vegplot_transect_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # veg transect data - many transects to one vegplot
-  q.vegtransect<- "SELECT siteiid, p.peiid, vegplotiidref, vegtransectiid, usiteid as site_id, assocuserpedonid as pedon_id, vegplotid as vegplot_id, vegplotname, vegtransectid as vegtransect_id, obsdate, primarydatacollector, datacollectionpurpose, transectstartlatitude, transectstartlongitude, transectendlatitude, transectendlongitude, transectazimuth, transectlength, transectstartelevation, transectendelevation, dblsampquadratssampled, dblsampquadratsclipped, nestedfreqquadratssampled, freqquadratssampled, dwrquadratssampled, daubenmirequadratssampled, quadratsizedomlegacy, quadratsizeseclegacy, quadratshapedomlegacy, quadratshapeseclegacy, beltwidth, dblsampannualprod, totharvestannualprod, wtunitannualprod, dwrannualprod, comparativeyieldprod, comparativeyieldranktotal, comparativeyieldrankave, comparativerefclipwtave, abovegroundbiomasstotal, standingherbbiomass, transectbasalcovpct, basalcovpcttotal, basalgapsizemin, canopygapsizemin, gapsmeasuredbetween, canopygaplengthtotal, canopygappcttotal, basalgaplengthtotal, basalgappcttotal, vt.understoryreprodabundance, vt.woodyunderstoryabundance, vt.herbundertoryabundance, vt.lichensunderstoryabundance, cancovpcttotaltrans, cancovtotalclasstrans, cancovassessmethod, vt.crowncanclosurepct, vt.crowncancloseassessmethod, vt.crowncompfactorlpp, vt.crowncomplppavedbh, overstorycancovpcttrans, overstorycancovclasstrans, groundcovassessmethod, groundcovquadratssampled, groundcovpointssampled, groundsurfcovassessmethod, groundsurfcovquadratsamp, groundsurfcovpointssamp, lpiobsinterval, totalpointssampledcount, topcanopyhtave, topcanopyhtstddev, totalnumplantsbelt, totalnumspeciesbelt, totalplantdensitybelt
+  q.vegtransect <- "SELECT siteiid, p.peiid, vegplotiidref, vegtransectiid, usiteid as site_id, assocuserpedonid as pedon_id, vegplotid as vegplot_id, vegplotname, vegtransectid as vegtransect_id, obsdate, primarydatacollector, datacollectionpurpose, transectstartlatitude, transectstartlongitude, transectendlatitude, transectendlongitude, transectazimuth, transectlength, transectstartelevation, transectendelevation, dblsampquadratssampled, dblsampquadratsclipped, nestedfreqquadratssampled, freqquadratssampled, dwrquadratssampled, daubenmirequadratssampled, quadratsizedomlegacy, quadratsizeseclegacy, quadratshapedomlegacy, quadratshapeseclegacy, beltwidth, dblsampannualprod, totharvestannualprod, wtunitannualprod, dwrannualprod, comparativeyieldprod, comparativeyieldranktotal, comparativeyieldrankave, comparativerefclipwtave, abovegroundbiomasstotal, standingherbbiomass, transectbasalcovpct, basalcovpcttotal, basalgapsizemin, canopygapsizemin, gapsmeasuredbetween, canopygaplengthtotal, canopygappcttotal, basalgaplengthtotal, basalgappcttotal, vt.understoryreprodabundance, vt.woodyunderstoryabundance, vt.herbundertoryabundance, vt.lichensunderstoryabundance, cancovpcttotaltrans, cancovtotalclasstrans, cancovassessmethod, vt.crowncanclosurepct, vt.crowncancloseassessmethod, vt.crowncompfactorlpp, vt.crowncomplppavedbh, overstorycancovpcttrans, overstorycancovclasstrans, groundcovassessmethod, groundcovquadratssampled, groundcovpointssampled, groundsurfcovassessmethod, groundsurfcovquadratsamp, groundsurfcovpointssamp, lpiobsinterval, totalpointssampledcount, topcanopyhtave, topcanopyhtstddev, totalnumplantsbelt, totalnumspeciesbelt, totalplantdensitybelt
   FROM
   site_View_1 AS s
   INNER JOIN siteobs_View_1 AS so ON so.siteiidref=s.siteiid
@@ -207,42 +186,37 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   LEFT JOIN vegtransect_View_1 AS vt ON vt.vegplotiidref=v.vegplotiid
   ORDER BY s.siteiid;"
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegtransect <- RODBC::sqlQuery(channel, q.vegtransect, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vegtransect <- gsub(pattern = '_View_1', replacement = '', x = q.vegtransect, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.vegtransect)
+  # exec query
+  d.vegtransect <- dbQueryNASIS(channel, q.vegtransect)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots transects in your selected set!')
+  if (nrow(d.vegtransect) == 0) {
+    stop('there are no NASIS vegplots transects in your selected set!', call. = FALSE)
+  }
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegtransect, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
 
-  # get vegplot transect species data
-  get_vegplot_transpecies_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get vegplot transect species data
+get_vegplot_transpecies_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # veg transect species data - many species to one veg transect
-  q.vtps<- "SELECT siteiid, vegtransectiidref as vegtransect_id, vegplotid, vegplotname, obsdate, vegtransplantsummiid as vtpsiid, vtps.seqnum, plantsym, plantsciname, plantnatvernm, plantnativity, planttypegroup, plantheightcllowerlimit, plantheightclupperlimit, sociabilityclass, specieslivecanhtbotave, specieslivecanhttopave, overstorydbhmin, overstorydbhmax, speciesovercancovpct, speciesovercancovclass, plantprodquadratsize, plantprodquadratshape, nestedfreqquadratsize, nestedfreqquadratshape, frequencyquadratsize, frequencyquadratshape, dwrquadratsize, dwrquadratshape, densityquadratsize, densityquadratshape, speciestotwtclippedest, speciestotwtclippedfresh, speciestotwtclippedairdry, speciestotwtairdry, speciestotwtest, speciestotwtexisting, speciesdrywtpct, speciestotwt, speciesaveyielddblsamp, speciescomppctdblsamp, speciescomppctdaubenmire, speciescomppctlineintercept, speciestraceamtflag, weightconvfactor, dblsampcorrectionfactor, airdrywtadjustment, utilizationadjustment, growthadjustment, weatheradjustment, numberofquadratsin, speciesfreqdaubenmire, dwronetally, dwrtwotally, dwrthreetally, dwrweightedtally, speciescomppctdwr, speciesaveyielddwr, wtunitweight, wtunitcounttotal, speciesaveyieldwtunit, wtunitwtclippedtotal, speciescancovhitcount, speciescancovpct, speciescancovpctavedaub, speciescancovaveclass, speciesfoliarcovhitcount, speciesfoliarcovpctlineint, speciestotfoliarcovlineint, speciesbasalcovhitcount, speciesbasalcovpctlineint, speciestotbasalcovlineint, maturecounttotal, maturedensityave, maturedensityaveclass, seedlingcounttotal, seedlingdensityave, seedlingdensityaveclass, speciesgroundcovabundclass, speciescancovportion, speciesbasalarea, vtps.basalareaassessmethod
+  q.vtps <- "SELECT siteiid, vegtransectiidref as vegtransect_id, vegplotid, vegplotname, obsdate, vegtransplantsummiid as vtpsiid, vtps.seqnum, plantsym, plantsciname, plantnatvernm, plantnativity, planttypegroup, plantheightcllowerlimit, plantheightclupperlimit, sociabilityclass, specieslivecanhtbotave, specieslivecanhttopave, overstorydbhmin, overstorydbhmax, speciesovercancovpct, speciesovercancovclass, plantprodquadratsize, plantprodquadratshape, nestedfreqquadratsize, nestedfreqquadratshape, frequencyquadratsize, frequencyquadratshape, dwrquadratsize, dwrquadratshape, densityquadratsize, densityquadratshape, speciestotwtclippedest, speciestotwtclippedfresh, speciestotwtclippedairdry, speciestotwtairdry, speciestotwtest, speciestotwtexisting, speciesdrywtpct, speciestotwt, speciesaveyielddblsamp, speciescomppctdblsamp, speciescomppctdaubenmire, speciescomppctlineintercept, speciestraceamtflag, weightconvfactor, dblsampcorrectionfactor, airdrywtadjustment, utilizationadjustment, growthadjustment, weatheradjustment, numberofquadratsin, speciesfreqdaubenmire, dwronetally, dwrtwotally, dwrthreetally, dwrweightedtally, speciescomppctdwr, speciesaveyielddwr, wtunitweight, wtunitcounttotal, speciesaveyieldwtunit, wtunitwtclippedtotal, speciescancovhitcount, speciescancovpct, speciescancovpctavedaub, speciescancovaveclass, speciesfoliarcovhitcount, speciesfoliarcovpctlineint, speciestotfoliarcovlineint, speciesbasalcovhitcount, speciesbasalcovpctlineint, speciestotbasalcovlineint, maturecounttotal, maturedensityave, maturedensityaveclass, seedlingcounttotal, seedlingdensityave, seedlingdensityaveclass, speciesgroundcovabundclass, speciescancovportion, speciesbasalarea, vtps.basalareaassessmethod
   FROM
   site_View_1 AS s
   INNER JOIN siteobs_View_1 AS so ON so.siteiidref=s.siteiid
@@ -252,43 +226,35 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   INNER JOIN plant ON plant.plantiid=vtps.plantiidref
   ORDER BY s.siteiid;"
 
+  channel <- dbConnectNASIS(dsn)
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
-
-  # exec query
-  d.vegtransplantsum <- RODBC::sqlQuery(channel, q.vtps, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vtps <- gsub(pattern = '_View_1', replacement = '', x = q.vtps, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.vegtransplantsum)
+  # exec query
+  d.vegtransplantsum <- dbQueryNASIS(channel, q.vtps)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots transect species in your selected set!')
+  if (nrow(d.vegtransplantsum) == 0)
+    stop('there are no NASIS vegplots transect species in your selected set!')
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegtransplantsum, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
-
-  # get vegplot tree site index summary data
-  get_vegplot_tree_si_summary_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get vegplot tree site index summary data
+get_vegplot_tree_si_summary_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # plot tree site index summary data
-  q.pltsis<- "SELECT vegplotiidref AS vegplotiid, pltsis.seqnum, plantiidref, plantsym, plantsciname, plantnatvernm, plantnativity, siteindexbase, speciestreecount, siteindexplotave, speciesdbhaverage, treeageave, treecanopyhttopave, plottreesiteindsumiid
+  q.pltsis <- "SELECT vegplotiidref AS vegplotiid, pltsis.seqnum, plantiidref, plantsym, plantsciname, plantnatvernm, plantnativity, siteindexbase, speciestreecount, siteindexplotave, speciesdbhaverage, treeageave, treecanopyhttopave, plottreesiteindsumiid
 
   FROM
   site_View_1 AS s
@@ -298,42 +264,36 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   INNER JOIN plant ON plant.plantiid=pltsis.plantiidref
   ORDER BY s.siteiid;"
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegsiteindexsum <- RODBC::sqlQuery(channel, q.pltsis, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.pltsis <- gsub(pattern = '_View_1', replacement = '', x = q.pltsis, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.vegsiteindexsum)
+  # exec query
+  d.vegsiteindexsum <- dbQueryNASIS(channel, q.pltsis)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots tree site index data in your selected set!')
+  if (nrow(d.vegsiteindexsum) == 0)
+    stop('there are no NASIS vegplots tree site index data in your selected set!', call. = FALSE)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegsiteindexsum, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
 
-  # get vegplot tree site index details data
-  get_vegplot_tree_si_details_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+# get vegplot tree site index details data
+get_vegplot_tree_si_details_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # plot tree site index detail data
-  q.pltsid<- "SELECT plottreesiteindsumiidref, pltsid.seqnum, plantsym, plantsciname, plantnatvernm, treenumber, crownclass, reproductionsource, treediameterbreastheight, tenyeargrowthradius, growthringcount, growthringcountheight, growthringcountage, treeage, treecanopyhtbottom, treecanopyhttop, plottreesiteinddetailsiid
+  q.pltsid <- "SELECT plottreesiteindsumiidref, pltsid.seqnum, plantsym, plantsciname, plantnatvernm, treenumber, crownclass, reproductionsource, treediameterbreastheight, tenyeargrowthradius, growthringcount, growthringcountheight, growthringcountage, treeage, treecanopyhtbottom, treecanopyhttop, plottreesiteinddetailsiid
 
   FROM
   site_View_1 AS s
@@ -345,72 +305,62 @@ get_vegplot_from_NASIS_db <- function(SS=TRUE, stringsAsFactors = default.string
   INNER JOIN plant ON plant.plantiid=pltsis.plantiidref
   ORDER BY s.siteiid;"
 
+  channel <- dbConnectNASIS(dsn)
 
-  # setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
-
-  # exec query
-  d.vegsiteindexdet <- RODBC::sqlQuery(channel, q.pltsid, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.pltsid <- gsub(pattern = '_View_1', replacement = '', x = q.pltsid, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.vegsiteindexdet)
+  # exec query
+  d.vegsiteindexdet <- dbQueryNASIS(channel, q.pltsid)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots tree site index data in your selected set!')
+  if (nrow(d.vegsiteindexdet) == 0) {
+    stop('there are no NASIS vegplots tree site index data in your selected set!', call. = FALSE)
+  }
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegsiteindexdet, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # done
   return(d)
 }
 
 
-  # get vegplot textnotes
-  get_vegplot_textnote_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE, stringsAsFactors = default.stringsAsFactors()) {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
-
+# get vegplot textnotes
+get_vegplot_textnote_from_NASIS_db <- function(SS=TRUE, fixLineEndings=TRUE, stringsAsFactors = default.stringsAsFactors(), dsn = NULL) {
 
   # vegplot textnotes
   q.vegplottext <- "SELECT vegplotiidref as vegplotiid, seqnum, recdate, recauthor, vegplottextkind,
-textcat, textsubcat, CAST(textentry AS ntext) AS textentry, vegplottextiid
+textcat, textsubcat, vegplottextiid, CAST(textentry AS ntext) AS textentry
 FROM vegplottext_View_1;"
 
-# setup connection local NASIS
-  channel <- RODBC::odbcDriverConnect(connection=getOption('soilDB.NASIS.credentials'))
+  channel <- dbConnectNASIS(dsn)
 
-  # exec query
-  d.vegplottext <- RODBC::sqlQuery(channel, q.vegplottext, stringsAsFactors=FALSE)
+  if (inherits(channel, 'try-error'))
+    return(data.frame())
 
   # toggle selected set vs. local DB
-  if(SS == FALSE) {
-    q <- gsub(pattern = '_View_1', replacement = '', x = q, fixed = TRUE)
+  if (SS == FALSE) {
+    q.vegplottext <- gsub(pattern = '_View_1', replacement = '', x = q.vegplottext, fixed = TRUE)
   }
 
-  # close connection
-  RODBC::odbcClose(channel)
-
-  d <- uncode(d.vegplottext)
+  # exec query
+  d.vegplottext <- dbQueryNASIS(channel, q.vegplottext)
 
   # test for no data
-  if(nrow(d) == 0)
-  stop('there are no NASIS vegplots textnotes in your selected set!')
+  if (nrow(d.vegplottext) == 0)
+   stop('there are no NASIS vegplots textnotes in your selected set!', call. = FALSE)
 
   # uncode metadata domains
-  d <- uncode(d, stringsAsFactors = stringsAsFactors)
+  d <- uncode(d.vegplottext, stringsAsFactors = stringsAsFactors, dsn = dsn)
 
   # optionally convert \r\n -> \n
-  if(fixLineEndings){
+  if (fixLineEndings) {
     d$textentry <- gsub(d$textentry, pattern = '\r\n', replacement = '\n', fixed = TRUE)
   }
 
diff --git a/R/mix_and_clean_colors.R b/R/mix_and_clean_colors.R
index c7f9c14d..6d4dc7bb 100644
--- a/R/mix_and_clean_colors.R
+++ b/R/mix_and_clean_colors.R
@@ -5,30 +5,41 @@
 
 # x: data.frame, typically from NASIS containing at least 'r', 'g', 'b' colors {0,1} and some kind of weight
 # wt: fractional weights, usually area of hz face
+#' Mix and Clean Colors
+#'
+#' Deprecated: only used in PedonPC functionality; use `estimateColorMixture` instead
+#'
+#' @param x a \code{data.frame} object containing sRGB coordinates (`'r'`, `'g'`, `'b'`) in \[0,1]
+#' @param wt fractional weights, usually area of hz face
+#' @param backTransform logical, should the mixed sRGB representation of soil
+#' color be transformed to closest Munsell chips? This is performed by
+#'
+#' @return A data.frame containing mixed colors
+#' @export
 mix_and_clean_colors <- function(x, wt='pct', backTransform=FALSE) {
-  
+
   ## TODO finish this
   .Deprecated('estimateColorMixture', msg = '')
-  
+
   # sanity check: no NA
   if(any(c(is.na(x$r), is.na(x$g), is.na(x$b))))
     return(data.frame(r=NA, g=NA, b=NA, colorhue=NA, colorvalue=NA, colorchroma=NA, sigma=NA))
-  
+
   # attempt to fill missing weights
   missing.wts <- is.na(x[[wt]])
   if(any(missing.wts)) {
     # estimated weight is the mean of all other non-NA weights
     est.wt <- mean(x[[wt]], na.rm = TRUE)
-    
+
     # if there are no weights, then all colors are equally weighted
     if(is.na(est.wt)) {
       est.wt <- 1
     }
-    
+
     # fill missing weights
     x[[wt]][which(missing.wts)] <- est.wt
   }
-  
+
   ## 2020-01-22 DEB: mixing always in CIELAB, roughly linear in terms of avg. human perception of color
   ## simulate mixture via weighted average
   # convert sRGB -> LAB
@@ -37,23 +48,23 @@ mix_and_clean_colors <- function(x, wt='pct', backTransform=FALSE) {
   )
   # simpler names
   names(lab.cols) <- c('L', 'A', 'B')
-  
+
   # copy over weights, typically a percent by area
   lab.cols$pct <- x[[wt]]
-  
+
   # compute weighted mixtures in LAB space
   # 2019-11-04 DEB: dropping Hmisc import
   L <- with(lab.cols, weighted.mean(L, w=pct, na.rm = TRUE))
   A <- with(lab.cols, weighted.mean(A, w=pct, na.rm = TRUE))
   B <- with(lab.cols, weighted.mean(B, w=pct, na.rm = TRUE))
-  
+
   # back to sRGB
   mixed.color <- data.frame(convertColor(cbind(L, A, B), from='Lab', to='sRGB', from.ref.white='D65', to.ref.white = 'D65'))
   names(mixed.color) <- c('r', 'g', 'b')
-  
+
   # optionally back-transform mixture to Munsell
   if(backTransform) {
-    
+
     # convert with best available metric
     m <- rgb2munsell(mixed.color[, c('r', 'g', 'b')])
 
@@ -63,10 +74,10 @@ mix_and_clean_colors <- function(x, wt='pct', backTransform=FALSE) {
     # combine with mixed sRGB coordinates
     mixed.color <- cbind(mixed.color, m)
   }
-  
-  
+
+
   # done
   return(mixed.color)
-  
+
 }
 
diff --git a/R/openNASISchannel.R b/R/openNASISchannel.R
index d3ffc901..dc53c8e1 100644
--- a/R/openNASISchannel.R
+++ b/R/openNASISchannel.R
@@ -1,47 +1,81 @@
-# this function does all the checking to catch different NASIS errors
-#  before being able to safely run RODBC::sqlQuery
-#  **remember to close with RODBC::odbcClose()
+# internal method for opening a connection to local nasis database using credentials
 
-.openNASISchannel <- function() {
-  # must have RODBC installed
-  if(!requireNamespace('RODBC'))
-    stop('please install the `RODBC` package', call.=FALSE)
+.openNASISchannel <- function(dsn = NULL) {
 
-  if(is.null(getOption('soilDB.NASIS.credentials')))
+  use_sqlite <- !is.null(dsn)
+
+  if (is.null(getOption('soilDB.NASIS.credentials')))
     stop("soilDB.NASIS.credentials not set")
 
-  # setup connection local NASIS
-  channel <- suppressWarnings(RODBC::odbcDriverConnect(connection = getOption('soilDB.NASIS.credentials')))
+  if (!use_sqlite) {
+    
+    # assuming that default connection uses ODBC
+    if (!requireNamespace("odbc"))
+      stop("package `odbc` is required", call. = FALSE)
+    
+    # setup connection local NASIS
+    #suppressWarnings(RODBC::odbcDriverConnect(connection = getOption('soilDB.NASIS.credentials')))
+    credentials <- gsub("^.*\\=(.*)","\\1", strsplit(getOption('soilDB.NASIS.credentials'), ";")[[1]])
+    channel <- try(DBI::dbConnect(odbc::odbc(),
+                                  DSN = credentials[1],
+                                  UID = credentials[2],
+                                  PWD = credentials[3]))
+  } else {
+    
+    if (!requireNamespace("RSQLite"))
+      stop("package `RSQLite` is required", call. = FALSE)
+    
+    channel <- try(DBI::dbConnect(RSQLite::SQLite(), dsn))
+  }
 
   # every method that uses .openNASISchannel must handle possibility of
   # not having NASIS db for themselves. most return empty data.frame.
   # hypothetically a more complex empty structure could be returned
-  if  (channel == -1)
-    warning("no local NASIS database available", call.=FALSE)
+  if (inherits(channel, 'try-error')) {
+    warning("no local NASIS database available", call. = FALSE)
+  }
 
   return(channel)
 }
 
-#' Check for presence of `nasis_local` ODBC data source
-#'
+
+
+#' Check for presence of \code{nasis_local} ODBC data source
+#' 
+#' Check for presence of \code{nasis_local} ODBC data source
+#' 
+#' 
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: NULL
 #' @return logical
-#' @export local_NASIS_defined
-#'
 #' @examples
-#'
+#' 
+#' 
 #' if(local_NASIS_defined()) {
 #'   # use fetchNASIS or some other lower-level fetch function
 #' } else {
 #'   message('could not find `nasis_local` ODBC data source')
 #' }
-#'
-local_NASIS_defined <- function() {
-  # check for user-defined
-  if(!requireNamespace("RODBC"))
-    stop("package `RODBC` is required", call. = FALSE)
-  if('nasis_local' %in% names(RODBC::odbcDataSources())) {
-    return(TRUE)
+#' 
+#' @export local_NASIS_defined
+local_NASIS_defined <- function(dsn = NULL) {
+  
+  if (is.null(dsn)) {
+    
+    # assuming that default connection uses ODBC
+    if (!requireNamespace("odbc"))
+      stop("package `odbc` is required ", call. = FALSE)
+    
+    if ('nasis_local' %in% odbc::odbcListDataSources()$name) {
+      return(TRUE)
+    } else {
+      return(FALSE)
+    }
   } else {
-    return(FALSE)
+    
+    if (!requireNamespace("RSQLite"))
+      stop("package `RSQLite` is required", call. = FALSE)
+    
+    return(RSQLite::dbCanConnect(RSQLite::SQLite(), dsn))
   }
 }
diff --git a/R/parseWebReport.R b/R/parseWebReport.R
index 8856224f..f47e925c 100644
--- a/R/parseWebReport.R
+++ b/R/parseWebReport.R
@@ -1,4 +1,3 @@
-
 ## parallel requests?
 # https://cran.r-project.org/web/packages/curl/vignettes/intro.html#async_requests
 
@@ -17,6 +16,32 @@
 # examples:
 # url = 'https://nasis.sc.egov.usda.gov/NasisReportsWebSite/limsreport.aspx?report_name=WEB-PROJECT_MUKEY_BY_GOAL_YEAR'
 # args = list(msso='2-MIN', fy='2018', asym='%', proj='0')
+
+
+#' Parse contents of a web report, based on supplied arguments.
+#' 
+#' Parse contents of a web report, based on supplied arguments.
+#' 
+#' Report argument names can be inferred by inspection of the HTML source
+#' associated with any given web report.
+#' 
+#' @param url Base URL to a LIMS/NASIS web report.
+#' @param args List of named arguments to send to report, see details.
+#' @param index Integer index specifying the table to return, or, NULL for a
+#' list of tables
+#' @return A \code{data.frame} object in the case of a single integer passed to
+#' \code{index}, a \code{list} object in the case of an integer vector or NULL
+#' passed to \code{index}.
+#' @note Most web reports are for internal use only.
+#' @author D.E. Beaudette and S.M. Roecker
+#' @keywords IO
+#' @examples
+#' 
+#' \donttest{
+#' # pending
+#' }
+#' 
+#' @export parseWebReport
 parseWebReport <- function(url, args, index=1) {
   
   # sanity check: package requirements
diff --git a/R/siblings.R b/R/siblings.R
index dc8e4733..d8ec347f 100644
--- a/R/siblings.R
+++ b/R/siblings.R
@@ -1,9 +1,57 @@
-
 # 2018-11-14
 ## TODO: launder series names, all upper case?
 # return information on soil series that co-occur with `s`
 # component.data: should the component names, kind, percent, etc. be returned as well?
 # cousins: return siblings of siblings (cousins)?
+
+
+#' Lookup siblings and cousins for a given soil series.
+#' 
+#' Lookup siblings and cousins for a given soil series, from the current fiscal
+#' year SSURGO snapshot via SoilWeb.
+#' 
+#' The siblings of any given soil series are defined as those soil series
+#' (major and minor component) that share a parent map unit with the named
+#' series (as a major component). Cousins are siblings of siblings. Data are
+#' sourced from SoilWeb which maintains a copy of the current SSURGO snapshot.
+#' 
+#' @param s character vector, the name of a single soil series,
+#' case-insensitive.
+#' @param only.major logical, should only return siblings that are major
+#' components
+#' @param component.data logical, should component data for siblings (and
+#' optionally cousins) be returned?
+#' @param cousins logical, should siblings-of-siblings (cousins) be returned?
+#' @return \describe{ \item{sib}{\code{data.frame} containing siblings, major
+#' component flag, and number of co-occurrences}
+#' \item{sib.data}{\code{data.frame} containing sibling component data}
+#' \item{cousins}{\code{data.frame} containing cousins, major component flag,
+#' and number of co-occurrences} \item{cousin.data}{\code{data.frame}
+#' containing cousin component data} }
+#' @author D.E. Beaudette
+#' @seealso \link{OSDquery}, \link{siblings}, \link{fetchOSD}
+#' @references
+#'  - [Soil Series Query Functions](http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html)
+#'  - [Soil "Siblings" Tutorial](http://ncss-tech.github.io/AQP/soilDB/siblings.html)
+#' @keywords manip
+#' @examples
+#' 
+#' \donttest{
+#' if(requireNamespace("curl") &
+#'     curl::has_internet()) {
+#'     
+#'     # basic usage
+#'     x <- siblings('zook')
+#'     x$sib
+#'     
+#'     # restrict to siblings that are major components
+#'     # e.g. the most likely siblings
+#'     x <- siblings('zook', only.major = TRUE)
+#'     x$sib
+#' }
+#' }
+#' 
+#' @export siblings
 siblings <- function(s, only.major=FALSE, component.data=FALSE, cousins=FALSE) {
   
   # helper functions
diff --git a/R/simplfyFragmentData.R b/R/simplfyFragmentData.R
index 10e8ba63..4c3f5d42 100644
--- a/R/simplfyFragmentData.R
+++ b/R/simplfyFragmentData.R
@@ -1,4 +1,3 @@
-
 ## TODO: generalize, export, and make sieve sizes into an argument
 
 # latest NSSH part 618
@@ -128,6 +127,36 @@
 # rf: un-coded contents of the phfrags table
 # id.var: id column name
 # nullFragsAreZero: convert NA to 0?
+
+
+#' Simplify Coarse Fraction Data
+#' 
+#' Simplify multiple coarse fraction (>2mm) records by horizon.
+#' 
+#' This function is mainly intended for the processing of NASIS pedon/horizon
+#' data which contains multiple coarse fragment descriptions per horizon.
+#' \code{simplifyFragmentData} will "sieve out" coarse fragments into the USDA
+#' classes, split into hard and para- fragments.
+#' 
+#' The \code{simplifyFragmentData} function can be applied to data sources
+#' other than NASIS by careful use of the \code{id.var} argument. However,
+#' \code{rf} must contain coarse fragment volumes in the column "fragvol",
+#' fragment size (mm) in columns "fragsize_l", "fragsize_r", "fragsize_h", and
+#' fragment cementation class in "fraghard".
+#' 
+#' Examples: 
+#'  - [KSSL data](http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html)
+#' 
+#' @aliases simplifyFragmentData simplfyFragmentData simplifyArtifactData
+#' @param rf a \code{data.frame} object, typically returned from NASIS, see
+#' details
+#' @param id.var character vector with the name of the column containing an ID
+#' that is unique among all horizons in \code{rf}
+#' @param nullFragsAreZero should fragment volumes of NULL be interpreted as 0?
+#' (default: TRUE), see details
+#' @author D.E. Beaudette
+#' @keywords manip
+#' @export simplifyFragmentData
 simplifyFragmentData <- function(rf, id.var, nullFragsAreZero=TRUE) {
   
   # nasty hack to trick R CMD check
diff --git a/R/simplifyColorData.R b/R/simplifyColorData.R
index 49542e8f..0603212e 100644
--- a/R/simplifyColorData.R
+++ b/R/simplifyColorData.R
@@ -9,6 +9,51 @@
 # This function is heavily biased towared NASIS-specific data structures and assumptions
 # d: data.frame with color data from horizon-color table: expects "colorhue", "colorvalue", "colorchroma"
 # id.var: name of the column with unique horizon IDs
+
+
+#' Simplify Color Data by ID
+#' 
+#' Simplify multiple Munsell color observations associated with each horizon.
+#' 
+#' This function is mainly intended for the processing of NASIS pedon/horizon
+#' data which may or may not contain multiple colors per horizon/moisture
+#' status combination. \code{simplifyColorData} will "mix" multiple colors
+#' associated with horizons in \code{d}, according to IDs specified by
+#' \code{id.var}, using "weights" (area percentages) specified by the \code{wt}
+#' argument to \code{mix_and_clean_colors}.
+#' 
+#' Note that this function doesn't actually simulate the mixture of pigments on
+#' a surface, rather, "mixing" is approximated via weighted average in the
+#' CIELAB colorspace.
+#' 
+#' The \code{simplifyColorData} function can be applied to data sources other
+#' than NASIS by careful use of the \code{id.var} and \code{wt} arguments.
+#' However, \code{d} must contain Munsell colors split into columns named
+#' "colorhue", "colorvalue", and "colorchroma". In addition, the moisture state
+#' ("Dry" or "Moist") must be specified in a column named "colormoistst".
+#' 
+#' The \code{mix_and_clean_colors} function can be applied to arbitrary data
+#' sources as long as \code{x} contains sRGB coordinates in columns named "r",
+#' "g", and "b". This function should be applied to chunks of rows within which
+#' color mixtures make sense.
+#' 
+#' Examples: 
+#'  - [KSSL data](http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html)
+#'  - [soil color mixing tutorial](http://ncss-tech.github.io/AQP/soilDB/mixing-soil-color-data.html)
+#' 
+#' @param d a \code{data.frame} object, typically returned from NASIS, see
+#' details
+#' @param id.var character vector with the name of the column containing an ID
+#' that is unique among all horizons in \code{d}
+#' @param wt a character vector with the name of the column containing color
+#' weights for mixing
+#' @param bt logical, should the mixed sRGB representation of soil color be
+#' transformed to closest Munsell chips? This is performed by
+#' \code{aqp::rgb2Munsell}
+#' \code{aqp::rgb2Munsell}
+#' @author D.E. Beaudette
+#' @keywords manip
+#' @export simplifyColorData
 simplifyColorData <- function(d, id.var='phiid', wt='colorpct', bt=FALSE) {
   
   # sanity check: must contain 1 row
@@ -46,7 +91,7 @@ simplifyColorData <- function(d, id.var='phiid', wt='colorpct', bt=FALSE) {
   mix.vars <- c(wt, 'L', 'A', 'B')
   
   # mix/combine if there are any horizons that need mixing
-  if(length(dry.to.mix) > 0) {
+  if (length(dry.to.mix) > 0) {
     message(paste('mixing dry colors ... [', length(dry.to.mix), ' of ', nrow(dry.colors), ' horizons]', sep=''))
     
     # filter out and mix only colors with >1 color / horizon
@@ -75,14 +120,13 @@ simplifyColorData <- function(d, id.var='phiid', wt='colorpct', bt=FALSE) {
     # combine original[-horizons to be mixed] + mixed horizons
     dry.colors.final <- rbind(dry.colors[-dry.mix.idx, vars.to.keep], mixed.dry)
     names(dry.colors.final) <- c(id.var, 'd_r', 'd_g', 'd_b', 'd_hue', 'd_value', 'd_chroma', 'd_sigma')
-  }
-  else {# otherwise subset the columns only
+  } else {# otherwise subset the columns only
     dry.colors.final <- dry.colors[, vars.to.keep]
     names(dry.colors.final) <- c(id.var, 'd_r', 'd_g', 'd_b', 'd_hue', 'd_value', 'd_chroma', 'd_sigma')
   }
   
   # mix/combine if there are any horizons that need mixing
-  if(length(moist.to.mix) > 0) {
+  if (length(moist.to.mix) > 0) {
     message(paste('mixing moist colors ... [', length(moist.to.mix), ' of ', nrow(moist.colors), ' horizons]', sep=''))
     
     # filter out and mix only colors with >1 color / horizon
@@ -92,7 +136,7 @@ simplifyColorData <- function(d, id.var='phiid', wt='colorpct', bt=FALSE) {
     # note: split will re-order IDs
     mc <- split(moist.colors[moist.mix.idx, mix.vars], f = moist.colors[[id.var]][moist.mix.idx])
     
-    # final vesion
+    # final version
     mixed.moist <- lapply(mc, estimateColorMixture, wt = wt, backTransform = bt)
     
     # flatten and copy id.var from rownames
@@ -111,25 +155,30 @@ simplifyColorData <- function(d, id.var='phiid', wt='colorpct', bt=FALSE) {
     # combine original[-horizons to be mixed] + mixed horizons
     moist.colors.final <- rbind(moist.colors[-moist.mix.idx, vars.to.keep], mixed.moist)
     names(moist.colors.final) <- c(id.var, 'm_r', 'm_g', 'm_b', 'm_hue', 'm_value', 'm_chroma', 'm_sigma')
-  }
-  else {# otherwise subset the columns only
+  } else {# otherwise subset the columns only
     moist.colors.final <- moist.colors[, vars.to.keep]
     names(moist.colors.final) <- c(id.var, 'm_r', 'm_g', 'm_b', 'm_hue', 'm_value', 'm_chroma', 'm_sigma')
   }
   
   # FULL JOIN dry + moist colors
-  d.final <- merge(dry.colors.final, moist.colors.final, by=id.var, all.x=TRUE, all.y=TRUE, sort=FALSE)
+  d.final <- merge(dry.colors.final, moist.colors.final, by = id.var, 
+                   all.x = TRUE, all.y = TRUE, sort = FALSE)
   
   # make HEX colors
   # safely account for NA, rgb() will not accept NA input
-  d.final$moist_soil_color <- NA
-  idx <- complete.cases(d.final$m_r)
-  d.final$moist_soil_color[idx] <- with(d.final[idx, ], rgb(m_r, m_g, m_b))
-  
-  d.final$dry_soil_color <- NA
-  idx <- complete.cases(d.final$d_r)
-  d.final$dry_soil_color[idx] <- with(d.final[idx, ], rgb(d_r, d_g, d_b))
-  
+  if (nrow(d.final) > 0) {
+    d.final$moist_soil_color <- NA
+    idx <- complete.cases(d.final$m_r)
+    d.final$moist_soil_color[idx] <- with(d.final[idx, ], rgb(m_r, m_g, m_b))
+    
+    d.final$dry_soil_color <- NA
+    idx <- complete.cases(d.final$d_r)
+    d.final$dry_soil_color[idx] <- with(d.final[idx, ], rgb(d_r, d_g, d_b))
+  } else {
+    # this happens if all moisture states are NA
+    d.final$moist_soil_color <- character(0)
+    d.final$dry_soil_color <- character(0)
+  }
   return(d.final)
 }
 
diff --git a/R/soilDB-package.R b/R/soilDB-package.R
new file mode 100644
index 00000000..265499ad
--- /dev/null
+++ b/R/soilDB-package.R
@@ -0,0 +1,123 @@
+#' Soil Database Interface
+#' 
+#' This package provides methods for extracting soils information from local
+#' PedonPC and AK Site databases (MS Access format), local NASIS databases (MS
+#' SQL Server), and the SDA web service. Currently USDA-NCSS data sources are
+#' supported, however, there are plans to develop interfaces to outside systems
+#' such as the Global Soil Mapping project.
+#'  
+#' @name soilDB-package
+#' @aliases soilDB.env soilDB-package soilDB
+#' @docType package
+#' @author J.M. Skovlin, D.E. Beaudette, S.M Roecker, A.G. Brown
+#' @seealso \code{\link{fetchPedonPC}, \link{fetchNASIS}, \link{SDA_query}, \link{loafercreek}}
+#' @keywords package
+NULL
+
+#' Example \code{SoilProfilecollection} Objects Returned by \code{fetchNASIS}.
+#' 
+#' Several examples of soil profile collections returned by
+#' \code{fetchNASIS(from='pedons')} as \code{SoilProfileCollection} objects.
+#' 
+#' 
+#' @name loafercreek
+#' @aliases loafercreek gopheridge mineralKing
+#' @docType data
+#' @keywords datasets
+#' @examples
+#' 
+#' \donttest{
+#' if(require("aqp")) {
+#' # load example dataset
+#'   data("gopheridge")
+#'   
+#'   # what kind of object is this?
+#'   class(gopheridge)
+#'   
+#'   # how many profiles?
+#'   length(gopheridge)
+#'   
+#'   # there are 60 profiles, this calls for a split plot
+#'   par(mar=c(0,0,0,0), mfrow=c(2,1))
+#'   
+#'   # plot soil colors
+#'   plot(gopheridge[1:30, ], name='hzname', color='soil_color')
+#'   plot(gopheridge[31:60, ], name='hzname', color='soil_color')
+#'   
+#'   # need a larger top margin for legend
+#'   par(mar=c(0,0,4,0), mfrow=c(2,1))
+#'   # generate colors based on clay content
+#'   plot(gopheridge[1:30, ], name='hzname', color='clay')
+#'   plot(gopheridge[31:60, ], name='hzname', color='clay')
+#'   
+#'   # single row and no labels
+#'   par(mar=c(0,0,0,0), mfrow=c(1,1))
+#'   # plot soils sorted by depth to contact
+#'   plot(gopheridge, name='', print.id=FALSE, plot.order=order(gopheridge$bedrckdepth))
+#'   
+#'   # plot first 10 profiles
+#'   plot(gopheridge[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+#'   
+#'   # add rock fragment data to plot:
+#'   addVolumeFraction(gopheridge[1:10, ], colname='total_frags_pct')
+#'   
+#'   # add diagnostic horizons
+#'   addDiagnosticBracket(gopheridge[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+#'   
+#'   ## loafercreek
+#'   data("loafercreek")
+#'   # plot first 10 profiles
+#'   plot(loafercreek[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+#'   
+#'   # add rock fragment data to plot:
+#'   addVolumeFraction(loafercreek[1:10, ], colname='total_frags_pct')
+#'   
+#'   # add diagnostic horizons
+#'   addDiagnosticBracket(loafercreek[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+#' }
+#' }
+#' 
+NULL
+
+#' SCAN and SNOTEL Station Metadata
+#' 
+#' SCAN and SNOTEL station metadata, a work in progress.
+#' 
+#' These data have been compiled from several sources and represent a
+#' progressive effort to organize SCAN/SNOTEL station metadata. Therefore, some
+#' records may be missing or incorrect. 
+#' 
+#' @name SCAN_SNOTEL_metadata
+#' @aliases SCAN_SNOTEL_metadata state_FIPS_codes
+#' @docType data
+#' @format A data frame with 1092 observations on the following 12 variables.
+#' \describe{ \item{list("Name")}{station name} \item{list("Site")}{station ID}
+#' \item{list("State")}{state} \item{list("Network")}{sensor network: SCAN /
+#' SNOTEL} \item{list("County")}{county} \item{list("Elevation_ft")}{station
+#' elevation in feet} \item{list("Latitude")}{latitude of station}
+#' \item{list("Longitude")}{longitude of station} \item{list("HUC")}{associated
+#' watershed} \item{list("climstanm")}{climate station name (TODO: remove this
+#' column)} \item{list("upedonid")}{associated user pedon ID}
+#' \item{list("pedlabsampnum")}{associated lab sample ID} }
+#' @keywords datasets
+NULL
+
+#' Timeline of US Published Soil Surveys
+#' 
+#' This dataset contains the years of each US Soil Survey was published.
+#' 
+#' This data was web scraped from the NRCS Soils Website. The scraping
+#' procedure and a example plot are included in the examples section below.
+#' 
+#' @name us_ss_timeline
+#' @docType data
+#' 
+#' @format A data.frame with 5209 observations on the following 5 variables.
+#' - `"ssa"`: Soil Survey name, a character vector
+#' - `"year"`: Year of publication, a numeric vector
+#' - `"pdf"`: Does a manuscript PDF document exist? a logical vector
+#' - `"state"`: State abbreviation, a character vector
+#' 
+#' @source https://www.nrcs.usda.gov/wps/portal/nrcs/soilsurvey/soils/survey/state/
+#' @keywords datasets
+NULL
diff --git a/R/uncode.R b/R/uncode.R
index bdc55b3f..f994d4de 100644
--- a/R/uncode.R
+++ b/R/uncode.R
@@ -1,97 +1,166 @@
-uncode <- function(df, 
-                   invert = FALSE, 
-                   db = "NASIS", 
-                   droplevels = FALSE, 
-                   stringsAsFactors = default.stringsAsFactors()
-                   ) {
-  get_metadata <- function() {
-    # must have RODBC installed
-    if(!requireNamespace('RODBC'))
-      stop('please install the `RODBC` package', call.=FALSE)
-    
-    q <- "SELECT mdd.DomainID, DomainName, ChoiceSequence, ChoiceValue, ChoiceName, ChoiceLabel, ChoiceDescription, ColumnPhysicalName, ColumnLogicalName, ChoiceObsolete
-    
-    FROM MetadataDomainDetail mdd
-    INNER JOIN MetadataDomainMaster mdm ON mdm.DomainID = mdd.DomainID
-    INNER JOIN (SELECT MIN(DomainID) DomainID, MIN(ColumnPhysicalName) ColumnPhysicalName, MIN(ColumnLogicalName) ColumnLogicalName FROM MetadataTableColumn GROUP BY DomainID, ColumnPhysicalName) mtc ON mtc.DomainID = mdd.DomainID
-    
-    ORDER BY DomainID, ColumnPhysicalName, ChoiceValue;"
-    
-    # setup connection local NASIS
-    channel <- RODBC::odbcDriverConnect(connection = getOption('soilDB.NASIS.credentials'))
-    
-    # exec query
-    d <- RODBC::sqlQuery(channel, q, stringsAsFactors = FALSE)
-    
-    # close connection
-    RODBC::odbcClose(channel)
-    
-    # done
-    return(d)
-  }
-  
-  # load current metadata table
-  if (db == "NASIS"){
-    metadata <- get_metadata()
-    } else {
-      load(system.file("data/metadata.rda", package="soilDB")[1])
-      }
-  
-  # unique set of possible columns that will need replacement
-  possibleReplacements <- unique(metadata$ColumnPhysicalName)
-  
-  # names of raw data
-  nm <- names(df)
-  # index to columns with codes to be replaced
-  columnsToWorkOn.idx <- which(nm %in% possibleReplacements)
-  
-  # iterate over columns with codes
-  for (i in columnsToWorkOn.idx){
-    
-    # get the current metadata
-    sub <- metadata[metadata$ColumnPhysicalName %in% nm[i], ]
-    
-    # NASIS or LIMS
-    if (db %in% c("NASIS", "LIMS")) {
-      if (invert == FALSE){
-        # replace codes with values
-        df[, i] <- factor(df[, i], levels = sub$ChoiceValue, labels = sub$ChoiceName)
-      } else { 
-        # replace values with codes
-        df[, i] <- factor(df[, i], levels = sub$ChoiceName, labels = sub$ChoiceValue)}
-    }
-    
-    # SDA
-    if (db == "SDA") {
-      if (invert == FALSE){
-        # replace codes with values
-        df[, i] <- factor(df[, i], levels = sub$ChoiceLabel)
-      } else {
-        # replace values with codes
-        df[, i] <- factor(df[, i], levels = sub$ChoiceLabel, labels = sub$ChoiceValue)
-        }
-      }
-    }
-  
-  # drop unused levels
-  if (droplevels == TRUE) {
-    idx <- which(! nm %in% possibleReplacements)
-    df <- droplevels(df, except = idx)
-    }
-  
-  # convert factors to strings
-  if (stringsAsFactors == FALSE) {
-    idx <- unlist(lapply(df, is.factor))
-    df[idx] <- lapply(df[idx], as.character)
-  }
-  
-  return(df)
-}
-
-# convenient, inverted version of uncode()
-code <- function(df, ...) {
-  res <- uncode(df, invert=TRUE, ...)
-  return(res)
-}
-
-
+#' Convert coded values returned from NASIS and SDA queries to factors
+#'
+#' These functions convert the coded values returned from NASIS or SDA to
+#' factors (e.g. 1 = Alfisols) using the metadata tables from NASIS. For SDA
+#' the metadata is pulled from a static snapshot in the soilDB package
+#' (/data/metadata.rda).
+#'
+#' These functions convert the coded values returned from NASIS into their
+#' plain text representation. It duplicates the functionality of the CODELABEL
+#' function found in NASIS. This function is primarily intended to be used
+#' internally by other soilDB R functions, in order to minimizes the need to
+#' manually convert values.
+#'
+#' The function works by iterating through the column names in a data frame and
+#' looking up whether they match any of the ColumnPhysicalNames found in the
+#' metadata domain tables. If matches are found then the columns coded values
+#' are converted to their corresponding factor levels. Therefore it is not
+#' advisable to reuse column names from NASIS unless the contents match the
+#' range of values and format found in NASIS. Otherwise uncode() will convert
+#' their values to NA.
+#'
+#' When data is being imported from NASIS, the metadata tables are sourced
+#' directly from NASIS. When data is being imported from SDA or the NASIS Web
+#' Reports, the metadata is pulled from a static snapshot in the soilDB
+#' package.
+#'
+#' Beware the default is to return the values as factors rather than strings.
+#' While strings are generally preferable, factors make plotting more
+#' convenient. Generally the factor level ordering returned by uncode() follows
+#' the naturally ordering of categories that would be expected (e.g. sand,
+#' silt, clay).
+#'
+#' @aliases metadata uncode code
+#'
+#' @param df data.frame
+#'
+#' @param invert converts the code labels back to their coded values (`FALSE`)
+#'
+#' @param db label specifying the soil database the data is coming from, which
+#' indicates whether or not to query metadata from local NASIS database
+#' ("NASIS") or use soilDB-local snapshot ("LIMS" or "SDA")
+#'
+#' @param droplevels logical: indicating whether to drop unused levels in
+#' classifying factors. This is useful when a class has large number of unused
+#' classes, which can waste space in tables and figures.
+#'
+#' @param stringsAsFactors logical: should character vectors be converted to
+#' factors?
+#'
+#' @param dsn Optional: path to local SQLite database containing NASIS
+#' table structure; default: `NULL`
+#'
+#' @return A data frame with the results.
+#' @author Stephen Roecker
+#' @keywords manip
+#' @examples
+#'
+#' \donttest{
+#' if(requireNamespace("curl") &
+#'     curl::has_internet() &
+#'     require(aqp)) {
+#'   # query component by nationalmusym
+#'   comp <- fetchSDA(WHERE = "nationalmusym = '2vzcp'")
+#'   s <- site(comp)
+#'
+#'   # use SDA uncoding domain via db argument
+#'   s <- uncode(s,  db="SDA")
+#'   levels(s$taxorder)
+#' }
+#' }
+#'
+#' @export uncode
+uncode <- function(df,
+                   invert = FALSE,
+                   db = "NASIS",
+                   droplevels = FALSE,
+                   stringsAsFactors = default.stringsAsFactors(),
+                   dsn = NULL) {
+
+  get_metadata <- function() {
+
+    q <- "SELECT mdd.DomainID, DomainName, ChoiceSequence, ChoiceValue, ChoiceName,
+                 ChoiceLabel, ColumnPhysicalName, ColumnLogicalName, ChoiceObsolete, ChoiceDescription
+          FROM MetadataDomainDetail mdd
+            INNER JOIN MetadataDomainMaster mdm ON mdm.DomainID = mdd.DomainID
+            INNER JOIN (SELECT MIN(DomainID) DomainID, MIN(ColumnPhysicalName) ColumnPhysicalName, MIN(ColumnLogicalName) ColumnLogicalName
+                        FROM MetadataTableColumn GROUP BY DomainID, ColumnPhysicalName) mtc ON mtc.DomainID = mdd.DomainID
+          ORDER BY mdd.DomainID, ColumnPhysicalName, ChoiceValue;"
+
+    channel <- dbConnectNASIS(dsn)
+
+    if (inherits(channel, 'try-error'))
+      return(data.frame())
+
+    # exec query
+    d <- dbQueryNASIS(channel, q)
+
+    # done
+    return(d)
+  }
+
+  # load current metadata table
+  if (db == "NASIS") {
+    metadata <- get_metadata()
+  } else {
+      load(system.file("data/metadata.rda", package = "soilDB")[1])
+  }
+
+  # unique set of possible columns that will need replacement
+  possibleReplacements <- unique(metadata$ColumnPhysicalName)
+
+  # names of raw data
+  nm <- names(df)
+  # index to columns with codes to be replaced
+  columnsToWorkOn.idx <- which(nm %in% possibleReplacements)
+
+  # iterate over columns with codes
+  for (i in columnsToWorkOn.idx){
+
+    # get the current metadata
+    sub <- metadata[metadata$ColumnPhysicalName %in% nm[i], ]
+
+    # NASIS or LIMS
+    if (db %in% c("NASIS", "LIMS")) {
+      if (invert == FALSE){
+        # replace codes with values
+        df[, i] <- factor(df[, i], levels = sub$ChoiceValue, labels = sub$ChoiceName)
+      } else {
+        # replace values with codes
+        df[, i] <- factor(df[, i], levels = sub$ChoiceName, labels = sub$ChoiceValue)}
+    }
+
+    # SDA
+    if (db == "SDA") {
+      if (invert == FALSE){
+        # replace codes with values
+        df[, i] <- factor(df[, i], levels = sub$ChoiceLabel)
+      } else {
+        # replace values with codes
+        df[, i] <- factor(df[, i], levels = sub$ChoiceLabel, labels = sub$ChoiceValue)
+        }
+      }
+    }
+
+  # drop unused levels
+  if (droplevels == TRUE) {
+    idx <- which(! nm %in% possibleReplacements)
+    df <- droplevels(df, except = idx)
+    }
+
+  # convert factors to strings
+  if (stringsAsFactors == FALSE) {
+    idx <- unlist(lapply(df, is.factor))
+    df[idx] <- lapply(df[idx], as.character)
+  }
+
+  return(df)
+}
+
+# convenient, inverted version of uncode()
+code <- function(df, ...) {
+  res <- uncode(df, invert=TRUE, ...)
+  return(res)
+}
+
+
diff --git a/R/utils.R b/R/utils.R
index b9f2bf09..42ba4102 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -1,847 +1,866 @@
-## 
-## misc functions used by soilDB
-##
-
-## TODO: keep track of funky records in the soilDB.env
-
-## TODO: consider toggling paralithic contact to FALSE when lithic contact is TRUE
-# convert diagnostic horizon info into wide-formatted, boolean table
-.diagHzLongtoWide <- function(d, feature = 'featkind', id = 'peiid') {
-	
-	# get unique vector of diagnostic hz
-	d.unique <- na.omit(unique(as.character(d[[feature]])))
-	
-	# init list for storing initial FALSE for each ID / diag kind
-	l <- vector(mode='list')
-	
-	# add unique id
-	l[[id]] <- unique(d[[id]])
-	
-	# make a vector of FALSE, matching the length of unique ID
-	f <- rep(FALSE, times=length(l[[id]]))
-	
-	# iterate over diagnostic hz kind
-	for(i in d.unique) {
-		# fill this list element with FALSE
-		l[[i]] <- f
-		# lookup those ID with this feature
-		matching.id <- d[[id]][which(d[[feature]] == i)]
-		# toggle FALSE-->TRUE for these pedons
-		l[[i]][which(l[[id]] %in% matching.id)] <- TRUE
-	}
-	
-	# convert to DF
-	return(as.data.frame(l))
-		
-}
-
-
-## TODO: this may need some review
-## convert horizon designation pieces info into wide-formatted, boolean table
-.hzSuffixLongtoWide <- function(d) {
-	
-	# get unique vector of all hzdesgnsuffix
-	d.unique <- na.omit(unique(d$desgnsuffix))
-	
-	# init list for storing initial FALSE for each phiid / desgnsuffix
-	l <- vector(mode='list')
-	
-	# add unique phiid
-	l[['phiid']] <- unique(d$phiid)
-	
-	# make a vector of FALSE, matching the length of unique phiid
-	f <- rep(FALSE, times=length(l[['phiid']]))
-	
-	# iterate over hzdesgnsuffix
-	for(i in d.unique) {
-		# fill this list element with FALSE
-		l[[i]] <- f
-		# lookup those phiid with this horizon suffix
-		matching.phiid <- d$phiid[which(d$desgnsuffix == i)]
-		# toggle FALSE-->TRUE for these horizons
-		l[[i]][which(l[['phiid']] %in% matching.phiid)] <- TRUE
-	}
-	
-	# convert to DF
-	return(as.data.frame(l))
-		
-}
-
-
-## TODO: this may need some review
-## try and pick the best possible taxhistory record
-.pickBestTaxHistory <- function(d) {
-	
-	# add a method field
-	d$selection_method <- NA
-	
-	# short-circuit: 1 row
-	if(nrow(d) < 2) {
-	  d$selection_method <- 'single record'
-	  return(d)
-	}
-	
-	## TODO: this must be a POSIXct / Date class object, if not results will be incorrect
-	# try to get the most recent
-	d.order <- order(d$classdate, decreasing=TRUE)
-	
-	# if there are multiple (unique) dates, return the most recent
-	if(length(unique(d$classdate)) > 1) {
-		d$selection_method <- 'most recent'
-		return(d[d.order[1], ])
-	}
-	
-	# otherwise, return the record with the least number of missing cells
-	# if there are the same number of missing cells, the first record is returned
-	n.na <- apply(d, 1, function(i) length(which(is.na(i))))
-	best.record <- which.min(n.na)
-	
-	d$selection_method <- 'least missing data'
-	return(d[best.record, ])
-}
-
-
-## TODO: this may need some review
-## try and pick the best possible ecosite record
-.pickBestEcosite <- function(d) {
-	
-	# add a method field
-	d$es_selection_method <- NA
-	
-	# try to get the most recent:
-	d.order <- order(d$ecositecorrdate, decreasing=TRUE)
-	
-	# if there are multiple (unique) dates, return the most recent
-	if(length(unique(d$ecositecorrdate)) > 1) {
-		d$es_selection_method <- 'most recent'
-		return(d[d.order[1], ])
-	}
-	
-	# otherwise, return the record with the least number of missing cells
-	# if there are the same number of missing cells, the first record is returned
-	n.na <- apply(d, 1, function(i) length(which(is.na(i))))
-	best.record <- which.min(n.na)
-	
-	d$es_selection_method <- 'least missing data'
-	return(d[best.record, ])
-}
-
-## TODO: this may need some review
-## try and pick the best possible ecosite record
-# .pickBestOtherVeg <- function(d) {
-#   
-#   # add a method field
-#   d$es_selection_method <- NA
-#   
-#   # try to get the most recent:
-#   d.order <- order(d$ecositecorrdate, decreasing=TRUE)
-#   
-#   # if there are multiple (unique) dates, return the most recent
-#   if(length(unique(d$ecositecorrdate)) > 1) {
-#     d$es_selection_method <- 'most recent'
-#     return(d[d.order[1], ])
-#   }
-#   
-#   # otherwise, return the record with the least number of missing cells
-#   # if there are the same number of missing cells, the first record is returned
-#   n.na <- apply(d, 1, function(i) length(which(is.na(i))))
-#   best.record <- which.min(n.na)
-#   
-#   d$es_selection_method <- 'least missing data'
-#   return(d[best.record, ])
-# }
-
-## https://github.com/ncss-tech/soilDB/issues/84
-## TODO: https://github.com/ncss-tech/soilDB/issues/47
-## 2015-11-30: short-circuts could use some work, consider pre-marking mistakes in calling function
-# attempt to format "landform" records into a single string
-# note: there are several assumptions made about the data, 
-# see "short-circuits" used when there are funky data
-.formatLandformString <- function(i.gm, name.sep='|') {
-  # get the current 
-  u.peiid <- unique(i.gm$peiid)
-    
-  # sanity check: this function can only be applied to data from a single pedon
-  if(length(u.peiid) > 1)
-    stop('data are from multiple pedon records')
-  
-  # subset geomorph data to landforms
-  i.gm <- i.gm[which(i.gm$geomftname == 'landform'), ]
-  
-  # allow for NA's
-  if(nrow(i.gm) == 0)
-    return(data.frame(peiid=u.peiid, landform_string=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any geomfeatid are NA, then we don't know the order
-  # string together as-is, in row-order
-  if(any(is.na(i.gm$geomfeatid))) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      message(paste0('Using row-order. NA in geomfeatid:', u.peiid))
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # short-circuit: if any feature exists on itself, then use row-order
-  # string together as-is, in row-order
-  if(any(na.omit(c(i.gm$geomfeatid == i.gm$existsonfeat), FALSE))) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      message(paste0('Using row-order. Error in exists-on logic:', u.peiid))
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # get an index to the top-most and bottom-most features
-  # only 1 row should match these criteria
-  top.feature <- which(! i.gm$geomfeatid %in% i.gm$existsonfeat)
-  bottom.feature <- which(! i.gm$existsonfeat %in% i.gm$geomfeatid)
-  
-  # short-circut: incorrect data-population, usually duplicate entries with IDs that make no sense: use row-order
-  if(length(top.feature) == 0 & length(bottom.feature) == 0) {
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Error in exists-on logic: ', u.peiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-   
-  ## short-circuit: only 1 row, and exists-on logic is wrong, use row-order
-  if(nrow(i.gm) == 1 & length(top.feature) == length(bottom.feature)) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Single row / error in exists-on logic: ', u.peiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # short-circuit: if the exists-on logic is wrong, use row-order
-  if(length(top.feature) > 1 | length(bottom.feature) > 1) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.peiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # init a vector to store feature names
-  ft.vect <- vector(mode='character', length=nrow(i.gm))
-  # the first feature is the top-most feature
-  this.feature.idx <- top.feature
-  
-  # loop over features, until the bottom-most feature
-  i <- 1
-  while(i <= nrow(i.gm)){
-    # get the current feature
-    f.i <- i.gm$geomfname[this.feature.idx]
-    
-    if(length(f.i) == 0) {
-      print(this.feature.idx)
-      print(i.gm)
-    }
-      
-    
-    # assign to vector of labels
-    ft.vect[i] <- f.i
-    
-    # jump to the next feature
-    this.feature.idx <- which(i.gm$geomfeatid == i.gm$existsonfeat[this.feature.idx])
-    i <- i + 1
-  }
-  
-  # paste into single string
-  ft.string <- paste(ft.vect, collapse=name.sep)
-  
-  # done!
-  return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
-}
-
-
-## https://github.com/ncss-tech/soilDB/issues/84
-# attempt to flatten site parent material data into 2 strings
-.formatParentMaterialString <- function(i.pm, name.sep='|') {
-  # get the current site
-  u.siteiid <- unique(i.pm$siteiid)
-  
-  # sanity check: this function can only be applied to data from a single site
-  if(length(u.siteiid) > 1)
-    stop('data are from multiple site records')
-  
-  # subset sitepm data to remove any with NA for pm_kind
-  i.pm <- i.pm[which(!is.na(i.pm$pmkind)), ]
-  
-  # if there is no data, then return a DF formatted as if there were data
-  if(nrow(i.pm) == 0)
-    return(data.frame(siteiid=u.siteiid, pmkind=NA, pmorigin=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any pmorder are NA, then we don't know the order
-  # string together as-is, in row-order
-  if(any(is.na(i.pm$pmorder))) {
-    # optional information on which sites have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. NA in pmorder:', u.siteiid), call.=FALSE)
-  } else {
-    # there are no NAs in pmorder --> sort according to pmorder
-    i.pm <- i.pm[order(i.pm$pmorder), ]
-  }
-  
-  # composite strings and return
-  str.kind <- paste(i.pm$pmkind, collapse=name.sep)
-  str.origin <- paste(unique(i.pm$pmorigin), collapse=name.sep)
-  
-  return(data.frame(siteiid=u.siteiid, pmkind=str.kind, pmorigin=str.origin, stringsAsFactors=FALSE))
-}
-
-
-## TODO add .formatcoLandscapeString
-# g <- split(geo, geo$coiid)
-# 
-# gg <- lapply(g, function(i) {
-#   
-#   idx <- which(i$geomftname == 'landscape')
-#   id <- i$coiid[1]
-#   
-#   res <- data.frame(
-#     coiid = id,
-#     geomfname = paste(i$geomfname[idx], collapse = '/'),
-#     stringsAsFactors = FALSE
-#   )
-#   
-#   return(res)
-# })
-
-
-## https://github.com/ncss-tech/soilDB/issues/84
-# 2017-03-13: attempt to format COMPONENT "landform" records into a single string
-# note: there are several assumptions made about the data, 
-# see "short-circuits" used when there are funky data
-.formatcoLandformString <- function(i.gm, name.sep='|') {
-  
-  # hacks to make R CMD check --as-cran happy:
-  u.peiid <- NULL
-  
-  # get the current 
-  u.coiid <- unique(i.gm$coiid)
-  
-  # sanity check: this function can only be applied to data from a single component
-  if(length(u.coiid) > 1)
-    stop('data are from multiple component records')
-  
-  # subset geomorph data to landforms
-  i.gm <- i.gm[which(i.gm$geomftname == 'landform'), ]
-  
-  # allow for NA's
-  if(nrow(i.gm) == 0)
-    return(data.frame(coiid=u.coiid, landform_string=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any geomfeatid are NA, then we don't know the order
-  # string together as-is, in row-order
-  if(any(is.na(i.gm$geomfeatid))) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      message(paste0('Using row-order. NA in geomfeatid: ', u.peiid))
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # short-circuit: if any feature exists on itself, then use row-order
-  # string together as-is, in row-order
-  if(any(na.omit(c(i.gm$geomfeatid == i.gm$existsonfeat), FALSE))) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      message(paste0('Using row-order. Error in exists-on logic: ', u.coiid))
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # get an index to the top-most and bottom-most features
-  # only 1 row should match these criteria
-  top.feature <- which(! i.gm$geomfeatid %in% i.gm$existsonfeat)
-  bottom.feature <- which(! i.gm$existsonfeat %in% i.gm$geomfeatid)
-  
-  ## short-circuit: only 1 row, and exists-on logic is wrong, use row-order
-  if(nrow(i.gm) == 1 & length(top.feature) == length(bottom.feature)) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Single row / error in exists-on logic: ', u.coiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # short-circuit: if the exists-on logic is wrong, use row-order
-  if(length(top.feature) > 1 | length(bottom.feature) > 1) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.coiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # short circuit: if there is circularity in the exists-on logic, use row-order
-  # example coiid: 2119838
-  if(length(top.feature) < 1 | length(bottom.feature) < 1) {
-    
-    # optional information on which pedons have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.coiid), call.=FALSE)
-    
-    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
-    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-  }
-  
-  # init a vector to store feature names
-  ft.vect <- vector(mode='character', length=nrow(i.gm))
-  # the first feature is the top-most feature
-  this.feature.idx <- top.feature
-  
-  # loop over features, until the bottom-most feature
-  i <- 1
-  while(i <= nrow(i.gm)){
-    # get the current feature
-    f.i <- i.gm$geomfname[this.feature.idx]
-    
-    # likely an error condition, print some debugging info
-    if(length(f.i) == 0) {
-      print(this.feature.idx)
-      print(i.gm)
-    }
-    
-    
-    # assign to vector of labels
-    ft.vect[i] <- f.i
-    
-    # jump to the next feature
-    this.feature.idx <- which(i.gm$geomfeatid == i.gm$existsonfeat[this.feature.idx])
-    i <- i + 1
-  }
-  
-  # paste into single string
-  ft.string <- paste(ft.vect, collapse=name.sep)
-  
-  # done!
-  return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
-}
-
-
-## https://github.com/ncss-tech/soilDB/issues/84
-# attempt to flatten component parent material data into 2 strings
-.formatcoParentMaterialString <- function(i.pm, name.sep='|') {
-  # get the current site
-  u.coiid <- unique(i.pm$coiid)
-  
-  # sanity check: this function can only be applied to data from a single site
-  if(length(u.coiid) > 1)
-    stop('data are from multiple site records')
-  
-  # subset sitepm data to remove any with NA for pm_kind
-  i.pm <- i.pm[which(!is.na(i.pm$pmkind)), ]
-  
-  # if there is no data, then return a DF formatted as if there were data
-  if(nrow(i.pm) == 0)
-    return(data.frame(coiid=u.coiid, pmkind=NA, pmorigin=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any pmorder are NA, then we don't know the order
-  # string together as-is, in row-order
-  if(any(is.na(i.pm$pmorder))) {
-    # optional information on which sites have issues
-    if(getOption('soilDB.verbose', default=FALSE))
-      warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
-  }
-  else{
-    # there are no NAs in pmorder --> sort according to pmorder
-    i.pm <- i.pm[order(i.pm$pmorder), ]
-  }
-  
-  # composite strings and return
-  str.kind <- paste(i.pm$pmkind, collapse=name.sep)
-  str.origin <- paste(unique(i.pm$pmorigin), collapse=name.sep)
-  
-  return(data.frame(coiid=u.coiid, pmkind=str.kind, pmorigin=str.origin, stringsAsFactors=FALSE))
-}
-
-
-# attempt to flatten multiple ecosite entries into 1 string
-.formatEcositeString <- function(i.esd, name.sep='|') {
-  # get the current site
-  u.coiid <- unique(i.esd$coiid)
-  
-  # sanity check: this function can only be applied to data from a single component
-  if(length(u.coiid) > 1)
-    stop('data are from multiple component records')
-  
-  # subset othervegcl data to remove any with NA for othervegclass
-  i.esd <- i.esd[which(!is.na(i.esd$ecositeid)), ]
-  
-  # if there is no data, then return a DF formatted as if there were data
-  if(nrow(i.esd) == 0)
-    return(data.frame(coiid=u.coiid, ecosite_id=NA, ecosite_name=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any otherveg are NA, then we don't know the order
-  # string together as-is, in row-order
-  # if(any(is.na(i.ov$pmorder))) {
-  #   # optional information on which sites have issues
-  #   if(getOption('soilDB.verbose', default=FALSE))
-  #     warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
-  # }
-  # else{
-  #   # there are no NAs in pmorder --> sort according to pmorder
-  #   i.ov <- i.ov[order(i.ov$pmorder), ]
-  # }
-  
-  # composite strings and return
-  str.ecoid <- paste(i.esd$ecositeid, collapse=name.sep)
-  str.econm <- paste(unique(i.esd$ecositenm), collapse=name.sep)
-  
-  return(data.frame(coiid=u.coiid, ecosite_id=str.ecoid, ecosite_name=str.econm, stringsAsFactors=FALSE))
-}
-
-
-
-# attempt to flatten multiple other veg class entries into 1 string
-.formatOtherVegString <- function(i.ov, name.sep='|') {
-  # get the current site
-  u.coiid <- unique(i.ov$coiid)
-  
-  # sanity check: this function can only be applied to data from a single component
-  if(length(u.coiid) > 1)
-    stop('data are from multiple component records')
-  
-  # subset othervegcl data to remove any with NA for othervegclass
-  i.ov <- i.ov[which(!is.na(i.ov$ovegclid)), ]
-  
-  # if there is no data, then return a DF formatted as if there were data
-  if(nrow(i.ov) == 0)
-    return(data.frame(coiid=u.coiid, othervegid=NA, othervegclass=NA, stringsAsFactors=FALSE))
-  
-  # short-circuit: if any otherveg are NA, then we don't know the order
-  # string together as-is, in row-order
-  # if(any(is.na(i.ov$pmorder))) {
-  #   # optional information on which sites have issues
-  #   if(getOption('soilDB.verbose', default=FALSE))
-  #     warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
-  # }
-  # else{
-  #   # there are no NAs in pmorder --> sort according to pmorder
-  #   i.ov <- i.ov[order(i.ov$pmorder), ]
-  # }
-  
-  # composite strings and return
-  str.ovegid <- paste(i.ov$ovegclid, collapse=name.sep)
-  str.ovegclnm <- paste(unique(i.ov$ovegclname), collapse=name.sep)
-  
-  return(data.frame(coiid=u.coiid, othervegid=str.ovegid, othervegclass=str.ovegclnm, stringsAsFactors=FALSE))
-}
-
-
-
-
-
-# function to estimate the thickness of horizons and diagnostic features
-.test_thk <- function(x) {
-  names(x) <- gsub("^hz|^feat", "", names(x))
-  std <- with(x, data.frame(
-    l  = depb_l - dept_h,
-    rv = depb_r - dept_r,
-    h  = depb_h - dept_l
-  ))
-  
-  idx <- std < 0
-  idx[is.na(idx)] <- FALSE
-  
-  if (any(idx)) {
-    std_new <- std
-    std_flip <- with(x, data.frame(
-      l = depb_l - dept_l,
-      rv = depb_r - dept_r,
-      h = depb_h - dept_h
-    ))
-    std_new[idx] <- std_flip[idx]
-  }
-  
-  if (exists("std_new")) {
-    idx2 <- which(std_new$l > std$rv)
-    std_new$l[idx2] <- std$r[idx2]
-    idx3 <- which(std$h < std$rv)
-    std_new$h[idx2] <- std$r[idx2]
-  } else std_new <- std
-  
-  return(std_new)
-}
-
-
-
-# impute "not populated" into freqcl and "201" into dept_r & depb_r if !is.na(freqcl)
-.cosoilmoist_prep <- function(df, impute, stringsAsFactors) {
-  
-  # cache original column names
-  orig_names <- names(df)
-  
-  # relabel names
-  names(df) <- gsub("^soimoist", "", names(df))
-  old_names <- "stat"
-  new_names <- "status"
-  names(df)[names(df) %in% old_names] <- new_names
-  
-  
-  # refactoring frequency levels, not sure why these aren't sorted naturally
-  flod_lev <- levels(df$flodfreqcl)
-  pond_lev <- levels(df$pondfreqcl)
-  idx      <- c(1, 5, 2, 7, 3, 4, 6)
-  df <- within(df, {
-    flodfreqcl = levels(flodfreqcl)[as.integer(flodfreqcl)]
-    flodfreqcl = factor(flodfreqcl, levels = flod_lev[idx])
-    pondfreqcl = levels(pondfreqcl)[as.integer(pondfreqcl)]
-    pondfreqcl = factor(pondfreqcl, levels = pond_lev[idx])
-  })
-  
-  
-  # impute NA freqcl values, default = "not populated"
-  if (impute == TRUE) {
-    
-    missing <- "Not populated"
-    lev_flodfreqcl <- c(missing, levels(df$flodfreqcl))
-    lev_pondfreqcl <- c(missing, levels(df$pondfreqcl))
-    lev_status <- c(missing, levels(df$status))
-    
-    df <- within(df, {
-      # replace NULL RV depths with 201 cm if pondfreqcl or flodqcl is not NULL
-      dept_r[is.na(dept_r) & (!is.na(pondfreqcl) | !is.na(flodfreqcl))] = 201
-      depb_r[is.na(depb_r) & (!is.na(pondfreqcl) | !is.na(flodfreqcl))] = 201
-      
-      # replace NULL L and H depths with the RV
-      dept_l = ifelse(is.na(dept_l), dept_r, dept_l)
-      dept_h = ifelse(is.na(dept_h), dept_r, dept_h)
-      
-      depb_l = ifelse(is.na(depb_l), depb_r, depb_l)
-      depb_h = ifelse(is.na(depb_h), depb_r, depb_h)
-      
-      # replace NULL freqcl with "Not_Populated"
-      status = factor(levels(status)[status], levels = lev_status)
-      flodfreqcl = factor(levels(flodfreqcl)[flodfreqcl], levels = lev_flodfreqcl)
-      pondfreqcl = factor(levels(pondfreqcl)[pondfreqcl], levels = lev_flodfreqcl)
-      
-      status[is.na(status)] <- missing
-      flodfreqcl[is.na(flodfreqcl)] <- missing
-      pondfreqcl[is.na(pondfreqcl)] <- missing
-    })
-  }
-  
-  # convert factors to strings
-  idx <- unlist(lapply(df, is.factor))
-  if (stringsAsFactors == FALSE & any(idx)) {
-    df[idx] <- lapply(df[idx], as.character)
-  }
-  
-  return(df)
-}
-
-
-## https://github.com/ncss-tech/soilDB/issues/84
-# Prep of the component parent material
-# flatten multiple records into 1 cokey
-.copm_prep <- function(df, db = NULL) {
-  
-  if (db == "SDA") {
-    
-    # flatten
-    idx <- duplicated(df$cokey)
-    
-    if (any(idx) & nrow(df) > 0) {
-      dups_idx <- df$cokey %in% df[idx, "cokey"]
-      dups     <- df[dups_idx, ]
-      nodups   <- df[!dups_idx, ]
-      
-      # hack to make CRAN check happy
-      pmorigin = NA; pmkind = NA;
-      
-      dups_clean <- {
-        transform(dups, 
-                  idx_pmo = !is.na(pmorigin),
-                  idx_pmk = !is.na(pmkind)
-                  ) ->.;
-        split(., .$cokey, drop = TRUE) ->.
-        lapply(., function(x) { data.frame(
-          x[1, c("cokey", "pmgroupname")],
-          pmkind   = paste(x[x$idx_pmk, "pmkind"  ][order(x[x$idx_pmk, "pmorder"])],   collapse = " over "),
-          pmorigin = paste(x[x$idx_pmo, "pmorigin"][order(x[x$idx_pmo, "pmorder"])], collapse = " over "),
-          stringsAsFactors = FALSE
-          )}) ->.
-        do.call("rbind", .) ->.;
-      }
-      nodups <- nodups[! names(df) %in% c("copmgrpkey", "pmorder")]
-      
-      df <- rbind(nodups, dups_clean)  
-      df <- df[order(df$cokey), ]
-      row.names(df) <- 1:nrow(df)
-      } else df <- df[! names(df) %in% c("copmgrpkey", "pmorder")]
-    
-    
-    # replace "" with NA
-    vars <- c("pmorigin", "pmkind")
-    idx <- unlist(lapply(df, is.character))
-    idx <- names(df) %in% vars & idx
-    df[, idx] <- lapply(df[, idx], function(x) ifelse(x == "", NA, x))
-    }
-  
-  return(df)
-  }
-
-
-## https://github.com/ncss-tech/soilDB/issues/84
-# Prep of the component geomorphic description
-# flatten multiple records into 1 cokey
-.cogmd_prep <- function(df, db = NULL) {
-  
-  # rename LIMS columns and sort comma separated lists
-  if (db == "LIMS") {
-    # rename columns
-    vars <- c("pmkind_grp", "pmorigin_grp", "gc_mntn", "gc_hill", "gc_trce", "gc_flats", "hs_hillslopeprof", "ss_shapeacross", "ss_shapedown")
-    new_names <- c("pmkind", "pmorigin", "mntn", "hill", "trce", "flats", "hillslopeprof", "shapeacross", "shapedown")
-    idx <- which(names(df) %in% vars)
-    names(df)[idx] <- new_names
-    
-    # hack to make CRAN check happy
-    mntn = NULL; hill = NULL; trce = NULL; flats = NULL; hillslopeprof = NULL;
-    
-    ## TODO: consider using is(x, 'character')
-    df <- within(df, {
-      if (class(mntn) == "character") {
-        mntn  = sapply(strsplit(mntn, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
-        }
-      if (class(hill) == "character") {
-        hill  = sapply(strsplit(hill, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
-        }
-      if (class(trce) == "character") {
-        trce  = sapply(strsplit(trce, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
-        }
-      if (class(flats) == "character") {
-        flats = sapply(strsplit(flats, ", "), function(x) paste(sort(unlist(x)), collapse = ", "))
-        }
-      if (class(hillslopeprof) == "character") {
-        hillslopeprof = sapply(strsplit(hillslopeprof, ", "), function(x) paste(sort(unlist(x)), collapse = ", "))
-        }
-      })
-    }
-  
-  
-  # flatten the SDA results to 1 cokey
-  if (db == "SDA") {
-    
-    # flatten
-    idx <- duplicated(df$cokey)
-    
-    if (any(idx) & nrow(df) > 0) {
-      dups_idx <- df$cokey %in% df[idx, "cokey"]
-      dups     <- df[dups_idx, ]
-      nodups   <- df[!dups_idx, ]
-      
-      dups_clean <- {
-        split(dups, dups$cokey, drop = TRUE) ->.
-        lapply(., function(x) { data.frame(
-          cokey = x$cokey[1],
-          landscape     = paste(unique(x$landscape),           collapse = " and "),
-          landform      = paste(unique(x$landform),            collapse = " on  "),
-          mntn          = paste(sort(unique(x$mntn)),          collapse = ", "   ),
-          hill          = paste(sort(unique(x$hill)),          collapse = ", "   ),
-          trce          = paste(sort(unique(x$trce)),          collapse = ", "   ),
-          flats         = paste(sort(unique(x$flats)),         collapse = ", "   ),
-          shapeacross   = paste(sort(unique(x$shapeacross)),   collapse = ", "   ),
-          shapedown     = paste(sort(unique(x$shapedown)),     collapse = ", "   ),
-          hillslopeprof = paste(sort(unique(x$hillslopeprof)), collapse = ", "),
-          stringsAsFactors = TRUE
-        )}) ->.
-        do.call("rbind", .) ->.
-      }
-      nodups <- nodups[! names(nodups) %in% c("geomfeatid", "existsonfeat")]
-      
-      df <- rbind(nodups, dups_clean)  
-      df <- df[order(df$cokey), ]
-      row.names(df) <- 1:nrow(df)
-      } else df <- df[! names(df) %in% c("geomfeatid", "existsonfeat")]
-    }
-  
-  vars <- c("landscape", "landform", "mntn", "hill", "trce", "flats", "hillslopeprof")
-  idx <- unlist(lapply(df, is.character))
-  idx <- names(df) %in% vars & idx
-  df[, idx] <- lapply(df[, idx], function(x) ifelse(x %in% c("", "NA"), NA, x))
-  
-  # hack to make CRAN check happy
-  mntn = NA; hill = NA; trce = NA; flats = NA; shapeacross = NA; shapedown = NA;
-  
-  # combine geompos and shapes
-  if (nrow(df) > 0) {
-    df <- within(df, {
-      geompos = NA
-      geompos = paste(mntn, hill, trce, flats, sep = ", ")
-      geompos = gsub("NA", "", geompos)
-      geompos = gsub("^, |^, , |^, , , |, $|, , $|, , , $", "", geompos)
-      geompos = gsub(", , ", ", ", geompos)
-      geompos[geompos == ""] = NA
-      
-      ssa = NA # slope shape across
-      ssd = NA # slope shape down
-      slopeshape = NA
-
-      ssa = gsub("Concave", "C", shapeacross)
-      ssa = gsub("Linear",  "L", ssa)
-      ssa = gsub("Convex",  "V", ssa)
-
-      ssd = gsub("Concave", "C", shapedown)
-      ssd = gsub("Linear",  "L", ssd)
-      ssd = gsub("Convex",  "V", ssd)
-
-      slopeshape = paste0(ssd, ssa, sep = "")
-      slopeshape[slopeshape %in% c("NANA", "")] = NA
-      })
-    df[c("ssa", "ssd")] <- NULL
-  } else df <- cbind(df, geompos = as.character(NULL))
-  
-  ss_vars <- c("CC", "CV", "CL", "LC", "LL", "LV", "VL", "VC", "VV")
-  if (all(df$slopeshape[!is.na(df$slopeshape)] %in% ss_vars)) {
-    df$slopeshape <- factor(df$slopeshape, levels = ss_vars)
-    df$slopeshape <- droplevels(df$slopeshape)
-  }
-  
-  hs_vars <- c("Toeslope", "Footslope", "Backslope", "Shoulder", "Summit")
-  if (all(df$hillslopeprof[!is.na(df$hillslopeprof)] %in% hs_vars)) {
-    df$hillslopeprof <- factor(df$hillslopeprof, levels = hs_vars)
-    df$hillslopeprof <- droplevels(df$hillslopeprof)
-  }
-  
-  hill_vars <- c("Base Slope", "Head Slope", "Side Slope", "Free Face", "Nose Slope", "Crest", "Interfluve")
-  if (all(df$hill[!is.na(df$hill)] %in% hill_vars)) {
-    df$hill <- factor(df$hill, levels = hill_vars)
-    df$hill <- droplevels(df$hill)
-  }
-  
-  flats_vars <- c("Dip", "Talf", "Rise")
-  if (all(df$flats[!is.na(df$flats)] %in% flats_vars)) {
-    df$flats <- factor(df$flats, levels = flats_vars)
-    df$flats <- droplevels(df$flats)
-  }
-  
-  trce_vars <- c("Tread", "Riser")
-  if (all(df$trce[!is.na(df$trce)] %in% trce_vars)) {
-    df$trce <- factor(df$trce, levels = trce_vars)
-    df$trce <- droplevels(df$trce)
-  }
-
-  return(df)
-  }
+## 
+## misc functions used by soilDB
+##
+
+## TODO: keep track of funky records in the soilDB.env
+
+## TODO: consider toggling paralithic contact to FALSE when lithic contact is TRUE
+# convert diagnostic horizon info into wide-formatted, boolean table
+.diagHzLongtoWide <- function(d, feature = 'featkind', id = 'peiid') {
+	
+	# get unique vector of diagnostic hz
+	d.unique <- na.omit(unique(as.character(d[[feature]])))
+	
+	# init list for storing initial FALSE for each ID / diag kind
+	l <- vector(mode='list')
+	
+	# add unique id
+	l[[id]] <- unique(d[[id]])
+	
+	# make a vector of FALSE, matching the length of unique ID
+	f <- rep(FALSE, times=length(l[[id]]))
+	
+	# iterate over diagnostic hz kind
+	for(i in d.unique) {
+		# fill this list element with FALSE
+		l[[i]] <- f
+		# lookup those ID with this feature
+		matching.id <- d[[id]][which(d[[feature]] == i)]
+		# toggle FALSE-->TRUE for these pedons
+		l[[i]][which(l[[id]] %in% matching.id)] <- TRUE
+	}
+	
+	# convert to DF
+	return(as.data.frame(l))
+		
+}
+
+
+## TODO: this may need some review
+## convert horizon designation pieces info into wide-formatted, boolean table
+.hzSuffixLongtoWide <- function(d) {
+	
+	# get unique vector of all hzdesgnsuffix
+	d.unique <- na.omit(unique(d$desgnsuffix))
+	
+	# init list for storing initial FALSE for each phiid / desgnsuffix
+	l <- vector(mode='list')
+	
+	# add unique phiid
+	l[['phiid']] <- unique(d$phiid)
+	
+	# make a vector of FALSE, matching the length of unique phiid
+	f <- rep(FALSE, times=length(l[['phiid']]))
+	
+	# iterate over hzdesgnsuffix
+	for(i in d.unique) {
+		# fill this list element with FALSE
+		l[[i]] <- f
+		# lookup those phiid with this horizon suffix
+		matching.phiid <- d$phiid[which(d$desgnsuffix == i)]
+		# toggle FALSE-->TRUE for these horizons
+		l[[i]][which(l[['phiid']] %in% matching.phiid)] <- TRUE
+	}
+	
+	# convert to DF
+	return(as.data.frame(l))
+		
+}
+
+
+## TODO: this may need some review
+## try and pick the best possible taxhistory record
+.pickBestTaxHistory <- function(d) {
+	
+	# add a method field (a character)
+	d$selection_method <- NA_character_
+	
+	# short-circuit: 1 row
+	if(nrow(d) < 2) {
+	  d$selection_method <- 'single record'
+	  return(d)
+	}
+	
+	## TODO: this must be a POSIXct / Date class object, if not results will be incorrect
+	# try to get the most recent
+	d.order <- order(d$classdate, decreasing=TRUE)
+	
+	# if there are multiple (unique) dates, return the most recent
+	if(length(unique(d$classdate)) > 1) {
+		d$selection_method <- 'most recent'
+		return(d[d.order[1], ])
+	}
+	
+	# otherwise, return the record with the least number of missing cells
+	# if there are the same number of missing cells, the first record is returned
+	n.na <- apply(d, 1, function(i) length(which(is.na(i))))
+	best.record <- which.min(n.na)
+	
+	d$selection_method <- 'least missing data'
+	return(d[best.record, ])
+}
+
+
+## TODO: this may need some review
+## try and pick the best possible ecosite record
+.pickBestEcosite <- function(d) {
+	
+	# add a method field
+	d$es_selection_method <- NA_character_
+	
+	# try to get the most recent:
+	d.order <- order(d$ecositecorrdate, decreasing=TRUE)
+	
+	# if there are multiple (unique) dates, return the most recent
+	if(length(unique(d$ecositecorrdate)) > 1) {
+		d$es_selection_method <- 'most recent'
+		return(d[d.order[1], ])
+	}
+	
+	# otherwise, return the record with the least number of missing cells
+	# if there are the same number of missing cells, the first record is returned
+	n.na <- apply(d, 1, function(i) length(which(is.na(i))))
+	best.record <- which.min(n.na)
+	
+	d$es_selection_method <- 'least missing data'
+	return(d[best.record, ])
+}
+
+## TODO: this may need some review
+## try and pick the best possible ecosite record
+# .pickBestOtherVeg <- function(d) {
+#   
+#   # add a method field
+#   d$es_selection_method <- NA
+#   
+#   # try to get the most recent:
+#   d.order <- order(d$ecositecorrdate, decreasing=TRUE)
+#   
+#   # if there are multiple (unique) dates, return the most recent
+#   if(length(unique(d$ecositecorrdate)) > 1) {
+#     d$es_selection_method <- 'most recent'
+#     return(d[d.order[1], ])
+#   }
+#   
+#   # otherwise, return the record with the least number of missing cells
+#   # if there are the same number of missing cells, the first record is returned
+#   n.na <- apply(d, 1, function(i) length(which(is.na(i))))
+#   best.record <- which.min(n.na)
+#   
+#   d$es_selection_method <- 'least missing data'
+#   return(d[best.record, ])
+# }
+
+## https://github.com/ncss-tech/soilDB/issues/84
+## TODO: https://github.com/ncss-tech/soilDB/issues/47
+## 2015-11-30: short-circuts could use some work, consider pre-marking mistakes in calling function
+# attempt to format "landform" records into a single string
+# note: there are several assumptions made about the data, 
+# see "short-circuits" used when there are funky data
+.formatLandformString <- function(i.gm, uid = NULL, name.sep='|') {
+
+  # get the current group of rows by unique ID (either passed by caller or calculated)
+  if (is.null(uid))
+    u.peiid <- unique(i.gm$peiid)
+  else 
+    u.peiid <- uid
+  
+  if (is.null(u.peiid))
+    
+  # sanity check: this function can only be applied to data from a single pedon
+  if (length(u.peiid) > 1)
+    stop('data are from multiple pedon records')
+  
+  # subset geomorph data to landforms
+  i.gm <- i.gm[which(i.gm$geomftname == 'landform'), ]
+  
+  # allow for NA's
+  if (nrow(i.gm) == 0) {
+    if(is.null(uid))
+      return(NULL)
+    return(data.frame(peiid = uid,
+                      landform_string = NA_character_,
+                      stringsAsFactors = FALSE))
+  }
+
+  # short-circuit: if any geomfeatid are NA, then we don't know the order
+  # string together as-is, in row-order
+  if(any(is.na(i.gm$geomfeatid))) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      message(paste0('Using row-order. NA in geomfeatid:', u.peiid))
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # short-circuit: if any feature exists on itself, then use row-order
+  # string together as-is, in row-order
+  if(any(na.omit(c(i.gm$geomfeatid == i.gm$existsonfeat), FALSE))) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      message(paste0('Using row-order. Error in exists-on logic:', u.peiid))
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # get an index to the top-most and bottom-most features
+  # only 1 row should match these criteria
+  top.feature <- which(! i.gm$geomfeatid %in% i.gm$existsonfeat)
+  bottom.feature <- which(! i.gm$existsonfeat %in% i.gm$geomfeatid)
+  
+  # short-circut: incorrect data-population, usually duplicate entries with IDs that make no sense: use row-order
+  if(length(top.feature) == 0 & length(bottom.feature) == 0) {
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Error in exists-on logic: ', u.peiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+   
+  ## short-circuit: only 1 row, and exists-on logic is wrong, use row-order
+  if(nrow(i.gm) == 1 & length(top.feature) == length(bottom.feature)) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Single row / error in exists-on logic: ', u.peiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # short-circuit: if the exists-on logic is wrong, use row-order
+  if(length(top.feature) > 1 | length(bottom.feature) > 1) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.peiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # init a vector to store feature names
+  ft.vect <- vector(mode='character', length=nrow(i.gm))
+  # the first feature is the top-most feature
+  this.feature.idx <- top.feature
+  
+  # loop over features, until the bottom-most feature
+  i <- 1
+  while(i <= nrow(i.gm)){
+    # get the current feature
+    f.i <- i.gm$geomfname[this.feature.idx]
+    
+    if(length(f.i) == 0) {
+      print(this.feature.idx)
+      print(i.gm)
+    }
+      
+    
+    # assign to vector of labels
+    ft.vect[i] <- f.i
+    
+    # jump to the next feature
+    this.feature.idx <- which(i.gm$geomfeatid == i.gm$existsonfeat[this.feature.idx])
+    i <- i + 1
+  }
+  
+  # paste into single string
+  ft.string <- paste(ft.vect, collapse=name.sep)
+  
+  # done!
+  return(data.frame(peiid=u.peiid, landform_string=ft.string, stringsAsFactors=FALSE))
+}
+
+
+## https://github.com/ncss-tech/soilDB/issues/84
+# attempt to flatten site parent material data into 2 strings
+.formatParentMaterialString <- function(i.pm, uid = NULL, name.sep='|') {
+  
+  # get the current group of rows by unique ID (either passed by caller or calculated)
+  if (is.null(uid))
+    u.siteiid <- unique(i.pm$siteiid)
+  else 
+    u.siteiid <- uid
+  
+  # sanity check: this function can only be applied to data from a single site
+  if (length(u.siteiid) > 1)
+    stop('data are from multiple site records')
+  
+  # subset sitepm data to remove any with NA for pmkind
+  i.pm <- i.pm[which(!is.na(i.pm$pmkind)), ]
+  
+  # if there is no data, then return NULL
+  if (nrow(i.pm) == 0) {
+    return(data.frame(siteiid = u.siteiid,
+                      pmkind = NA_character_[length(u.siteiid)],
+                      pmorigin = NA_character_[length(u.siteiid)],
+                      stringsAsFactors = FALSE))
+  }
+  
+  # short-circuit: if any pmorder are NA, then we don't know the order
+  # string together as-is, in row-order
+  if (any(is.na(i.pm$pmorder))) {
+    # optional information on which sites have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. NA in pmorder:', u.siteiid), call.=FALSE)
+  } else {
+    # there are no NAs in pmorder --> sort according to pmorder
+    i.pm <- i.pm[order(i.pm$pmorder), ]
+  }
+  
+  # composite strings and return
+  str.kind <- paste(i.pm$pmkind, collapse=name.sep)
+  str.origin <- paste(unique(i.pm$pmorigin), collapse=name.sep)
+  
+  return(data.frame(siteiid=u.siteiid, pmkind=str.kind, pmorigin=str.origin, stringsAsFactors=FALSE))
+}
+
+
+## TODO add .formatcoLandscapeString
+# g <- split(geo, geo$coiid)
+# 
+# gg <- lapply(g, function(i) {
+#   
+#   idx <- which(i$geomftname == 'landscape')
+#   id <- i$coiid[1]
+#   
+#   res <- data.frame(
+#     coiid = id,
+#     geomfname = paste(i$geomfname[idx], collapse = '/'),
+#     stringsAsFactors = FALSE
+#   )
+#   
+#   return(res)
+# })
+
+
+## https://github.com/ncss-tech/soilDB/issues/84
+# 2017-03-13: attempt to format COMPONENT "landform" records into a single string
+# note: there are several assumptions made about the data, 
+# see "short-circuits" used when there are funky data
+.formatcoLandformString <- function(i.gm, name.sep='|') {
+  
+  # hacks to make R CMD check --as-cran happy:
+  u.peiid <- NULL
+  
+  # get the current 
+  u.coiid <- unique(i.gm$coiid)
+  
+  # sanity check: this function can only be applied to data from a single component
+  if(length(u.coiid) > 1)
+    stop('data are from multiple component records')
+  
+  # subset geomorph data to landforms
+  i.gm <- i.gm[which(i.gm$geomftname == 'landform'), ]
+  
+  # allow for NA's
+  if(nrow(i.gm) == 0)
+    return(data.frame(coiid=u.coiid, landform_string=NA, stringsAsFactors=FALSE))
+  
+  # short-circuit: if any geomfeatid are NA, then we don't know the order
+  # string together as-is, in row-order
+  if(any(is.na(i.gm$geomfeatid))) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      message(paste0('Using row-order. NA in geomfeatid: ', u.peiid))
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # short-circuit: if any feature exists on itself, then use row-order
+  # string together as-is, in row-order
+  if(any(na.omit(c(i.gm$geomfeatid == i.gm$existsonfeat), FALSE))) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      message(paste0('Using row-order. Error in exists-on logic: ', u.coiid))
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # get an index to the top-most and bottom-most features
+  # only 1 row should match these criteria
+  top.feature <- which(! i.gm$geomfeatid %in% i.gm$existsonfeat)
+  bottom.feature <- which(! i.gm$existsonfeat %in% i.gm$geomfeatid)
+  
+  ## short-circuit: only 1 row, and exists-on logic is wrong, use row-order
+  if(nrow(i.gm) == 1 & length(top.feature) == length(bottom.feature)) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Single row / error in exists-on logic: ', u.coiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # short-circuit: if the exists-on logic is wrong, use row-order
+  if(length(top.feature) > 1 | length(bottom.feature) > 1) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.coiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # short circuit: if there is circularity in the exists-on logic, use row-order
+  # example coiid: 2119838
+  if(length(top.feature) < 1 | length(bottom.feature) < 1) {
+    
+    # optional information on which pedons have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. Incorrect exists-on specification: ', u.coiid), call.=FALSE)
+    
+    ft.string <- paste(i.gm$geomfname, collapse=name.sep)
+    return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+  }
+  
+  # init a vector to store feature names
+  ft.vect <- vector(mode='character', length=nrow(i.gm))
+  # the first feature is the top-most feature
+  this.feature.idx <- top.feature
+  
+  # loop over features, until the bottom-most feature
+  i <- 1
+  while(i <= nrow(i.gm)){
+    # get the current feature
+    f.i <- i.gm$geomfname[this.feature.idx]
+    
+    # likely an error condition, print some debugging info
+    if(length(f.i) == 0) {
+      print(this.feature.idx)
+      print(i.gm)
+    }
+    
+    
+    # assign to vector of labels
+    ft.vect[i] <- f.i
+    
+    # jump to the next feature
+    this.feature.idx <- which(i.gm$geomfeatid == i.gm$existsonfeat[this.feature.idx])
+    i <- i + 1
+  }
+  
+  # paste into single string
+  ft.string <- paste(ft.vect, collapse=name.sep)
+  
+  # done!
+  return(data.frame(coiid=u.coiid, landform_string=ft.string, stringsAsFactors=FALSE))
+}
+
+
+## https://github.com/ncss-tech/soilDB/issues/84
+# attempt to flatten component parent material data into 2 strings
+.formatcoParentMaterialString <- function(i.pm, name.sep='|') {
+  # get the current site
+  u.coiid <- unique(i.pm$coiid)
+  
+  # sanity check: this function can only be applied to data from a single site
+  if(length(u.coiid) > 1)
+    stop('data are from multiple site records')
+  
+  # subset sitepm data to remove any with NA for pm_kind
+  i.pm <- i.pm[which(!is.na(i.pm$pmkind)), ]
+  
+  # if there is no data, then return a DF formatted as if there were data
+  if(nrow(i.pm) == 0)
+    return(data.frame(coiid=u.coiid, pmkind=NA, pmorigin=NA, stringsAsFactors=FALSE))
+  
+  # short-circuit: if any pmorder are NA, then we don't know the order
+  # string together as-is, in row-order
+  if(any(is.na(i.pm$pmorder))) {
+    # optional information on which sites have issues
+    if(getOption('soilDB.verbose', default=FALSE))
+      warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
+  }
+  else{
+    # there are no NAs in pmorder --> sort according to pmorder
+    i.pm <- i.pm[order(i.pm$pmorder), ]
+  }
+  
+  # composite strings and return
+  str.kind <- paste(i.pm$pmkind, collapse=name.sep)
+  str.origin <- paste(unique(i.pm$pmorigin), collapse=name.sep)
+  
+  return(data.frame(coiid=u.coiid, pmkind=str.kind, pmorigin=str.origin, stringsAsFactors=FALSE))
+}
+
+
+# attempt to flatten multiple ecosite entries into 1 string
+.formatEcositeString <- function(i.esd, name.sep='|') {
+  # get the current site
+  u.coiid <- unique(i.esd$coiid)
+  
+  # sanity check: this function can only be applied to data from a single component
+  if(length(u.coiid) > 1)
+    stop('data are from multiple component records')
+  
+  # subset othervegcl data to remove any with NA for othervegclass
+  i.esd <- i.esd[which(!is.na(i.esd$ecositeid)), ]
+  
+  # if there is no data, then return a DF formatted as if there were data
+  if(nrow(i.esd) == 0)
+    return(data.frame(coiid=u.coiid, ecosite_id=NA, ecosite_name=NA, stringsAsFactors=FALSE))
+  
+  # short-circuit: if any otherveg are NA, then we don't know the order
+  # string together as-is, in row-order
+  # if(any(is.na(i.ov$pmorder))) {
+  #   # optional information on which sites have issues
+  #   if(getOption('soilDB.verbose', default=FALSE))
+  #     warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
+  # }
+  # else{
+  #   # there are no NAs in pmorder --> sort according to pmorder
+  #   i.ov <- i.ov[order(i.ov$pmorder), ]
+  # }
+  
+  # composite strings and return
+  str.ecoid <- paste(i.esd$ecositeid, collapse=name.sep)
+  str.econm <- paste(unique(i.esd$ecositenm), collapse=name.sep)
+  
+  return(data.frame(coiid=u.coiid, ecosite_id=str.ecoid, ecosite_name=str.econm, stringsAsFactors=FALSE))
+}
+
+
+
+# attempt to flatten multiple other veg class entries into 1 string
+.formatOtherVegString <- function(i.ov, name.sep='|') {
+  # get the current site
+  u.coiid <- unique(i.ov$coiid)
+  
+  # sanity check: this function can only be applied to data from a single component
+  if(length(u.coiid) > 1)
+    stop('data are from multiple component records')
+  
+  # subset othervegcl data to remove any with NA for othervegclass
+  i.ov <- i.ov[which(!is.na(i.ov$ovegclid)), ]
+  
+  # if there is no data, then return a DF formatted as if there were data
+  if(nrow(i.ov) == 0)
+    return(data.frame(coiid=u.coiid, othervegid=NA, othervegclass=NA, stringsAsFactors=FALSE))
+  
+  # short-circuit: if any otherveg are NA, then we don't know the order
+  # string together as-is, in row-order
+  # if(any(is.na(i.ov$pmorder))) {
+  #   # optional information on which sites have issues
+  #   if(getOption('soilDB.verbose', default=FALSE))
+  #     warning(paste0('Using row-order. NA in pmorder:', u.coiid), call.=FALSE)
+  # }
+  # else{
+  #   # there are no NAs in pmorder --> sort according to pmorder
+  #   i.ov <- i.ov[order(i.ov$pmorder), ]
+  # }
+  
+  # composite strings and return
+  str.ovegid <- paste(i.ov$ovegclid, collapse=name.sep)
+  str.ovegclnm <- paste(unique(i.ov$ovegclname), collapse=name.sep)
+  
+  return(data.frame(coiid=u.coiid, othervegid=str.ovegid, othervegclass=str.ovegclnm, stringsAsFactors=FALSE))
+}
+
+
+
+
+
+# function to estimate the thickness of horizons and diagnostic features
+.test_thk <- function(x) {
+  names(x) <- gsub("^hz|^feat", "", names(x))
+  std <- with(x, data.frame(
+    l  = depb_l - dept_h,
+    rv = depb_r - dept_r,
+    h  = depb_h - dept_l
+  ))
+  
+  idx <- std < 0
+  idx[is.na(idx)] <- FALSE
+  
+  if (any(idx)) {
+    std_new <- std
+    std_flip <- with(x, data.frame(
+      l = depb_l - dept_l,
+      rv = depb_r - dept_r,
+      h = depb_h - dept_h
+    ))
+    std_new[idx] <- std_flip[idx]
+  }
+  
+  if (exists("std_new")) {
+    idx2 <- which(std_new$l > std$rv)
+    std_new$l[idx2] <- std$r[idx2]
+    idx3 <- which(std$h < std$rv)
+    std_new$h[idx2] <- std$r[idx2]
+  } else std_new <- std
+  
+  return(std_new)
+}
+
+
+
+# impute "not populated" into freqcl and "201" into dept_r & depb_r if !is.na(freqcl)
+.cosoilmoist_prep <- function(df, impute, stringsAsFactors) {
+  
+  # cache original column names
+  orig_names <- names(df)
+  
+  # relabel names
+  names(df) <- gsub("^soimoist", "", names(df))
+  old_names <- "stat"
+  new_names <- "status"
+  names(df)[names(df) %in% old_names] <- new_names
+  
+  
+  # refactoring frequency levels, not sure why these aren't sorted naturally
+  flod_lev <- levels(df$flodfreqcl)
+  pond_lev <- levels(df$pondfreqcl)
+  idx      <- c(1, 5, 2, 7, 3, 4, 6)
+  df <- within(df, {
+    flodfreqcl = levels(flodfreqcl)[as.integer(flodfreqcl)]
+    flodfreqcl = factor(flodfreqcl, levels = flod_lev[idx])
+    pondfreqcl = levels(pondfreqcl)[as.integer(pondfreqcl)]
+    pondfreqcl = factor(pondfreqcl, levels = pond_lev[idx])
+  })
+  
+  
+  # impute NA freqcl values, default = "not populated"
+  if (impute == TRUE) {
+    
+    missing <- "Not populated"
+    lev_flodfreqcl <- c(missing, levels(df$flodfreqcl))
+    lev_pondfreqcl <- c(missing, levels(df$pondfreqcl))
+    lev_status <- c(missing, levels(df$status))
+    
+    df <- within(df, {
+      # replace NULL RV depths with 201 cm if pondfreqcl or flodqcl is not NULL
+      dept_r[is.na(dept_r) & (!is.na(pondfreqcl) | !is.na(flodfreqcl))] = 201
+      depb_r[is.na(depb_r) & (!is.na(pondfreqcl) | !is.na(flodfreqcl))] = 201
+      
+      # replace NULL L and H depths with the RV
+      dept_l = ifelse(is.na(dept_l), dept_r, dept_l)
+      dept_h = ifelse(is.na(dept_h), dept_r, dept_h)
+      
+      depb_l = ifelse(is.na(depb_l), depb_r, depb_l)
+      depb_h = ifelse(is.na(depb_h), depb_r, depb_h)
+      
+      # replace NULL freqcl with "Not_Populated"
+      status = factor(levels(status)[status], levels = lev_status)
+      flodfreqcl = factor(levels(flodfreqcl)[flodfreqcl], levels = lev_flodfreqcl)
+      pondfreqcl = factor(levels(pondfreqcl)[pondfreqcl], levels = lev_flodfreqcl)
+      
+      status[is.na(status)] <- missing
+      flodfreqcl[is.na(flodfreqcl)] <- missing
+      pondfreqcl[is.na(pondfreqcl)] <- missing
+    })
+  }
+  
+  # convert factors to strings
+  idx <- unlist(lapply(df, is.factor))
+  if (stringsAsFactors == FALSE & any(idx)) {
+    df[idx] <- lapply(df[idx], as.character)
+  }
+  
+  return(df)
+}
+
+
+## https://github.com/ncss-tech/soilDB/issues/84
+# Prep of the component parent material
+# flatten multiple records into 1 cokey
+.copm_prep <- function(df, db = NULL) {
+  
+  if (db == "SDA") {
+    
+    # flatten
+    idx <- duplicated(df$cokey)
+    
+    if (any(idx) & nrow(df) > 0) {
+      dups_idx <- df$cokey %in% df[idx, "cokey"]
+      dups     <- df[dups_idx, ]
+      nodups   <- df[!dups_idx, ]
+      
+      # hack to make CRAN check happy
+      pmorigin = NA; pmkind = NA;
+      
+      dups_clean <- {
+        transform(dups, 
+                  idx_pmo = !is.na(pmorigin),
+                  idx_pmk = !is.na(pmkind)
+                  ) ->.;
+        split(., .$cokey, drop = TRUE) ->.
+        lapply(., function(x) { data.frame(
+          x[1, c("cokey", "pmgroupname")],
+          pmkind   = paste(x[x$idx_pmk, "pmkind"  ][order(x[x$idx_pmk, "pmorder"])],   collapse = " over "),
+          pmorigin = paste(x[x$idx_pmo, "pmorigin"][order(x[x$idx_pmo, "pmorder"])], collapse = " over "),
+          stringsAsFactors = FALSE
+          )}) ->.
+        do.call("rbind", .) ->.;
+      }
+      nodups <- nodups[! names(df) %in% c("copmgrpkey", "pmorder")]
+      
+      df <- rbind(nodups, dups_clean)  
+      df <- df[order(df$cokey), ]
+      row.names(df) <- 1:nrow(df)
+      } else df <- df[! names(df) %in% c("copmgrpkey", "pmorder")]
+    
+    
+    # replace "" with NA
+    vars <- c("pmorigin", "pmkind")
+    idx <- unlist(lapply(df, is.character))
+    idx <- names(df) %in% vars & idx
+    df[, idx] <- lapply(df[, idx], function(x) ifelse(x == "", NA, x))
+    }
+  
+  return(df)
+  }
+
+
+## https://github.com/ncss-tech/soilDB/issues/84
+# Prep of the component geomorphic description
+# flatten multiple records into 1 cokey
+.cogmd_prep <- function(df, db = NULL) {
+  
+  # rename LIMS columns and sort comma separated lists
+  if (db == "LIMS") {
+    # rename columns
+    vars <- c("pmkind_grp", "pmorigin_grp", "gc_mntn", "gc_hill", "gc_trce", "gc_flats", "hs_hillslopeprof", "ss_shapeacross", "ss_shapedown")
+    new_names <- c("pmkind", "pmorigin", "mntn", "hill", "trce", "flats", "hillslopeprof", "shapeacross", "shapedown")
+    idx <- which(names(df) %in% vars)
+    names(df)[idx] <- new_names
+    
+    # hack to make CRAN check happy
+    mntn = NULL; hill = NULL; trce = NULL; flats = NULL; hillslopeprof = NULL;
+    
+    ## TODO: consider using is(x, 'character')
+    df <- within(df, {
+      if (class(mntn) == "character") {
+        mntn  = sapply(strsplit(mntn, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
+        }
+      if (class(hill) == "character") {
+        hill  = sapply(strsplit(hill, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
+        }
+      if (class(trce) == "character") {
+        trce  = sapply(strsplit(trce, ", "),  function(x) paste(sort(unlist(x)), collapse = ", "))
+        }
+      if (class(flats) == "character") {
+        flats = sapply(strsplit(flats, ", "), function(x) paste(sort(unlist(x)), collapse = ", "))
+        }
+      if (class(hillslopeprof) == "character") {
+        hillslopeprof = sapply(strsplit(hillslopeprof, ", "), function(x) paste(sort(unlist(x)), collapse = ", "))
+        }
+      })
+    }
+  
+  
+  # flatten the SDA results to 1 cokey
+  if (db == "SDA") {
+    
+    # flatten
+    idx <- duplicated(df$cokey)
+    
+    if (any(idx) & nrow(df) > 0) {
+      dups_idx <- df$cokey %in% df[idx, "cokey"]
+      dups     <- df[dups_idx, ]
+      nodups   <- df[!dups_idx, ]
+      
+      dups_clean <- {
+        split(dups, dups$cokey, drop = TRUE) ->.
+        lapply(., function(x) { data.frame(
+          cokey = x$cokey[1],
+          landscape     = paste(unique(x$landscape),           collapse = " and "),
+          landform      = paste(unique(x$landform),            collapse = " on  "),
+          mntn          = paste(sort(unique(x$mntn)),          collapse = ", "   ),
+          hill          = paste(sort(unique(x$hill)),          collapse = ", "   ),
+          trce          = paste(sort(unique(x$trce)),          collapse = ", "   ),
+          flats         = paste(sort(unique(x$flats)),         collapse = ", "   ),
+          shapeacross   = paste(sort(unique(x$shapeacross)),   collapse = ", "   ),
+          shapedown     = paste(sort(unique(x$shapedown)),     collapse = ", "   ),
+          hillslopeprof = paste(sort(unique(x$hillslopeprof)), collapse = ", "),
+          stringsAsFactors = TRUE
+        )}) ->.
+        do.call("rbind", .) ->.
+      }
+      nodups <- nodups[! names(nodups) %in% c("geomfeatid", "existsonfeat")]
+      
+      df <- rbind(nodups, dups_clean)  
+      df <- df[order(df$cokey), ]
+      row.names(df) <- 1:nrow(df)
+      } else df <- df[! names(df) %in% c("geomfeatid", "existsonfeat")]
+    }
+  
+  vars <- c("landscape", "landform", "mntn", "hill", "trce", "flats", "hillslopeprof")
+  idx <- unlist(lapply(df, is.character))
+  idx <- names(df) %in% vars & idx
+  df[, idx] <- lapply(df[, idx], function(x) ifelse(x %in% c("", "NA"), NA, x))
+  
+  # hack to make CRAN check happy
+  mntn = NA; hill = NA; trce = NA; flats = NA; shapeacross = NA; shapedown = NA;
+  
+  # combine geompos and shapes
+  if (nrow(df) > 0) {
+    df <- within(df, {
+      geompos = NA
+      geompos = paste(mntn, hill, trce, flats, sep = ", ")
+      geompos = gsub("NA", "", geompos)
+      geompos = gsub("^, |^, , |^, , , |, $|, , $|, , , $", "", geompos)
+      geompos = gsub(", , ", ", ", geompos)
+      geompos[geompos == ""] = NA
+      
+      ssa = NA # slope shape across
+      ssd = NA # slope shape down
+      slopeshape = NA
+
+      ssa = gsub("Concave", "C", shapeacross)
+      ssa = gsub("Linear",  "L", ssa)
+      ssa = gsub("Convex",  "V", ssa)
+
+      ssd = gsub("Concave", "C", shapedown)
+      ssd = gsub("Linear",  "L", ssd)
+      ssd = gsub("Convex",  "V", ssd)
+
+      slopeshape = paste0(ssd, ssa, sep = "")
+      slopeshape[slopeshape %in% c("NANA", "")] = NA
+      })
+    df[c("ssa", "ssd")] <- NULL
+  } else df <- cbind(df, geompos = as.character(NULL))
+  
+  ss_vars <- c("CC", "CV", "CL", "LC", "LL", "LV", "VL", "VC", "VV")
+  if (all(df$slopeshape[!is.na(df$slopeshape)] %in% ss_vars)) {
+    df$slopeshape <- factor(df$slopeshape, levels = ss_vars)
+    df$slopeshape <- droplevels(df$slopeshape)
+  }
+  
+  hs_vars <- c("Toeslope", "Footslope", "Backslope", "Shoulder", "Summit")
+  if (all(df$hillslopeprof[!is.na(df$hillslopeprof)] %in% hs_vars)) {
+    df$hillslopeprof <- factor(df$hillslopeprof, levels = hs_vars)
+    df$hillslopeprof <- droplevels(df$hillslopeprof)
+  }
+  
+  hill_vars <- c("Base Slope", "Head Slope", "Side Slope", "Free Face", "Nose Slope", "Crest", "Interfluve")
+  if (all(df$hill[!is.na(df$hill)] %in% hill_vars)) {
+    df$hill <- factor(df$hill, levels = hill_vars)
+    df$hill <- droplevels(df$hill)
+  }
+  
+  flats_vars <- c("Dip", "Talf", "Rise")
+  if (all(df$flats[!is.na(df$flats)] %in% flats_vars)) {
+    df$flats <- factor(df$flats, levels = flats_vars)
+    df$flats <- droplevels(df$flats)
+  }
+  
+  trce_vars <- c("Tread", "Riser")
+  if (all(df$trce[!is.na(df$trce)] %in% trce_vars)) {
+    df$trce <- factor(df$trce, levels = trce_vars)
+    df$trce <- droplevels(df$trce)
+  }
+
+  return(df)
+  }
diff --git a/R/waterDayYear.R b/R/waterDayYear.R
index 9e632be8..08332800 100644
--- a/R/waterDayYear.R
+++ b/R/waterDayYear.R
@@ -1,9 +1,32 @@
-
 ## TODO: leap years? 365 vs 366 total days
 
 # compute water year and day
 # d: anythihng the can be safely converted it POSIXlt
 # end: MM-DD notation for end of water year
+
+
+#' Compute Water Day and Year
+#' 
+#' Compute "water" day and year, based on the end of the typical or legal dry
+#' season. This is September 30 in California.
+#' 
+#' This function doesn't know about leap-years. Probably worth checking.
+#' 
+#' @param d anything the can be safely converted to \code{PPOSIXlt}
+#' @param end "MM-DD" notation for end of water year
+#' @return A \code{data.frame} object with the following \item{wy}{the "water
+#' year"} \item{wd}{the "water day"}
+#' @author D.E. Beaudette
+#' @references Ideas borrowed from:
+#' \url{https://github.com/USGS-R/dataRetrieval/issues/246} and
+#' \url{https://stackoverflow.com/questions/48123049/create-day-index-based-on-water-year}
+#' @keywords manip
+#' @examples
+#' 
+#' # try it
+#' waterDayYear('2019-01-01')
+#' 
+#' @export waterDayYear
 waterDayYear <- function(d, end="09-30") {
   # convert to water year, using Sept
   # ideas from: https://github.com/USGS-R/dataRetrieval/issues/246
diff --git a/README.Rmd b/README.Rmd
index a6bb10ea..f0de4188 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -31,10 +31,11 @@ remotes::install_github("ncss-tech/soilDB", dependencies = FALSE, upgrade = FALS
 
 #### NASIS
 
- * low-level functions return empty `data.frame` objects when local database (or selected set) is empty
- * `fetchNASIS()` is now a wrapper around pedon and component "fetch" functions
- * `uncode()` is now used in all queries to local NASIS database
-
+ * all NASIS queries now use {DBI} w/ {odbc} or {RSQLite} as the back-end
+   * Install required R packages with `install.packages(c("DBI","odbc","RSQLite"), dependencies = TRUE)`
+ * new methods for connecting to NASIS and querying NASIS data allow for `dsn` argument to specify a local "static" SQLite file containing NASIS tables.
+   * Default argument `dsn = NULL` uses `"nasis_local"` [ODBC connection](http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html) to a local NASIS SQL Server instance 
+   
 #### Soil Data Access (SDA)
 
  * `SDA_query` returns a `try-error` for queries with invalid syntax or on network error; empty results are an empty `data.frame()`
@@ -57,6 +58,10 @@ remotes::install_github("ncss-tech/soilDB", dependencies = FALSE, upgrade = FALS
     
   * NASIS local database
     + [`fetchNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/fetchNASIS.html)
+    + **NEW:** Argument `dsn` to specify path to connect to alternate (SQLite) data sources with NASIS schema
+    + **NEW:**[`dbConnectNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/dbConnectNASIS.html) (alias `NASIS`) - create a _DBIConnection_ to local NASIS database
+    + **NEW:**[`dbQueryNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/dbQueryNASIS.html) - query NASIS local database (and close connection with `close=TRUE`)
+    + **NEW:**[`createStaticNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/createStaticNASIS.html) - create list of NASIS tables or write to SQLite
 
   * ROSETTA
     + **NEW:** [`ROSETTA`](http://ncss-tech.github.io/soilDB/docs/reference/ROSETTA.html)
diff --git a/README.md b/README.md
index 6d9f96c0..fa7f127b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [![CRAN Version
 (Stable)](http://www.r-pkg.org/badges/version/soilDB)](https://cran.r-project.org/package=soilDB)
 [![GitHub Version
-(Development)](https://img.shields.io/badge/GitHub-2.6.0-yellowgreen)](https://github.com/ncss-tech/soilDB)
+(Development)](https://img.shields.io/badge/GitHub-2.6.1-yellowgreen)](https://github.com/ncss-tech/soilDB)
 [![R-CMD-check Build
 Status](https://github.com/ncss-tech/soilDB/workflows/R-CMD-check/badge.svg)](https://github.com/ncss-tech/soilDB/actions)
 [![Total CRAN
@@ -25,18 +25,23 @@ Website
 
 -   http://ncss-tech.github.io/AQP/
 
-soilDB 2.6.0
+soilDB 2.6.1
 ------------
 
 ### Notices on Database Interfaces
 
 #### NASIS
 
--   low-level functions return empty `data.frame` objects when local
-    database (or selected set) is empty
--   `fetchNASIS()` is now a wrapper around pedon and component “fetch”
-    functions
--   `uncode()` is now used in all queries to local NASIS database
+-   all NASIS queries now use {DBI} w/ {odbc} or {RSQLite} as the
+    back-end
+    -   Install required R packages with
+        `install.packages(c("DBI","odbc","RSQLite"), dependencies = TRUE)`
+-   new methods for connecting to NASIS and querying NASIS data allow
+    for `dsn` argument to specify a local “static” SQLite file
+    containing NASIS tables.
+    -   Default argument `dsn = NULL` uses `"nasis_local"` [ODBC
+        connection](http://ncss-tech.github.io/AQP/soilDB/setup_local_nasis.html)
+        to a local NASIS SQL Server instance
 
 #### Soil Data Access (SDA)
 
@@ -65,6 +70,20 @@ Functions by Data Source
     -   [`fetchGDB`](http://ncss-tech.github.io/soilDB/docs/reference/fetchGDB.html)
 -   NASIS local database
     -   [`fetchNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/fetchNASIS.html)
+    -   **NEW:** Argument `dsn` to
+        specify path to connect to alternate (SQLite) data sources with
+        NASIS schema
+    -   **NEW:**[`dbConnectNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/dbConnectNASIS.html)
+        (alias `NASIS`) - create a *DBIConnection* to local NASIS
+        database
+    -   **NEW:**[`dbQueryNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/dbQueryNASIS.html) -
+        query NASIS local database (and close connection with
+        `close=TRUE`)
+    -   **NEW:**[`createStaticNASIS`](http://ncss-tech.github.io/soilDB/docs/reference/createStaticNASIS.html) -
+        create list of NASIS tables or write to SQLite
 -   ROSETTA
     -   **NEW:**
         [`ROSETTA`](http://ncss-tech.github.io/soilDB/docs/reference/ROSETTA.html)
@@ -139,7 +158,7 @@ Examples
     res <- vizHillslopePosition(s$hillpos, annotation.cex = 0.9)
     print(res$fig)
 
-
+
 
 ### Make Profile Sketches
 
@@ -153,7 +172,7 @@ Examples
       width = 0.2
     )
 
-
+
 
 ### Identify Tabular “Siblings”
 
@@ -182,8 +201,7 @@ Examples
       cex.taxon.labels = 1,
       cex.names = 1
     )
-
-
+
 
 Dependency Graph
 ----------------
diff --git a/man/ISSR800.wcs.Rd b/man/ISSR800.wcs.Rd
index f633dce8..772a0bd6 100644
--- a/man/ISSR800.wcs.Rd
+++ b/man/ISSR800.wcs.Rd
@@ -25,8 +25,8 @@ Intermediate-scale gridded (800m) soil property and interpretation maps from agg
 \code{aoi} should be specified as either a \code{Spatial*}, \code{sf}, \code{sfc} or \code{bbox} object or a \code{list} containing:
 
 \describe{
-  \item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
-  \item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
+\item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
+\item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
 }
 
 The WCS query is parameterized using \code{raster::extent} derived from the above AOI specification, after conversion to the native CRS (EPSG:6350) of the ISSR-800 grids.
diff --git a/man/KSSL_VG_model.Rd b/man/KSSL_VG_model.Rd
index a3928b99..017007fe 100644
--- a/man/KSSL_VG_model.Rd
+++ b/man/KSSL_VG_model.Rd
@@ -18,9 +18,9 @@ KSSL_VG_model(VG_params, phi_min = 10^-6, phi_max = 10^8, pts = 100)
 \value{
 A list with the following components:
 \describe{
-  \item{VG_curve}{estimated water retention curve: paired estimates of water potential (phi) and water content (theta)}
-  \item{VG_function}{spline function for converting water potential (phi, units of kPa) to estimated volumetric water content (theta, units of percent, range: \{0, 1\})}
-  \item{VG_inverse_function}{spline function for converting volumetric water content (theta, units of percent, range: \{0, 1\}) to estimated water potential (phi, units of kPa)}
+\item{VG_curve}{estimated water retention curve: paired estimates of water potential (phi) and water content (theta)}
+\item{VG_function}{spline function for converting water potential (phi, units of kPa) to estimated volumetric water content (theta, units of percent, range: \{0, 1\})}
+\item{VG_inverse_function}{spline function for converting volumetric water content (theta, units of percent, range: \{0, 1\}) to estimated water potential (phi, units of kPa)}
 }
 }
 \description{
@@ -29,10 +29,10 @@ Water retention curve modeling via van Genuchten model and KSSL data.
 \details{
 This function was developed to work with measured or estimated parameters of the \href{https://en.wikipedia.org/wiki/Water_retention_curve}{van Genuchten model}, as generated by the \href{https://www.ars.usda.gov/pacific-west-area/riverside-ca/agricultural-water-efficiency-and-salinity-research-unit/docs/model/rosetta-model/}{Rosetta model}. As such, \code{VG_params} should have the following format and conventions:
 \describe{
-  \item{theta_r}{saturated water content, values should be in the range of \{0, 1\}}
-  \item{theta_s}{residual water content, values should be in the range of \{0, 1\}}
-  \item{alpha}{related to the inverse of the air entry suction, function expects log10-transformed values with units of cm}
-  \item{npar}{index of pore size distribution, function expects log10-transformed values with units of 1/cm}
+\item{theta_r}{saturated water content, values should be in the range of \{0, 1\}}
+\item{theta_s}{residual water content, values should be in the range of \{0, 1\}}
+\item{alpha}{related to the inverse of the air entry suction, function expects log10-transformed values with units of cm}
+\item{npar}{index of pore size distribution, function expects log10-transformed values with units of 1/cm}
 }
 }
 \note{
diff --git a/man/OSDquery.Rd b/man/OSDquery.Rd
index 8c6b56ff..13991569 100644
--- a/man/OSDquery.Rd
+++ b/man/OSDquery.Rd
@@ -18,7 +18,7 @@ OSDquery(
 )
 }
 \arguments{
-\item{everything}{search entire OSD text (default is NULL), `mlra` may also be specified, all other arguments are ignored}
+\item{everything}{search entire OSD text (default is NULL), \code{mlra} may also be specified, all other arguments are ignored}
 
 \item{mlra}{a comma-delimited string of MLRA to search ('17,18,22A')}
 
@@ -48,30 +48,22 @@ OSD records are searched with the \href{https://www.postgresql.org/docs/9.5/text
 }
 \details{
 See \href{https://casoilresource.lawr.ucdavis.edu/osd-search/}{this webpage} for more information.
-
- - family level taxa are derived from SC database, not parsed OSD records
- 
- - MLRA are derived via spatial intersection (SSURGO x MLRA polygons)
- 
- - MLRA-filtering is only possible for series used in the current SSURGO snapshot (component name)
- 
- - logical AND: &
- 
- - logical OR: |
- 
- - wildcard, e.g. rhy-something rhy:*
- 
- - search terms with spaces need doubled single quotes: ''san joaquin''
- 
- - combine search terms into a single expression: (grano:* | granite)
+\itemize{
+\item family level taxa are derived from SC database, not parsed OSD records
+\item MLRA are derived via spatial intersection (SSURGO x MLRA polygons)
+\item MLRA-filtering is only possible for series used in the current SSURGO snapshot (component name)
+\item logical AND: &
+\item logical OR: |
+\item wildcard, e.g. rhy-something rhy:*
+\item search terms with spaces need doubled single quotes: ''san joaquin''
+\item combine search terms into a single expression: (grano:* | granite)
+}
 
 Related documentation can be found in the following tutorials
 \itemize{
-  \item{\href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}}
-  
-  \item{\href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}}
-  
-  \item{\href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}}
+\item \href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}
+\item \href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}
+\item \href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}
 }
 }
 \note{
@@ -84,13 +76,13 @@ SoilWeb maintains a snapshot of the Official Series Description data.
 if(requireNamespace("curl") &
    curl::has_internet() &
    require(aqp)) {
-  
+
   # find all series that list Pardee as a geographically associated soil.
   s <- OSDquery(geog_assoc_soils = 'pardee')
-  
+
   # get data for these series
   x <- fetchOSD(s$series, extended = TRUE, colorState = 'dry')
-  
+
   # simple figure
   par(mar=c(0,0,1,1))
   plot(x$SPC)
diff --git a/man/ROSETTA.Rd b/man/ROSETTA.Rd
index 0522eef8..cfad70b2 100644
--- a/man/ROSETTA.Rd
+++ b/man/ROSETTA.Rd
@@ -17,24 +17,6 @@ ROSETTA(x, vars, v = c("1", "2", "3"), chunkSize = 10000, conf = NULL)
 
 \item{conf}{configuration passed to \code{httr::POST()} such as \code{verbose()}.}
 }
-\value{
-a \code{data.frame} object:
-
-\describe{
-
- \item{... }{columns present in \code{x}}
-
- \item{theta_r: }{residual volumetric water content (cm^3/cm^3)}
- \item{theta_s: }{saturated volumetric water content (cm^3/cm^3)}
- \item{alpha:}{related to the inverse of the air entry suction, log10-transformed values with units of cm}
- \item{npar: }{index of pore size distribution, log10-transformed values with units of 1/cm}
- \item{ksat: }{saturated hydraulic conductivity, log10-transformed values with units of cm/day}
-
- \item{.rosetta.model}{best-available model selection (-1 signifies that prediction was not possible due to missing values in \code{x})}
- \item{.rosetta.version}{ROSETTA algorithm version, selected via function argument \code{v}}
-
-}
-}
 \description{
 A simple interface to the \href{https://www.ars.usda.gov/pacific-west-area/riverside-ca/agricultural-water-efficiency-and-salinity-research-unit/docs/model/rosetta-model/}{ROSETTA model} for predicting hydraulic parameters from soil properties. The ROSETTA API was developed by Dr. Todd Skaggs (USDA-ARS) and links to the work of Zhang and Schaap, (2017). See the \href{http://ncss-tech.github.io/AQP/soilDB/ROSETTA-API.html}{related tutorial} for additional examples.
 }
@@ -42,29 +24,21 @@ A simple interface to the \href{https://www.ars.usda.gov/pacific-west-area/river
 Soil properties supplied in \code{x} must be described, in order, via \code{vars} argument. The API does not use the names but column ordering must follow: sand, silt, clay, bulk density, volumetric water content at 33kPa (1/3 bar), and volumetric water content at 1500kPa (15 bar).
 
 The ROSETTA model relies on a minimum of 3 soil properties, with increasing (expected) accuracy as additional properties are included:
- \itemize{
-   \item{required, sand, silt, clay: }{USDA soil texture separates (percentages) that sum to 100\%}
-   \item{optional, bulk density (any moisture basis): }{mass per volume after accounting for >2mm fragments, units of gm/cm3}
-   \item{optional, volumetric water content at 33 kPa: }{roughly "field capacity" for most soils, units of cm^3/cm^3}
-   \item{optional, volumetric water content at 1500 kPa: }{roughly "permanent wilting point" for most plants, units of cm^3/cm^3}
- }
+\itemize{
+\item required, sand, silt, clay: USDA soil texture separates (percentages) that sum to 100\\%
+\item optional, bulk density (any moisture basis): mass per volume after accounting for >2mm fragments, units of gm/cm3
+\item optional, volumetric water content at 33 kPa: roughly "field capacity" for most soils, units of cm^3/cm^3
+\item optional, volumetric water content at 1500 kPa: roughly "permanent wilting point" for most plants, units of cm^3/cm^3
+}
 
 Column names not specified in \code{vars} are retained in the output.
 
 Three versions of the ROSETTA model are available, selected using \code{v = 1}, \code{v = 2}, or \code{v = 3}.
-
-\describe{
-
-\item{version 1}{Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}}.
-
-\item{version 2}{Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}}.
-
-
-\item{version 3}{Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}}.
-}
+\itemize{
+\item version 1 - Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}.
+\item version 2 - Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}.
+\item version 3 - Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}.
 }
-\note{
-Input data should not contain columns names that will conflict with the ROSETTA API results: `theta_r`, `theta_s`, `alpha`, `npar`, `ksat`.
 }
 \references{
 Consider using the interactive version, with copy/paste functionality at: \url{https://www.handbook60.org/rosetta}.
diff --git a/man/SCAN_SNOTEL_metadata.Rd b/man/SCAN_SNOTEL_metadata.Rd
index bd2c1b89..eb276445 100644
--- a/man/SCAN_SNOTEL_metadata.Rd
+++ b/man/SCAN_SNOTEL_metadata.Rd
@@ -1,34 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/soilDB-package.R
+\docType{data}
 \name{SCAN_SNOTEL_metadata}
 \alias{SCAN_SNOTEL_metadata}
 \alias{state_FIPS_codes}
-
-\docType{data}
-
 \title{SCAN and SNOTEL Station Metadata}
-
-\description{SCAN and SNOTEL station metadata, a work in progress.}
-
-\usage{data("SCAN_SNOTEL_metadata")}
-
 \format{
-  A data frame with 1092 observations on the following 12 variables.
-  \describe{
-    \item{\code{Name}}{station name}
-    \item{\code{Site}}{station ID}
-    \item{\code{State}}{state}
-    \item{\code{Network}}{sensor network: SCAN / SNOTEL}
-    \item{\code{County}}{county}
-    \item{\code{Elevation_ft}}{station elevation in feet}
-    \item{\code{Latitude}}{latitude of station}
-    \item{\code{Longitude}}{longitude of station}
-    \item{\code{HUC}}{associated watershed}
-    \item{\code{climstanm}}{climate station name (TODO: remove this column)}
-    \item{\code{upedonid}}{associated user pedon ID}
-    \item{\code{pedlabsampnum}}{associated lab sample ID}
-  }
+A data frame with 1092 observations on the following 12 variables.
+\describe{ \item{list("Name")}{station name} \item{list("Site")}{station ID}
+\item{list("State")}{state} \item{list("Network")}{sensor network: SCAN /
+SNOTEL} \item{list("County")}{county} \item{list("Elevation_ft")}{station
+elevation in feet} \item{list("Latitude")}{latitude of station}
+\item{list("Longitude")}{longitude of station} \item{list("HUC")}{associated
+watershed} \item{list("climstanm")}{climate station name (TODO: remove this
+column)} \item{list("upedonid")}{associated user pedon ID}
+\item{list("pedlabsampnum")}{associated lab sample ID} }
+}
+\description{
+SCAN and SNOTEL station metadata, a work in progress.
+}
+\details{
+These data have been compiled from several sources and represent a
+progressive effort to organize SCAN/SNOTEL station metadata. Therefore, some
+records may be missing or incorrect.
 }
-
-\details{These data have been compiled from several sources and represent a progressive effort to organize SCAN/SNOTEL station metadata. Therefore, some records may be missing or incorrect. Details on this effort can be found at the associated GitHub issue page: \url{https://github.com/ncss-tech/soilDB/issues/61}.}
-
-
 \keyword{datasets}
diff --git a/man/SDA_query.Rd b/man/SDA_query.Rd
index da117e87..57a3e0a3 100644
--- a/man/SDA_query.Rd
+++ b/man/SDA_query.Rd
@@ -21,7 +21,7 @@ The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov} and qu
 SSURGO (detailed soil survey) and STATSGO (generalized soil survey) data are stored together within SDA. This means that queries that don't specify an area symbol may result in a mixture of SSURGO and STATSGO records. See the examples below and the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for details.
 }
 \note{
-This function requires the `httr`, `jsonlite`, and `XML` packages
+This function requires the \code{httr}, \code{jsonlite}, and \code{XML} packages
 }
 \examples{
 \donttest{
diff --git a/man/SDA_spatialQuery.Rd b/man/SDA_spatialQuery.Rd
index 42b32f72..144e4f29 100644
--- a/man/SDA_spatialQuery.Rd
+++ b/man/SDA_spatialQuery.Rd
@@ -14,33 +14,55 @@ SDA_spatialQuery(
 )
 }
 \arguments{
-\item{geom}{a Spatial* object, with valid CRS. May contain multiple features.}
+\item{geom}{a Spatial* object, with valid CRS. May contain multiple
+features.}
 
-\item{what}{a character vector specifying what to return. 'mukey': \code{data.frame} with intersecting map unit keys and names, \code{geom} overlapping or intersecting map unit polygons}
+\item{what}{a character vector specifying what to return. 'mukey':
+\code{data.frame} with intersecting map unit keys and names, \code{geom}
+overlapping or intersecting map unit polygons}
 
-\item{geomIntersection}{logical; \code{FALSE}: overlapping map unit polygons returned, \code{TRUE}: intersection of \code{geom} + map unit polygons is returned.}
+\item{geomIntersection}{logical; \code{FALSE}: overlapping map unit polygons
+returned, \code{TRUE}: intersection of \code{geom} + map unit polygons is
+returned.}
 
 \item{db}{a character vector identifying the Soil Geographic Databases
-('SSURGO' or 'STATSGO') to query. Option \var{STATSGO} currently works
-only in combination with \code{what = "geom"}.}
+('SSURGO' or 'STATSGO') to query. Option \var{STATSGO} currently works only
+in combination with \code{what = "geom"}.}
 }
 \value{
-A \code{data.frame} if \code{what = 'mukey'}, otherwise \code{SpatialPolygonsDataFrame} object.
+A \code{data.frame} if \code{what = 'mukey'}, otherwise
+\code{SpatialPolygonsDataFrame} object.
 }
 \description{
-Query SDA (SSURGO / STATSGO) records via spatial intersection with supplied geometries. Input can be SpatialPoints, SpatialLines, or SpatialPolygons objects with a valid CRS. Map unit keys, overlapping polygons, or the spatial intersection of \code{geom} + SSURGO / STATSGO polygons can be returned. See details.
+Query SDA (SSURGO / STATSGO) records via spatial intersection with supplied
+geometries. Input can be SpatialPoints, SpatialLines, or SpatialPolygons
+objects with a valid CRS. Map unit keys, overlapping polygons, or the
+spatial intersection of \code{geom} + SSURGO / STATSGO polygons can be
+returned. See details.
 }
 \details{
-Queries for map unit keys are always more efficient vs. queries for overlapping or intersecting (i.e. least efficient) features. \code{geom} is converted to GCS / WGS84 as needed. Map unit keys are always returned when using \code{what = "geom"}.
+Queries for map unit keys are always more efficient vs. queries for
+overlapping or intersecting (i.e. least efficient) features. \code{geom} is
+converted to GCS / WGS84 as needed. Map unit keys are always returned when
+using \code{what = "geom"}.
 
 There is a 100,000 record limit and 32Mb JSON serializer limit, per query.
 
-SSURGO (detailed soil survey, typically 1:24,000 scale) and STATSGO (generalized soil survey, 1:250,000 scale) data are stored together within SDA. This means that queries that don't specify an area symbol may result in a mixture of SSURGO and STATSGO records. See the examples below and the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for details.
+SSURGO (detailed soil survey, typically 1:24,000 scale) and STATSGO
+(generalized soil survey, 1:250,000 scale) data are stored together within
+SDA. This means that queries that don't specify an area symbol may result in
+a mixture of SSURGO and STATSGO records. See the examples below and the
+\href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial}
+for details.
 }
 \note{
-Row-order is not preserved across features in \code{geom} and returned object. Use \code{sp::over()} or similar functionality to extract from results. Polygon area in acres is computed server-side when \code{what = 'geom'} and \code{geomIntersection = TRUE}.
+Row-order is not preserved across features in \code{geom} and returned
+object. Use \code{sp::over()} or similar functionality to extract from
+results. Polygon area in acres is computed server-side when \code{what =
+'geom'} and \code{geomIntersection = TRUE}.
 }
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
    curl::has_internet() & 
@@ -177,6 +199,7 @@ mtext(
  }
 }
 
+
 }
 \seealso{
 \code{\link{SDA_query}}
diff --git a/man/STRplot.Rd b/man/STRplot.Rd
index 61995c12..3eb26b81 100644
--- a/man/STRplot.Rd
+++ b/man/STRplot.Rd
@@ -1,41 +1,45 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/STR.R
 \name{STRplot}
 \alias{STRplot}
-
 \title{Graphical Description of US Soil Taxonomy Soil Temperature Regimes}
-\description{Graphical Description of US Soil Taxonomy Soil Temperature Regimes}
-
 \usage{
 STRplot(mast, msst, mwst, permafrost = FALSE, pt.cex = 2.75, leg.cex = 0.85)
 }
-
 \arguments{
-  \item{mast}{single value or vector of mean annual soil temperature (deg C)}
-  \item{msst}{single value or vector of mean summer soil temperature (deg C)}
-  \item{mwst}{single value of mean winter soil temperature (deg C)}
-  \item{permafrost}{logical: permafrost presence / absence}
-  \item{pt.cex}{symbol size}
-  \item{leg.cex}{legend size}
-}
+\item{mast}{single value or vector of mean annual soil temperature (deg C)}
 
-\details{
-\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Related tutorial}.
-}
+\item{msst}{single value or vector of mean summer soil temperature (deg C)}
 
-\references{
-Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. U.S. Department of Agriculture, Natural Resources Conservation Service, National Soil Survey Center, Lincoln, Nebraska.
-}
+\item{mwst}{single value of mean winter soil temperature (deg C)}
 
-\author{D.E. Beaudette}
+\item{permafrost}{logical: permafrost presence / absence}
 
+\item{pt.cex}{symbol size}
 
-\seealso{
-\code{\link{estimateSTR}}
+\item{leg.cex}{legend size}
+}
+\description{
+Graphical Description of US Soil Taxonomy Soil Temperature Regimes
+}
+\details{
+\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Soil Temperature Regime Evaluation Tutorial}
 }
-
 \examples{
+
 par(mar=c(4,1,0,1))
 STRplot(mast = 0:25, msst = 10, mwst = 1)
-}
-
-\keyword{ hplot }% use one of  RShowDoc("KEYWORDS")
 
+}
+\references{
+Soil Survey Staff. 2015. Illustrated guide to soil taxonomy.
+U.S. Department of Agriculture, Natural Resources Conservation Service,
+National Soil Survey Center, Lincoln, Nebraska.
+}
+\seealso{
+\code{\link{estimateSTR}}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{hplot}
diff --git a/man/SoilWeb_spatial_query.Rd b/man/SoilWeb_spatial_query.Rd
new file mode 100644
index 00000000..cabc8cbd
--- /dev/null
+++ b/man/SoilWeb_spatial_query.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/SSURGO_spatial_query.R
+\name{SoilWeb_spatial_query}
+\alias{SoilWeb_spatial_query}
+\title{Get SSURGO Data via Spatial Query}
+\usage{
+SoilWeb_spatial_query(
+  bbox = NULL,
+  coords = NULL,
+  what = "mapunit",
+  source = "soilweb"
+)
+}
+\arguments{
+\item{bbox}{a bounding box in WGS84 geographic coordinates, see examples}
+
+\item{coords}{a coordinate pair in WGS84 geographic coordinates, see
+examples}
+
+\item{what}{data to query, currently ignored}
+
+\item{source}{the data source, currently ignored}
+}
+\value{
+The data returned from this function will depend on the query style.
+See examples below.
+}
+\description{
+Get SSURGO Data via Spatial Query to SoilWeb
+}
+\details{
+Data are currently available from SoilWeb. These data are a snapshot of the
+"official" data. The snapshot date is encoded in the "soilweb_last_update"
+column in the function return value. Planned updates to this function will
+include a switch to determine the data source: "official" data via USDA-NRCS
+servers, or a "snapshot" via SoilWeb.
+}
+\note{
+This function should be considered experimental; arguments, results,
+and side-effects could change at any time. SDA now supports spatial queries,
+consider using \code{\link{SDA_query_features}} instead.
+}
+\examples{
+
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet()) {
+    
+    # query by bbox
+    SoilWeb_spatial_query(bbox=c(-122.05, 37, -122, 37.05))
+    
+    # query by coordinate pair
+    SoilWeb_spatial_query(coords=c(-121, 38))
+}
+}
+
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/man/createStaticNASIS.Rd b/man/createStaticNASIS.Rd
new file mode 100644
index 00000000..c1477dee
--- /dev/null
+++ b/man/createStaticNASIS.Rd
@@ -0,0 +1,50 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/createStaticNASIS.R
+\name{createStaticNASIS}
+\alias{createStaticNASIS}
+\title{Create a memory or file-based instance of NASIS database (for selected
+tables)}
+\usage{
+createStaticNASIS(
+  tables = NULL,
+  SS = TRUE,
+  ignore_pattern = "^sys|_SS|_State|NONDEL|View_0|dm_|xml_",
+  dsn = NULL,
+  output_path = NULL,
+  verbose = FALSE
+)
+}
+\arguments{
+\item{tables}{Character vector of target tables. Default: \code{NULL} is all
+tables meeting the following criteria.}
+
+\item{SS}{Logical. Include "selected set" tables (ending with suffix
+\code{"_View_1"}). Default: \code{TRUE}}
+
+\item{ignore_pattern}{A regular expression identifying tables in NASIS schema to ignore. Default: \code{"^sys|_SS|_State|NONDEL|View_0|dm_"}}
+
+\item{dsn}{Optional: path to SQLite database containing NASIS table
+structure; Default: \code{NULL}}
+
+\item{output_path}{Optional: path to new/existing SQLite database to write
+tables to. Default: \code{NULL} returns table results as named list.}
+
+\item{verbose}{Issue error messages for unqueryable tables?}
+}
+\value{
+A named list of results from calling \code{dbQueryNASIS} for all
+columns in each NASIS table.
+}
+\description{
+Create a memory or file-based instance of NASIS database (for selected
+tables)
+}
+\examples{
+
+
+\dontrun{
+ str(createStaticNASIS(tables = c("calculation","formtext")))
+}
+
+
+}
diff --git a/man/dbConnectNASIS.Rd b/man/dbConnectNASIS.Rd
new file mode 100644
index 00000000..445fae22
--- /dev/null
+++ b/man/dbConnectNASIS.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dbQueryNASIS.R
+\name{dbConnectNASIS}
+\alias{dbConnectNASIS}
+\alias{NASIS}
+\title{Create a connection to a local NASIS database}
+\usage{
+dbConnectNASIS(dsn = NULL)
+}
+\arguments{
+\item{dsn}{Optional: path to SQLite database containing NASIS table
+structure; Default: \code{NULL}}
+}
+\value{
+A \code{DBIConnection} object, as returned by
+\code{DBI::dbConnect()}.
+}
+\description{
+Create a connection to a local NASIS database with \code{DBI}
+}
diff --git a/man/dbQueryNASIS.Rd b/man/dbQueryNASIS.Rd
new file mode 100644
index 00000000..7299767e
--- /dev/null
+++ b/man/dbQueryNASIS.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dbQueryNASIS.R
+\name{dbQueryNASIS}
+\alias{dbQueryNASIS}
+\title{Send queries to a NASIS DBIConnection}
+\usage{
+dbQueryNASIS(conn, q, close = TRUE, ...)
+}
+\arguments{
+\item{conn}{A \code{DBIConnection} object, as returned by \code{DBI::dbConnect()}.}
+
+\item{q}{A statement to execute using \code{DBI::dbGetQuery}; or a (named) vector containing multiple statements to evaluate separately}
+
+\item{close}{Close connection after query? Default: \code{TRUE}}
+
+\item{...}{Additional arguments to \code{DBI::dbGetQuery}}
+}
+\value{
+Result of \code{DBI::dbGetQuery}
+}
+\description{
+Send queries to a NASIS DBIConnection
+}
diff --git a/man/dot-dump_NASIS_table.Rd b/man/dot-dump_NASIS_table.Rd
new file mode 100644
index 00000000..e545ac97
--- /dev/null
+++ b/man/dot-dump_NASIS_table.Rd
@@ -0,0 +1,20 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/createStaticNASIS.R
+\name{.dump_NASIS_table}
+\alias{.dump_NASIS_table}
+\title{Method for "dumping" contents of an entire NASIS table}
+\usage{
+.dump_NASIS_table(table_name, dsn = NULL)
+}
+\arguments{
+\item{table_name}{Character name of table.}
+
+\item{dsn}{Optional: path to SQLite database containing NASIS table
+structure; Default: \code{NULL}}
+}
+\value{
+A data.frame or other result of \code{DBI::dbGetQuery}
+}
+\description{
+Method for "dumping" contents of an entire NASIS table
+}
diff --git a/man/estimateSTR.Rd b/man/estimateSTR.Rd
index 1cc735fc..2d043ab0 100644
--- a/man/estimateSTR.Rd
+++ b/man/estimateSTR.Rd
@@ -1,44 +1,59 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/STR.R
 \name{estimateSTR}
 \alias{estimateSTR}
-
 \title{Estimate Soil Temperature Regime}
-\description{Estimate soil temperature regime (STR) based on mean annual soil temperature (MAST), mean summer temperature (MSST), mean winter soil temperature (MWST), presence of O horizons, saturated conditions, and presence of permafrost. Several assumptions are made when O horizon  or saturation are undefined.}
-
 \usage{
-estimateSTR(mast, mean.summer, mean.winter, O.hz = NA, saturated = NA, permafrost = FALSE)
+estimateSTR(
+  mast,
+  mean.summer,
+  mean.winter,
+  O.hz = NA,
+  saturated = NA,
+  permafrost = FALSE
+)
 }
-
 \arguments{
-  \item{mast}{vector of mean annual soil temperature (deg C)}
-  \item{mean.summer}{vector of mean summer soil temperature (deg C)}
-  \item{mean.winter}{vector of mean winter soil temperature (deg C)}
-  \item{O.hz}{logical vector of O horizon presence / absence}
-  \item{saturated}{logical vector of seasonal saturation}
-  \item{permafrost}{logical vector of permafrost presence / absence}
-}
+\item{mast}{vector of mean annual soil temperature (deg C)}
 
-\details{
-\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Related tutorial}.
-}
+\item{mean.summer}{vector of mean summer soil temperature (deg C)}
 
-\value{Vector of soil temperature regimes.}
+\item{mean.winter}{vector of mean winter soil temperature (deg C)}
 
-\references{
-Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. U.S. Department of Agriculture, Natural Resources Conservation Service, National Soil Survey Center, Lincoln, Nebraska.
-}
-
-\author{D.E. Beaudette}
+\item{O.hz}{logical vector of O horizon presence / absence}
 
+\item{saturated}{logical vector of seasonal saturation}
 
-\seealso{
-\code{\link{STRplot}}
+\item{permafrost}{logical vector of permafrost presence / absence}
+}
+\value{
+Vector of soil temperature regimes.
+}
+\description{
+Estimate soil temperature regime (STR) based on mean annual soil temperature
+(MAST), mean summer temperature (MSST), mean winter soil temperature (MWST),
+presence of O horizons, saturated conditions, and presence of permafrost.
+Several assumptions are made when O horizon or saturation are undefined.
+}
+\details{
+\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Soil Temperature Regime Evaluation Tutorial}
 }
-
 \examples{
+
 # simple example
 estimateSTR(mast=17, mean.summer = 22, mean.winter = 12)
 
-}
-
-\keyword{ manip }% use one of  RShowDoc("KEYWORDS")
 
+}
+\references{
+Soil Survey Staff. 2015. Illustrated guide to soil taxonomy.
+U.S. Department of Agriculture, Natural Resources Conservation Service,
+National Soil Survey Center, Lincoln, Nebraska.
+}
+\seealso{
+\code{\link{STRplot}}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/man/fetchGDB.Rd b/man/fetchGDB.Rd
index ee031d50..3edf972d 100644
--- a/man/fetchGDB.Rd
+++ b/man/fetchGDB.Rd
@@ -1,63 +1,58 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_component_from_GDB.R
 \name{fetchGDB}
 \alias{fetchGDB}
 \alias{get_legend_from_GDB}
 \alias{get_mapunit_from_GDB}
 \alias{get_component_from_GDB}
-
 \title{Load and Flatten Data from SSURGO file geodatabases}
-\description{Functions to load and flatten commonly used tables and from SSURGO file geodatabases, and create soil profile collection objects (SPC).}
 \usage{
-fetchGDB(dsn = "gNATSGO_CONUS.gdb",
-         WHERE = NULL,
-         childs = TRUE,
-         droplevels = TRUE,
-         stringsAsFactors = TRUE
-         )
-
-
-get_legend_from_GDB(dsn = "gNATSGO_CONUS.gdb",
-                    WHERE = NULL,
-                    droplevels = TRUE,
-                    stringsAsFactors = TRUE,
-                    stats = FALSE
-                    )
-
-get_mapunit_from_GDB(dsn = "gNATSGO_CONUS.gdb",
-                     WHERE = NULL,
-                     droplevels = TRUE,
-                     stringsAsFactors = TRUE,
-                     stats = FALSE
-                     )
-
-get_component_from_GDB(dsn = "gNATSGO_CONUS.gdb",
-                       WHERE = NULL,
-                       childs = FALSE,
-                       droplevels = TRUE,
-                       stringsAsFactors = TRUE
-                       )
-
+fetchGDB(
+  dsn = "gNATSGO_CONUS.gdb",
+  WHERE = NULL,
+  childs = TRUE,
+  droplevels = TRUE,
+  stringsAsFactors = TRUE
+)
 }
-
-
 \arguments{
-  \item{dsn}{data source name (interpretation varies by driver - for some drivers, dsn is a file name, but may also be a folder, or contain the name and access credentials of a database); in case of GeoJSON, dsn may be the character string holding the geojson data. It can also be an open database connection.}
-  \item{WHERE}{text string formatted as an SQL WHERE clause (default: FALSE)}
-  \item{childs}{logical; if FALSE parent material and geomorphic child tables are not flattened and appended}
-  \item{droplevels}{logical: indicating whether to drop unused levels in classifying factors. This is useful when a class has large number of unused classes, which can waste space in tables and figures.}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-  \item{stats}{Return extended summary statistics (for legend or mapunit only)}
-  }
-
-
-\details{These functions return data from SSURGO file geodatabases with the use of a simple text string that formatted as an SQL WHERE clause (e.g. \code{WHERE = "areasymbol = 'IN001'"}. Any columns within the target table can be specified (except for fetchGDB() currently, which only targets the legend with the WHERE clause).
+\item{dsn}{data source name (interpretation varies by driver - for some
+drivers, dsn is a file name, but may also be a folder, or contain the name
+and access credentials of a database); in case of GeoJSON, dsn may be the
+character string holding the geojson data. It can also be an open database
+connection.}
+
+\item{WHERE}{text string formatted as an SQL WHERE clause (default: FALSE)}
+
+\item{childs}{logical; if FALSE parent material and geomorphic child tables
+are not flattened and appended}
+
+\item{droplevels}{logical: indicating whether to drop unused levels in
+classifying factors. This is useful when a class has large number of unused
+classes, which can waste space in tables and figures.}
+
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the uncode() function. It does not
+convert those vectors that have set outside of uncode() (i.e. hard coded).
+The 'factory-fresh' default is TRUE, but this can be changed by setting
+options(stringsAsFactors = FALSE)}
+}
+\value{
+A \code{data.frame} or \code{SoilProfileCollection} object.
+}
+\description{
+Functions to load and flatten commonly used tables and from SSURGO file
+geodatabases, and create soil profile collection objects (SPC).
+}
+\details{
+These functions return data from SSURGO file geodatabases with the use of a
+simple text string that formatted as an SQL WHERE clause (e.g. \code{WHERE =
+"areasymbol = 'IN001'"}. Any columns within the target table can be
+specified (except for fetchGDB() currently, which only targets the legend
+with the WHERE clause).
 }
-\value{A \code{data.frame} or \code{SoilProfileCollection} object.}
-\author{Stephen Roecker}
-
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \examples{
+
 \donttest{
 
 ## replace `dsn` with path to your own geodatabase (SSURGO OR gNATSGO)
@@ -79,5 +74,9 @@ get_component_from_GDB(dsn = "gNATSGO_CONUS.gdb",
 # f_in_GDB <- fetchGDB(WHERE = "areasymbol LIKE 'IN\%'")
 
 }
+
+}
+\author{
+Stephen Roecker
 }
 \keyword{manip}
diff --git a/man/fetchHenry.Rd b/man/fetchHenry.Rd
index a9f680f0..f11a49fe 100644
--- a/man/fetchHenry.Rd
+++ b/man/fetchHenry.Rd
@@ -1,45 +1,80 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchHenry.R
 \name{fetchHenry}
 \alias{fetchHenry}
 \alias{month2season}
 \alias{summarizeSoilTemperature}
-
 \title{Download Data from the Henry Mount Soil Temperature and Water Database}
-
-\description{This function is a front-end to the REST query functionality of the Henry Mount Soil Temperature and Water Database.}
-
 \usage{
-fetchHenry(what='all', usersiteid = NULL, project = NULL, sso = NULL,
-gran = "day", start.date = NULL, stop.date = NULL, 
-pad.missing.days = TRUE, soiltemp.summaries = TRUE)
+fetchHenry(
+  what = "all",
+  usersiteid = NULL,
+  project = NULL,
+  sso = NULL,
+  gran = "day",
+  start.date = NULL,
+  stop.date = NULL,
+  pad.missing.days = TRUE,
+  soiltemp.summaries = TRUE
+)
 }
-
 \arguments{
-  \item{what}{type of data to return: 'sensors': sensor metadata only | 'soiltemp': sensor metadata + soil temperature data | 'soilVWC': sensor metadata + soil moisture data | 'airtemp': sensor metadata + air temperature data | 'waterlevel': sensor metadata + water level data |'all': sensor metadata + all sensor data}
-  \item{usersiteid}{(optional) filter results using a NASIS user site ID}
-  \item{project}{(optional) filter results using a project ID}
-  \item{sso}{(optional) filter results using a soil survey office code}
-  \item{gran}{data granularity: "day", "week", "month", "year"; returned data are averages}
-  \item{start.date}{(optional) starting date filter}
-  \item{stop.date}{(optional) ending date filter}
-  \item{pad.missing.days}{should missing data ("day" granularity) be filled with NA? see details}
-  \item{soiltemp.summaries}{should soil temperature ("day" granularity only) be summarized? see details}
-}
+\item{what}{type of data to return: 'sensors': sensor metadata only |
+'soiltemp': sensor metadata + soil temperature data | 'soilVWC': sensor
+metadata + soil moisture data | 'airtemp': sensor metadata + air temperature
+data | 'waterlevel': sensor metadata + water level data |'all': sensor
+metadata + all sensor data}
 
-\details{Filling missing days with NA is useful for computing and index of how complete the data are, and for estimating (mostly) unbiased MAST and seasonal mean soil temperatures. Summaries are computed by first averaging over Julian day, then averaging over all days of the year (MAST) or just those days that occur within "summer" or "winter". This approach makes it possible to estimate summaries in the presence of missing data. The quality of summaries should be weighted by the number of "functional years" (number of years with non-missing data after combining data by Julian day) and "complete years" (number of years of data with >= 365 days of non-missing data).}
+\item{usersiteid}{(optional) filter results using a NASIS user site ID}
 
-\value{a list containing:
- \item{sensors}{a \code{SpatialPointsDataFrame} object containing site-level information}
- \item{soiltemp}{a \code{data.frame} object containing soil temperature timeseries data}
- \item{soilVWC}{a \code{data.frame} object containing soil moisture timeseries data}
- \item{airtemp}{a \code{data.frame} object containing air temperature timeseries data}
- \item{waterlevel}{a \code{data.frame} object containing water level timeseries data}
-}
+\item{project}{(optional) filter results using a project ID}
 
-\author{D.E. Beaudette}
-\note{This function and the back-end database are very much a work in progress.}
+\item{sso}{(optional) filter results using a soil survey office code}
 
-\seealso{\code{\link{fetchSCAN}}}
+\item{gran}{data granularity: "day", "week", "month", "year"; returned data
+are averages}
+
+\item{start.date}{(optional) starting date filter}
+
+\item{stop.date}{(optional) ending date filter}
+
+\item{pad.missing.days}{should missing data ("day" granularity) be filled
+with NA? see details}
+
+\item{soiltemp.summaries}{should soil temperature ("day" granularity only)
+be summarized? see details}
+}
+\value{
+a list containing: \item{sensors}{a \code{SpatialPointsDataFrame}
+object containing site-level information} \item{soiltemp}{a
+\code{data.frame} object containing soil temperature timeseries data}
+\item{soilVWC}{a \code{data.frame} object containing soil moisture
+timeseries data} \item{airtemp}{a \code{data.frame} object containing air
+temperature timeseries data} \item{waterlevel}{a \code{data.frame} object
+containing water level timeseries data}
+}
+\description{
+This function is a front-end to the REST query functionality of the Henry
+Mount Soil Temperature and Water Database.
+}
+\details{
+Filling missing days with NA is useful for computing and index of how
+complete the data are, and for estimating (mostly) unbiased MAST and
+seasonal mean soil temperatures. Summaries are computed by first averaging
+over Julian day, then averaging over all days of the year (MAST) or just
+those days that occur within "summer" or "winter". This approach makes it
+possible to estimate summaries in the presence of missing data. The quality
+of summaries should be weighted by the number of "functional years" (number
+of years with non-missing data after combining data by Julian day) and
+"complete years" (number of years of data with >= 365 days of non-missing
+data).
+}
+\note{
+This function and the back-end database are very much a work in
+progress.
+}
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
     curl::has_internet() &
@@ -57,7 +92,12 @@ if(requireNamespace("curl") &
 
 }
 }
-}
 
+}
+\seealso{
+\code{\link{fetchSCAN}}
+}
+\author{
+D.E. Beaudette
+}
 \keyword{manip}
-
diff --git a/man/fetchKSSL.Rd b/man/fetchKSSL.Rd
index 69803095..d0d52a1d 100644
--- a/man/fetchKSSL.Rd
+++ b/man/fetchKSSL.Rd
@@ -1,50 +1,97 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchKSSL.R
 \name{fetchKSSL}
 \alias{fetchKSSL}
 \title{Fetch KSSL Data}
-\description{Download soil characterization and morphologic data via BBOX, MLRA, or soil series name query, from the KSSL database.}
+\usage{
+fetchKSSL(
+  series = NA,
+  bbox = NA,
+  mlra = NA,
+  pedlabsampnum = NA,
+  pedon_id = NA,
+  pedon_key = NA,
+  returnMorphologicData = FALSE,
+  returnGeochemicalData = FALSE,
+  simplifyColors = FALSE,
+  progress = TRUE
+)
+}
+\arguments{
+\item{series}{vector of soil series names, case insensitive}
 
-\usage{fetchKSSL(series=NA, bbox=NA, mlra=NA, pedlabsampnum=NA, 
-pedon_id=NA, pedon_key=NA, returnMorphologicData=FALSE, returnGeochemicalData=FALSE,
-simplifyColors=FALSE, progress=TRUE)}
+\item{bbox}{a single bounding box in WGS84 geographic coordinates e.g.
+\code{c(-120, 37, -122, 38)}}
 
-\arguments{
-  \item{series}{vector of soil series names, case insensitive}
-  \item{bbox}{a single bounding box in WGS84 geographic coordinates e.g. \code{c(-120, 37, -122, 38)}}
-  \item{mlra}{vector of MLRA IDs, e.g. "18" or "22A"}
-  \item{pedlabsampnum}{vector of KSSL pedon lab sample number}
-  \item{pedon_id}{vector of user pedon ID}
-  \item{pedon_key}{vector of KSSL internal pedon ID}
-  \item{returnMorphologicData}{logical, optionally request basic morphologic data, see details section}
-  \item{returnGeochemicalData}{logical, optionally request geochemical, optical and XRD/thermal data, see details section}
-  \item{simplifyColors}{logical, simplify colors (from morphologic data) and join with horizon data}
-  \item{progress}{logical, optionally give progress when iterating over multiple requests}
-}
+\item{mlra}{vector of MLRA IDs, e.g. "18" or "22A"}
 
+\item{pedlabsampnum}{vector of KSSL pedon lab sample number}
 
-\details{This is an experimental interface to a subset for the most commonly used data from a snapshot of KSSL (lab characterization) and NASIS (morphologic) data. 
+\item{pedon_id}{vector of user pedon ID}
 
-Series-queries are case insensitive. Series name is based on the "correlated as" field (from KSSL snapshot) when present.  The "sampled as" classification was promoted to "correlated as" if the "correlated as" classification was missing.
+\item{pedon_key}{vector of KSSL internal pedon ID}
 
-When \code{returnMorphologicData} is TRUE, the resulting object is a list. The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} object) is stored in the named element "SPC". The additional elements are basic morphologic data: soil color, rock fragment volume, pores, structure, and redoximorphic features. There is a 1:many relationship between the horizon data in "SPC" and the additional dataframes in \code{morph}. See examples for ideas on how to "flatten" these tables.
+\item{returnMorphologicData}{logical, optionally request basic morphologic
+data, see details section}
 
-When \code{returnGeochemicalData} is TRUE, the resulting object is a list. The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} object) is stored in the named element "SPC". The additional elements are geochemical and mineralogy analysis tables, specifically: geochemical/elemental analyses "geochem", optical mineralogy "optical", and X-ray diffraction / thermal "xrd_thermal". \code{returnGeochemicalData} will include additional dataframes \code{geochem}, \code{optical}, and \code{xrd_thermal} in list result. 
+\item{returnGeochemicalData}{logical, optionally request geochemical,
+optical and XRD/thermal data, see details section}
 
-Setting \code{simplifyColors=TRUE} will automatically flatten the soil color data and join to horizon level attributes.
+\item{simplifyColors}{logical, simplify colors (from morphologic data) and
+join with horizon data}
 
-Function arguments (\code{series}, \code{mlra}, etc.) are fully vectorized except for \code{bbox}.
+\item{progress}{logical, optionally give progress when iterating over
+multiple requests}
+}
+\value{
+a \code{SoilProfileCollection} object when
+\code{returnMorphologicData} is FALSE, otherwise a list.
 }
+\description{
+Download soil characterization and morphologic data via BBOX, MLRA, or soil
+series name query, from the KSSL database.
+}
+\details{
+This is an experimental interface to a subset for the most commonly used
+data from a snapshot of KSSL (lab characterization) and NASIS (morphologic)
+data.
 
-\value{a \code{SoilProfileCollection} object when \code{returnMorphologicData} is FALSE, otherwise a list.}
+Series-queries are case insensitive. Series name is based on the "correlated
+as" field (from KSSL snapshot) when present.  The "sampled as"
+classification was promoted to "correlated as" if the "correlated as"
+classification was missing.
 
-\author{D.E. Beaudette and A.G. Brown}
-\note{SoilWeb maintains a snapshot of these KSSL and NASIS data. The SoilWeb snapshot was developed using methods described here: \url{https://github.com/dylanbeaudette/process-kssl-snapshot}. Please use the link below for the live data.}
+When \code{returnMorphologicData} is TRUE, the resulting object is a list.
+The standard output from \code{fetchKSSL} (\code{SoilProfileCollection}
+object) is stored in the named element "SPC". The additional elements are
+basic morphologic data: soil color, rock fragment volume, pores, structure,
+and redoximorphic features. There is a 1:many relationship between the
+horizon data in "SPC" and the additional dataframes in \code{morph}. See
+examples for ideas on how to "flatten" these tables.
 
-\references{
-\url{http://ncsslabdatamart.sc.egov.usda.gov/}
-}
+When \code{returnGeochemicalData} is TRUE, the resulting object is a list.
+The standard output from \code{fetchKSSL} (\code{SoilProfileCollection}
+object) is stored in the named element "SPC". The additional elements are
+geochemical and mineralogy analysis tables, specifically:
+geochemical/elemental analyses "geochem", optical mineralogy "optical", and
+X-ray diffraction / thermal "xrd_thermal". \code{returnGeochemicalData} will
+include additional dataframes \code{geochem}, \code{optical}, and
+\code{xrd_thermal} in list result.
+
+Setting \code{simplifyColors=TRUE} will automatically flatten the soil color
+data and join to horizon level attributes.
 
-\seealso{\code{\link{fetchOSD}}}
+Function arguments (\code{series}, \code{mlra}, etc.) are fully vectorized
+except for \code{bbox}.
+}
+\note{
+SoilWeb maintains a snapshot of these KSSL and NASIS data. The SoilWeb
+snapshot was developed using methods described here:
+\url{https://github.com/dylanbeaudette/process-kssl-snapshot}. Please use
+the link below for the live data.
+}
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
     curl::has_internet()) {
@@ -84,6 +131,15 @@ if(requireNamespace("curl") &
     
 }
 }
-}
 
+}
+\references{
+\url{http://ncsslabdatamart.sc.egov.usda.gov/}
+}
+\seealso{
+\code{\link{fetchOSD}}
+}
+\author{
+D.E. Beaudette and A.G. Brown
+}
 \keyword{utilities}
diff --git a/man/fetchNASIS.Rd b/man/fetchNASIS.Rd
index a8804ef7..2f8439f2 100644
--- a/man/fetchNASIS.Rd
+++ b/man/fetchNASIS.Rd
@@ -1,80 +1,110 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchNASIS.R
 \name{fetchNASIS}
 \alias{fetchNASIS}
-\alias{getHzErrorsNASIS}
 \alias{get_phorizon_from_NASIS_db}
 \alias{get_component_copm_data_from_NASIS_db}
 \alias{get_component_horizon_data_from_NASIS_db}
 \alias{get_component_correlation_data_from_NASIS_db}
-\alias{get_component_copm_data_from_NASIS_db}
 \alias{get_component_cogeomorph_data_from_NASIS_db}
 \alias{get_component_esd_data_from_NASIS_db}
 \alias{get_component_otherveg_data_from_NASIS_db}
 \alias{get_copedon_from_NASIS_db}
-
-\alias{get_legend_from_NASIS}
-\alias{get_lmuaoverlap_from_NASIS}
+\alias{get_legend_from_NASISget_lmuaoverlap_from_NASIS}
 \alias{get_mapunit_from_NASIS}
 \alias{get_projectmapunit_from_NASIS}
 \alias{get_component_diaghz_from_NASIS_db}
 \alias{get_mutext_from_NASIS_db}
-\alias{get_cotext_from_NASIS_db}
 \alias{get_phfmp_from_NASIS_db}
 \alias{get_RMF_from_NASIS_db}
 \alias{get_concentrations_from_NASIS_db}
-
-\alias{fetchVegdata}
-\alias{get_vegplot_from_NASIS_db}
-\alias{get_vegplot_location_from_NASIS_db}
-\alias{get_vegplot_species_from_NASIS_db}
-\alias{get_vegplot_textnote_from_NASIS_db}
-\alias{get_vegplot_transect_from_NASIS_db}
-\alias{get_vegplot_transpecies_from_NASIS_db}
-\alias{get_vegplot_tree_si_details_from_NASIS_db}
-\alias{get_vegplot_tree_si_summary_from_NASIS_db}
-\alias{get_vegplot_trhi_from_NASIS_db}
-
-
-
+\alias{get_cotext_from_NASIS_db}
 \title{Fetch commonly used site/pedon/horizon or component data from NASIS.}
-\description{Fetch commonly used site/pedon/horizon data or component from NASIS, returned as a SoilProfileCollection object.}
-
 \usage{
-fetchNASIS(from = 'pedons', url = NULL, SS=TRUE, rmHzErrors=TRUE, nullFragsAreZero=TRUE, 
-                  soilColorState='moist', lab=FALSE, fill = FALSE,
-                  stringsAsFactors = default.stringsAsFactors()
-                  )
-
-getHzErrorsNASIS(strict=TRUE)
+fetchNASIS(
+  from = "pedons",
+  url = NULL,
+  SS = TRUE,
+  rmHzErrors = TRUE,
+  nullFragsAreZero = TRUE,
+  soilColorState = "moist",
+  lab = FALSE,
+  fill = FALSE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-
 \arguments{
-  \item{from}{determines what objects should fetched? ('pedons' | 'components' | 'pedon_report')}
-  \item{url}{string specifying the url for the NASIS pedon_report (default: NULL)}
-  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-  \item{rmHzErrors}{should pedons with horizonation errors be removed from the results? (default: TRUE)}
-  \item{nullFragsAreZero}{should fragment volumes of NULL be interpreted as 0? (default: TRUE), see details}
-  \item{soilColorState}{which colors should be used to generate the convenience field 'soil_color'? ('moist' | 'dry')}
-  \item{lab}{should the phlabresults child table be fetched with site/pedon/horizon data (default: FALSE)}  
-  \item{fill}{(fetchNASIS(from='components') only: include component records without horizon data in result? (default: FALSE)}
-  \item{strict}{how strict should horizon boundaries be checked for consistency: TRUE=more | FALSE=less}
-}
-
-\value{a SoilProfileCollection class object}
-\author{D. E. Beaudette, J. M. Skovlin, and S.M. Roecker}
+\item{from}{determines what objects should fetched? ('pedons' | 'components' | 'pedon_report')}
 
-\details{This function imports data from NASIS into R as a SoilProfileCollection object. It "flattens" NASIS pedon and component tables, including their child tables, into several more easily manageable data frames. Primarily these functions access the local NASIS database using an ODBC connection. However using the fetchNASIS() argument from = "pedon_report", data can be read from the NASIS Report 'fetchNASIS', as either a txt file or url. The primary purpose of fetchNASIS(from = "pedon_report") is to facilitate importing datasets larger than 8000+ pedons/components.
+\item{url}{string specifying the url for the NASIS pedon_report (default:
+\code{NULL})}
 
-The value of \code{nullFragsAreZero} will have a significant impact on the rock fragment fractions returned by \code{fetchNASIS}. Set \code{nullFragsAreZero = FALSE} in those cases where there are many data-gaps and NULL rock fragment values should be interpreted as NULLs. Set \code{nullFragsAreZero = TRUE} in those cases where NULL rock fragment values should be interpreted as 0.
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
-This function attempts to do most of the boilerplate work when extracting site/pedon/horizon or component data from a local NASIS database. Pedons that are missing horizon data, or have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. Pedons with combination horizons (e.g. B/C) are erroneously marked as errors due to the way in which they are stored in NASIS as two overlapping horizon records.
+\item{rmHzErrors}{should pedons with horizon depth errors be removed from
+the results? (default: \code{TRUE})}
 
-See \code{\link{getHzErrorsNASIS}} for a simple approach to identifying pedons with problematic horizonation.
+\item{nullFragsAreZero}{should fragment volumes of \code{NULL} be interpreted as \code{0}?
+(default: \code{TRUE}), see details}
 
-See the \href{http://ncss-tech.github.io/AQP/soilDB/NASIS-component-data.html}{NASIS component tutorial}, and \href{http://ncss-tech.github.io/AQP/soilDB/fetchNASIS-mini-tutorial.html}{NASIS pedon tutorial} for more information.}
+\item{soilColorState}{which colors should be used to generate the
+convenience field \code{soil_color}? (\code{'moist'} or \code{'dry'})}
 
+\item{lab}{should the \code{phlabresults} child table be fetched with
+site/pedon/horizon data (default: \code{FALSE})}
 
-  
+\item{fill}{(\code{fetchNASIS(from='components')} only: include component records
+without horizon data in result? (default: \code{FALSE})}
 
-\keyword{manip}
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
+coded).}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A SoilProfileCollection object
+}
+\description{
+Fetch commonly used site/pedon/horizon data or component from NASIS,
+returned as a SoilProfileCollection object.
+}
+\details{
+This function imports data from NASIS into R as a
+\code{SoilProfileCollection} object. It "flattens" NASIS pedon and component
+tables, including their child tables, into several more easily manageable
+data frames. Primarily these functions access the local NASIS database using
+an ODBC connection. However using the \code{fetchNASIS()} argument
+\code{from = "pedon_report"}, data can be read from the NASIS Report
+'fetchNASIS', as either a txt file or url. The primary purpose of
+\code{fetchNASIS(from = "pedon_report")} is to facilitate importing datasets
+larger than 8000+ pedons/components.
+
+The value of \code{nullFragsAreZero} will have a significant impact on the
+rock fragment fractions returned by fetchNASIS. Set \code{nullFragsAreZero =
+FALSE} in those cases where there are many data-gaps and \code{NULL} rock
+fragment values should be interpreted as \code{NULL}. Set
+\code{nullFragsAreZero = TRUE} in those cases where \code{NULL} rock
+fragment values should be interpreted as 0.
+
+This function attempts to do most of the boilerplate work when extracting
+site/pedon/horizon or component data from a local NASIS database. Pedons
+that are missing horizon data, or have errors in their horizonation are
+excluded from the returned object, however, their IDs are printed on the
+console. Pedons with combination horizons (e.g. B/C) are erroneously marked
+as errors due to the way in which they are stored in NASIS as two
+overlapping horizon records.
+
+Tutorials:
+\itemize{
+\item \href{http://ncss-tech.github.io/AQP/soilDB/fetchNASIS-mini-tutorial.html}{fetchNASIS Pedons Tutorial}
+\item \href{http://ncss-tech.github.io/AQP/soilDB/NASIS-component-data.html}{fetchNASIS Components Tutorial}
+}
+}
+\author{
+D. E. Beaudette, J. M. Skovlin, S.M. Roecker, A.G. Brown
+}
diff --git a/man/fetchNASISLabData.Rd b/man/fetchNASISLabData.Rd
index 7bbb1d28..1fe932d0 100644
--- a/man/fetchNASISLabData.Rd
+++ b/man/fetchNASISLabData.Rd
@@ -1,20 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchNASISLabData.R
 \name{fetchNASISLabData}
 \alias{fetchNASISLabData}
-
-
 \title{Fetch lab data used site/horizon data from a PedonPC database.}
-\description{Fetch KSSL laboratory pedon/horizon layer data from a local NASIS database, return as a SoilProfileCollection object.}
-
-\usage{fetchNASISLabData(SS = TRUE)}
-\arguments{
-  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+\usage{
+fetchNASISLabData(SS = TRUE, dsn = NULL)
 }
-\value{a SoilProfileCollection class object}
-\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
-\author{J.M. Skovlin and D.E. Beaudette}
-\note{This function attempts to do most of the boilerplate work when extracting KSSL laboratory site/horizon data from a local NASIS database. Lab pedons that have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. See \code{\link{getHzErrorsNASIS}} for a simple approach to identifying pedons with problematic horizonation.}
-
-\seealso{\code{\link{get_labpedon_data_from_NASIS_db}}}
+\arguments{
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: \code{TRUE})#'}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+a SoilProfileCollection object
+}
+\description{
+Fetch KSSL laboratory pedon/horizon layer data from a local NASIS database,
+return as a SoilProfileCollection object.
+}
+\details{
+This function currently works only on Windows, and requires a 'nasis_local'
+ODBC connection.
+}
+\seealso{
+\code{\link{get_labpedon_data_from_NASIS_db}}
+}
+\author{
+J.M. Skovlin and D.E. Beaudette
+}
 \keyword{manip}
-
diff --git a/man/fetchNASISWebReport.Rd b/man/fetchNASISWebReport.Rd
index b6eef070..7554f0f2 100644
--- a/man/fetchNASISWebReport.Rd
+++ b/man/fetchNASISWebReport.Rd
@@ -77,38 +77,38 @@ get_project_correlation_from_NASISWebReport(
 }
 \arguments{
 \item{projectname}{text string vector of project names to be inserted into a
-SQL WHERE clause (default: `NA`)}
+SQL WHERE clause (default: \code{NA})}
 
 \item{rmHzErrors}{should pedons with horizonation errors be removed from the
-results? (default: `FALSE`)}
+results? (default: \code{FALSE})}
 
-\item{fill}{should rows with missing component ids be removed (default: `FALSE`)}
+\item{fill}{should rows with missing component ids be removed (default: \code{FALSE})}
 
 \item{stringsAsFactors}{logical: should character vectors be converted to
-factors? This argument is passed to the `uncode()` function. It does not
-convert those vectors that have been set outside of `uncode()` (i.e. hard
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
 coded). The 'factory-fresh' default is TRUE, but this can be changed by
-setting options(`stringsAsFactors = FALSE`)}
+setting options(\code{stringsAsFactors = FALSE})}
 
 \item{mlraoffice}{text string value identifying the MLRA Regional Soil
-Survey Office group name inserted into a SQL WHERE clause (default: `NA`)}
+Survey Office group name inserted into a SQL WHERE clause (default: \code{NA})}
 
 \item{areasymbol}{text string value identifying the area symbol (e.g.
-`IN001` or `IN%`) inserted into a SQL WHERE clause (default: `NA`)
-`NULL` (default: `TRUE`)}
+\code{IN001} or \verb{IN\%}) inserted into a SQL WHERE clause (default: \code{NA})
+\code{NULL} (default: \code{TRUE})}
 
 \item{droplevels}{logical: indicating whether to drop unused levels in
 classifying factors. This is useful when a class has large number of unused
 classes, which can waste space in tables and figures.}
 
 \item{mlrassoarea}{text string value identifying the MLRA Soil Survey Office
-areasymbol symbol inserted into a SQL WHERE clause (default: `NA`)}
+areasymbol symbol inserted into a SQL WHERE clause (default: \code{NA})}
 
 \item{fiscalyear}{text string value identifying the fiscal year inserted
-into a SQL WHERE clause (default: `NA`)}
+into a SQL WHERE clause (default: \code{NA})}
 
 \item{projecttypename}{text string value identifying the project type name
-inserted into a SQL WHERE clause (default: `NA`)}
+inserted into a SQL WHERE clause (default: \code{NA})}
 }
 \value{
 A data.frame or list with the results.
diff --git a/man/fetchOSD.Rd b/man/fetchOSD.Rd
index 55e8c31f..b7bebe0c 100644
--- a/man/fetchOSD.Rd
+++ b/man/fetchOSD.Rd
@@ -22,44 +22,42 @@ This function fetches a variety of data associated with named soil series, extra
 \details{
 {
 \itemize{
-  \item{\href{https://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}}
-  
-  \item{\href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}}
-  
-  \item{\href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}}
-}
+\item{\href{https://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}}
 
+\item{\href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}}
+
+\item{\href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}}
+}
 
 The standard set of "site" and "horizon" data are returned as a \code{SoilProfileCollection} object (\code{extended=FALSE}. The "extended" suite of summary data can be requested by setting \code{extended=TRUE}. The resulting object will be a \code{list} with the following elements:)
 
 \describe{
-  \item{SPC}{\code{SoilProfileCollection} containing standards "site" and "horizon" data}
-  \item{competing}{competing soil series from the SC database snapshot}
-  \item{geog_assoc_soils}{geographically associated soils, extracted from named section in the OSD}
-  \item{geomcomp}{empirical probabilities for geomorphic component, derived from the current SSURGO snapshot}
-  \item{hillpos}{empirical probabilities for hillslope position, derived from the current SSURGO snapshot}
-  \item{mtnpos}{empirical probabilities for mountain slope position, derived from the current SSURGO snapshot}
-  \item{terrace}{empirical probabilities for river terrace position, derived from the current SSURGO snapshot}
-  \item{flats}{empirical probabilities for flat landscapes, derived from the current SSURGO snapshot}
-  \item{pmkind}{empirical probabilities for parent material kind, derived from the current SSURGO snapshot}
-  \item{pmorigin}{empirical probabilities for parent material origin, derived from the current SSURGO snapshot}
-  \item{mlra}{empirical MLRA membership values, derived from the current SSURGO snapshot}
-  \item{climate}{experimental climate summaries from PRISM stack}
-  \item{metadata}{metadata associated with SoilWeb cached summaries}
-} 
-
+\item{SPC}{\code{SoilProfileCollection} containing standards "site" and "horizon" data}
+\item{competing}{competing soil series from the SC database snapshot}
+\item{geog_assoc_soils}{geographically associated soils, extracted from named section in the OSD}
+\item{geomcomp}{empirical probabilities for geomorphic component, derived from the current SSURGO snapshot}
+\item{hillpos}{empirical probabilities for hillslope position, derived from the current SSURGO snapshot}
+\item{mtnpos}{empirical probabilities for mountain slope position, derived from the current SSURGO snapshot}
+\item{terrace}{empirical probabilities for river terrace position, derived from the current SSURGO snapshot}
+\item{flats}{empirical probabilities for flat landscapes, derived from the current SSURGO snapshot}
+\item{pmkind}{empirical probabilities for parent material kind, derived from the current SSURGO snapshot}
+\item{pmorigin}{empirical probabilities for parent material origin, derived from the current SSURGO snapshot}
+\item{mlra}{empirical MLRA membership values, derived from the current SSURGO snapshot}
+\item{climate}{experimental climate summaries from PRISM stack}
+\item{metadata}{metadata associated with SoilWeb cached summaries}
+}
 
-When using `extended=TRUE`, there are a couple of scenarios in which series morphology contained in `SPC` do not fully match records in the associated series summaries (e.g. `competing`).
+When using \code{extended=TRUE}, there are a couple of scenarios in which series morphology contained in \code{SPC} do not fully match records in the associated series summaries (e.g. \code{competing}).
 
 \describe{
 
-  \item{1. A query for soil series that exist entirely outside of CONUS (e.g. PALAU).}{ - Climate summaries are empty \code{data.frames} because these summaries are currently generated from PRISM. We are working on a solution.}
-  
-  \item{2. A query for data within CONUS, but OSD morphology missing due to parsing error (e.g. formatting, typos).}{ - Extended summaries are present but morphology missing from `SPC`. A warning is issued.}
-  
-  \item{3. A query for multiple soil series, with one more more listed as "inactive" (e.g. BREADSPRINGS).}{ - Extended summaries are present but morphology missing from `SPC`. A warning is issued.}
-  
-} 
+\item{1. A query for soil series that exist entirely outside of CONUS (e.g. PALAU).}{ - Climate summaries are empty \code{data.frames} because these summaries are currently generated from PRISM. We are working on a solution.}
+
+\item{2. A query for data within CONUS, but OSD morphology missing due to parsing error (e.g. formatting, typos).}{ - Extended summaries are present but morphology missing from \code{SPC}. A warning is issued.}
+
+\item{3. A query for multiple soil series, with one more more listed as "inactive" (e.g. BREADSPRINGS).}{ - Extended summaries are present but morphology missing from \code{SPC}. A warning is issued.}
+
+}
 
 These last two cases are problematic for analysis that makes use of morphology and extended data, such as outlined in this tutorial on \href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}.
 
diff --git a/man/fetchPedonPC.Rd b/man/fetchPedonPC.Rd
index b768c092..b983e01e 100644
--- a/man/fetchPedonPC.Rd
+++ b/man/fetchPedonPC.Rd
@@ -1,26 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchPedonPC.R
 \name{fetchPedonPC}
 \alias{fetchPedonPC}
 \alias{getHzErrorsPedonPC}
-
 \title{Fetch commonly used site/horizon data from a PedonPC v.5 database.}
-\description{Fetch commonly used site/horizon data from a version 5.x PedonPC database, return as a SoilProfileCollection object.}
-
 \usage{
 fetchPedonPC(dsn)
-getHzErrorsPedonPC(dsn, strict=TRUE)
 }
-
 \arguments{
-  \item{dsn}{The path to a PedonPC version 5.x database}
-  \item{strict}{should horizonation by strictly enforced? (TRUE)}
-}
-
-\details{This function currently works only on Windows.}
-\value{a SoilProfileCollection class object}
-\author{D. E. Beaudette and J. M. Skovlin}
-\note{This function attempts to do most of the boilerplate work when extracting site/horizon data from a PedonPC or local NASIS database. Pedons that have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. See \code{\link{getHzErrorsPedonPC}} for a simple approach to identifying pedons with problematic horizonation. Records from the 'taxhistory' table are selected based on 1) most recent record, or 2) record with the least amount of missing data.}
-
-\seealso{\code{\link{get_hz_data_from_pedon_db}}}
-
+\item{dsn}{The path to a PedonPC version 5.x database}
+}
+\value{
+a SoilProfileCollection class object
+}
+\description{
+Fetch commonly used site/horizon data from a version 5.x PedonPC database,
+return as a SoilProfileCollection object.
+}
+\details{
+This function currently works only on Windows.
+}
+\note{
+This function attempts to do most of the boilerplate work when
+extracting site/horizon data from a PedonPC or local NASIS database. Pedons
+that have errors in their horizonation are excluded from the returned
+object, however, their IDs are printed on the console. See
+\code{\link{getHzErrorsPedonPC}} for a simple approach to identifying pedons
+with problematic horizonation. Records from the 'taxhistory' table are
+selected based on 1) most recent record, or 2) record with the least amount
+of missing data.
+}
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}
+}
+\author{
+D. E. Beaudette and J. M. Skovlin
+}
 \keyword{manip}
-
diff --git a/man/fetchRaCA.Rd b/man/fetchRaCA.Rd
index 241d7db3..c38d2cf3 100644
--- a/man/fetchRaCA.Rd
+++ b/man/fetchRaCA.Rd
@@ -45,25 +45,25 @@ The VNIR spectra associated with RaCA data are quite large [each gzip-compressed
 \donttest{
 if(requireNamespace("curl") &
    curl::has_internet()) {
-  
+
   if(require(aqp)) {
-    
+
     # search by series name
     s <- fetchRaCA(series='auburn')
-    
+
     # search by bounding-box
     # s <- fetchRaCA(bbox=c(-120, 37, -122, 38))
-    
+
     # check structure
     str(s, 1)
-    
+
     # extract pedons
     p <- s$pedons
-    
+
     # how many pedons
     length(p)
-    
-    # plot 
+
+    # plot
     par(mar=c(0,0,0,0))
     plot(p, name='hzn_desgn', max.depth=150)
   }
@@ -72,8 +72,8 @@ if(requireNamespace("curl") &
 }
 \references{
 {
-  \url{https://www.nrcs.usda.gov/wps/portal/nrcs/detail/soils/survey/?cid=nrcs142p2_054164}
-  \href{https://r-forge.r-project.org/scm/viewvc.php/*checkout*/docs/soilDB/RaCA-demo.html?root=aqp}{fetchRaCA() Tutorial}
+\url{https://www.nrcs.usda.gov/wps/portal/nrcs/detail/soils/survey/?cid=nrcs142p2_054164}
+\href{https://r-forge.r-project.org/scm/viewvc.php/\emph{checkout}/docs/soilDB/RaCA-demo.html?root=aqp}{fetchRaCA() Tutorial}
 }
 }
 \seealso{
diff --git a/man/fetchSCAN.Rd b/man/fetchSCAN.Rd
index 4d4169cc..6c256bec 100644
--- a/man/fetchSCAN.Rd
+++ b/man/fetchSCAN.Rd
@@ -1,54 +1,57 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchSCAN.R
 \name{fetchSCAN}
 \alias{fetchSCAN}
 \alias{SCAN_sensor_metadata}
 \alias{SCAN_site_metadata}
-
-
 \title{Fetch SCAN Data}
-\description{Query soil/climate data from USDA-NRCS SCAN Stations (experimental)}
-
 \usage{
-# get SCAN data
-fetchSCAN(site.code, year, report='SCAN', req=NULL)
-
-# get sensor metadata for one or more sites
-SCAN_sensor_metadata(site.code)
-
-# get site metadata for one or more sites
-SCAN_site_metadata(site.code)
+fetchSCAN(site.code, year, report = "SCAN", req = NULL)
 }
-
 \arguments{
-  \item{site.code}{a vector of site codes}
-  \item{year}{a vector of years}
-  \item{report}{report name, single value only}
-  \item{req}{list of SCAN request parameters, for backwards-compatibility only}
-}
-
-\details{See \href{http://ncss-tech.github.io/AQP/soilDB/fetchSCAN-demo.html}{the fetchSCAN tutorial for details.} These functions require the `httr` and `rvest` libraries.}
+\item{site.code}{a vector of site codes}
 
-\note{\code{SCAN_sensor_metadata()} is known to crash on 32bit R / libraries (Windows).}
+\item{year}{a vector of years}
 
-\value{a \code{data.frame} object}
-\references{https://www.wcc.nrcs.usda.gov/index.html}
-\author{D.E. Beaudette}
+\item{report}{report name, single value only}
 
+\item{req}{list of SCAN request parameters, for backwards-compatibility only}
+}
+\value{
+a \code{data.frame} object
+}
+\description{
+Query soil/climate data from USDA-NRCS SCAN Stations (experimental)
+}
+\details{
+See \href{http://ncss-tech.github.io/AQP/soilDB/fetchSCAN-demo.html}{the fetchSCAN tutorial for details}. These functions require the \code{httr} and \code{rvest} libraries.
+}
+\note{
+\code{SCAN_sensor_metadata()} is known to crash on 32bit R / libraries (Windows).
+}
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
     curl::has_internet()) {
-    
+
     # get data: new interface
     x <- fetchSCAN(site.code=c(356, 2072), year=c(2015, 2016))
     str(x)
-    
+
     # get sensor metadata
     m <- SCAN_sensor_metadata(site.code=c(356, 2072))
-    
+
     # get site metadata
     m <- SCAN_site_metadata(site.code=c(356, 2072))
 }
 }
+
+}
+\references{
+https://www.wcc.nrcs.usda.gov/index.html
+}
+\author{
+D.E. Beaudette
 }
 \keyword{manip}
-
diff --git a/man/fetchSDA.Rd b/man/fetchSDA.Rd
new file mode 100644
index 00000000..cb61b91f
--- /dev/null
+++ b/man/fetchSDA.Rd
@@ -0,0 +1,200 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_component_from_SDA.R
+\name{fetchSDA}
+\alias{fetchSDA}
+\alias{get_legend_from_SDA}
+\alias{get_lmuaoverlap_from_SDA}
+\alias{get_mapunit_from_SDA}
+\alias{get_component_from_SDA}
+\alias{get_chorizon_from_SDA}
+\alias{get_cosoilmoist_from_SDA}
+\alias{get_cointerp_from_SDA}
+\title{Download and Flatten Data from Soil Data Access}
+\usage{
+fetchSDA(
+  WHERE = NULL,
+  duplicates = FALSE,
+  childs = TRUE,
+  nullFragsAreZero = TRUE,
+  rmHzErrors = FALSE,
+  droplevels = TRUE,
+  stringsAsFactors = default.stringsAsFactors()
+)
+}
+\arguments{
+\item{WHERE}{text string formatted as an SQL WHERE clause (default: FALSE)}
+
+\item{duplicates}{logical; if TRUE a record is returned for each unique
+mukey (may be many per nationalmusym)}
+
+\item{childs}{logical; if FALSE parent material and geomorphic child tables
+are not flattened and appended}
+
+\item{nullFragsAreZero}{should fragment volumes of NULL be interpreted as 0?
+(default: TRUE), see details}
+
+\item{rmHzErrors}{should pedons with horizonation errors be removed from the
+results? (default: FALSE)}
+
+\item{droplevels}{logical: indicating whether to drop unused levels in
+classifying factors. This is useful when a class has large number of unused
+classes, which can waste space in tables and figures.}
+
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the uncode() function. It does not
+convert those vectors that have set outside of uncode() (i.e. hard coded).
+The 'factory-fresh' default is TRUE, but this can be changed by setting
+options(stringsAsFactors = FALSE)}
+}
+\value{
+A data.frame or SoilProfileCollection object.
+}
+\description{
+Functions to download and flatten commonly used tables and from Soil Data
+Access, and create soil profile collection objects (SPC).
+}
+\details{
+These functions return data from Soil Data Access with the use of a simple
+text string that formatted as an SQL WHERE clause (e.g. \code{WHERE =
+"areasymbol = 'IN001'"}. All functions are SQL queries that wrap around
+\code{SDAquery()} and format the data for analysis.
+
+Beware SDA includes the data for both SSURGO and STATSGO2. The
+\code{areasymbol} for STATSGO2 is \code{US}. For just SSURGO, include
+\code{WHERE = "areareasymbol != 'US'"}.
+
+If the duplicates argument is set to TRUE, duplicate components are
+returned. This is not necessary with data returned from NASIS, which has one
+unique national map unit. SDA has duplicate map national map units, one for
+each legend it exists in.
+
+The value of \code{nullFragsAreZero} will have a significant impact on the
+rock fragment fractions returned by \code{fetchSDA}. Set
+\code{nullFragsAreZero = FALSE} in those cases where there are many
+data-gaps and NULL rock fragment values should be interpreted as NULLs. Set
+\code{nullFragsAreZero = TRUE} in those cases where NULL rock fragment
+values should be interpreted as 0.
+}
+\examples{
+
+\donttest{
+
+
+if (requireNamespace("curl") &
+  curl::has_internet() &
+  require(aqp) &
+  require("ggplot2") &
+  require("gridExtra") &
+  require("viridisLite")
+) {
+
+  # query soil components by areasymbol and musym
+  test = fetchSDA(WHERE = "areasymbol = 'IN005' AND musym = 'MnpB2'")
+
+
+  # profile plot
+  plot(test)
+
+
+  # convert the data for depth plot
+  clay_slice = horizons(slice(test, 0:200 ~ claytotal_l + claytotal_r + claytotal_h))
+  names(clay_slice) <- gsub("claytotal_", "", names(clay_slice))
+
+  om_slice = horizons(slice(test, 0:200 ~ om_l + om_r + om_h))
+  names(om_slice) = gsub("om_", "", names(om_slice))
+
+  test2 = rbind(data.frame(clay_slice, var = "clay"),
+                data.frame(om_slice, var = "om")
+  )
+
+  h = merge(test2, site(test)[c("nationalmusym", "cokey", "compname", "comppct_r")],
+            by = "cokey",
+            all.x = TRUE
+  )
+
+  # depth plot of clay content by soil component
+  gg_comp <- function(x) {
+    ggplot(x) +
+      geom_line(aes(y = r, x = hzdept_r)) +
+      geom_line(aes(y = r, x = hzdept_r)) +
+      geom_ribbon(aes(ymin = l, ymax = h, x = hzdept_r), alpha = 0.2) +
+      xlim(200, 0) +
+      xlab("depth (cm)") +
+      facet_grid(var ~ nationalmusym + paste(compname, comppct_r)) +
+      coord_flip()
+  }
+  g1 <- gg_comp(subset(h, var == "clay"))
+  g2 <- gg_comp(subset(h, var == "om"))
+
+  grid.arrange(g1, g2)
+
+
+  # query cosoilmoist (e.g. water table data) by mukey
+  x <- get_cosoilmoist_from_SDA(WHERE = "mukey = '1395352'")
+
+  ggplot(x, aes(x = as.integer(month), y = dept_r, lty = status)) +
+    geom_rect(aes(xmin = as.integer(month), xmax = as.integer(month) + 1,
+                  ymin = 0, ymax = max(x$depb_r),
+                  fill = flodfreqcl)) +
+    geom_line(cex = 1) +
+    geom_point() +
+    geom_ribbon(aes(ymin = dept_l, ymax = dept_h), alpha = 0.2) +
+    ylim(max(x$depb_r), 0) +
+    xlab("month") + ylab("depth (cm)") +
+    scale_x_continuous(breaks = 1:12, labels = month.abb, name="Month") +
+    facet_wrap(~ paste0(compname, ' (', comppct_r , ')')) +
+    ggtitle(paste0(x$nationalmusym[1],
+                   ': Water Table Levels from Component Soil Moisture Month Data'))
+
+
+
+  # query all Miami major components
+  s <- get_component_from_SDA(WHERE = "compname = 'Miami' \n
+                AND majcompflag = 'Yes' AND areasymbol != 'US'")
+
+
+  # landform vs 3-D morphometry
+  test <- {
+    subset(s, ! is.na(landform) | ! is.na(geompos)) ->.;
+    split(., .$drainagecl, drop = TRUE) ->.;
+    lapply(., function(x) {
+      test = data.frame()
+      test = as.data.frame(table(x$landform, x$geompos))
+      test$compname   = x$compname[1]
+      test$drainagecl = x$drainagecl[1]
+      names(test)[1:2] <- c("landform", "geompos")
+      return(test)
+    }) ->.;
+    do.call("rbind", .) ->.;
+    .[.$Freq > 0, ] ->.;
+    within(., {
+      landform = reorder(factor(landform), Freq, max)
+      geompos  = reorder(factor(geompos),  Freq, max)
+      geompos  = factor(geompos, levels = rev(levels(geompos)))
+    }) ->.;
+  }
+  test$Freq2 <- cut(test$Freq,
+                    breaks = c(0, 5, 10, 25, 50, 100, 150),
+                    labels = c("<5", "5-10", "10-25", "25-50", "50-100", "100-150")
+  )
+  ggplot(test, aes(x = geompos, y = landform, fill = Freq2)) +
+    geom_tile(alpha = 0.5) + facet_wrap(~ paste0(compname, "\n", drainagecl)) +
+    discrete_scale("colour", "viridis", function(n) viridisLite::viridis(n)) +
+    theme(aspect.ratio = 1, axis.text.x = element_text(angle = 45, hjust = 1, vjust = 1)) +
+    ggtitle("Landform vs 3-D Morphometry for Miami Major Components on SDA")
+
+
+}
+
+
+
+}
+
+}
+\seealso{
+\link{SDA_query}
+}
+\author{
+Stephen Roecker
+}
+\keyword{manip}
diff --git a/man/fetchSDA_spatial.Rd b/man/fetchSDA_spatial.Rd
index 496c0b92..9e616724 100644
--- a/man/fetchSDA_spatial.Rd
+++ b/man/fetchSDA_spatial.Rd
@@ -33,7 +33,7 @@ fetchSDA_spatial(
 \item{verbose}{Print messages?}
 }
 \value{
-A Spatial*DataFrame corresponding to SDA spatial data for all symbols requested. Default result contains geometry with attribute table containing unique feature ID, symbol and area symbol plus additional fields in result specified with `add.fields`.
+A Spatial*DataFrame corresponding to SDA spatial data for all symbols requested. Default result contains geometry with attribute table containing unique feature ID, symbol and area symbol plus additional fields in result specified with \code{add.fields}.
 }
 \description{
 This is a high-level "fetch" method to facilitate spatial queries to Soil Data Access (SDA) based on mapunit key (\code{mukey}) and national mapunit symbol (\code{nationalmusym}) for \code{mupolygon} (SSURGO) or \code{gsmmupolygon} (STATSGO) geometry OR legend key (\code{lkey}) and area symbols (\code{areasymbol}) for \code{sapolygon} (Soil Survey Area; SSA) geometry).
@@ -42,7 +42,7 @@ A Soil Data Access spatial query is made returning geometry and key identifying
 
 This function automatically "chunks" the input vector (using \code{soilDB::makeChunks}) of mapunit identifiers to minimize the likelihood of exceeding the SDA data request size. The number of chunks varies with the \code{chunk.size} setting and the length of your input vector. If you are working with many mapunits and/or large extents, you may need to decrease this number in order to have more chunks.
 
-Querying regions with complex mapping may require smaller \code{chunk.size}. Numerically adjacent IDs in the input vector may share common qualities (say, all from same soil survey area or region) which could cause specific chunks to perform "poorly" [slow or error] no matter what the chunk size is. Shuffling the order of the inputs using \code{sample} may help to eliminate problems related to this, depending on how you obtained your set of MUKEY/nationalmusym to query. One could feasibly use \code{muacres} as a heuristic to adjust for total acreage within chunks.
+Querying regions with complex mapping may require smaller \code{chunk.size}. Numerically adjacent IDs in the input vector may share common qualities (say, all from same soil survey area or region) which could cause specific chunks to perform "poorly" (slow or error) no matter what the chunk size is. Shuffling the order of the inputs using \code{sample} may help to eliminate problems related to this, depending on how you obtained your set of MUKEY/nationalmusym to query. One could feasibly use \code{muacres} as a heuristic to adjust for total acreage within chunks.
 }
 \details{
 Note that STATSGO data are fetched using \code{CLIPAREASYMBOL = 'US'} to avoid duplicating state and national subsets of the geometry.
diff --git a/man/fetchVegdata.Rd b/man/fetchVegdata.Rd
new file mode 100644
index 00000000..03a31f1a
--- /dev/null
+++ b/man/fetchVegdata.Rd
@@ -0,0 +1,40 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchVegdata.R
+\name{fetchVegdata}
+\alias{fetchVegdata}
+\alias{get_vegplot_from_NASIS_db}
+\alias{get_vegplot_location_from_NASIS_db}
+\alias{get_vegplot_species_from_NASIS_db}
+\alias{get_vegplot_textnote_from_NASIS_db}
+\alias{get_vegplot_transect_from_NASIS_db}
+\alias{get_vegplot_transpecies_from_NASIS_db}
+\alias{get_vegplot_tree_si_details_from_NASIS_db}
+\alias{get_vegplot_tree_si_summary_from_NASIS_db}
+\alias{get_vegplot_trhi_from_NASIS_db}
+\alias{get_legend_from_NASIS}
+\alias{get_lmuaoverlap_from_NASIS}
+\title{Load most common vegplot data from local NASIS database}
+\usage{
+fetchVegdata(
+  SS = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
+}
+\arguments{
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: \code{TRUE})}
+
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
+coded).}
+
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A named list containing: "vegplot", "vegplotlocation", "vegplotrhi", "vegplotspecies", "vegtransect", "vegtransplantsum", 'vegsiteindexsum', "vegsiteindexdet", and  "vegplottext" tables
+}
+\description{
+Load most common vegplot data from local NASIS database
+}
diff --git a/man/format_SQL_in_statement.Rd b/man/format_SQL_in_statement.Rd
index 860bcbb1..99937140 100644
--- a/man/format_SQL_in_statement.Rd
+++ b/man/format_SQL_in_statement.Rd
@@ -2,7 +2,7 @@
 % Please edit documentation in R/SDA_query.R
 \name{format_SQL_in_statement}
 \alias{format_SQL_in_statement}
-\title{Format vector of values into a string suitable for an SQL `IN` statement.}
+\title{Format vector of values into a string suitable for an SQL \code{IN} statement.}
 \usage{
 format_SQL_in_statement(x)
 }
diff --git a/man/getHzErrorsNASIS.Rd b/man/getHzErrorsNASIS.Rd
new file mode 100644
index 00000000..5eea5dba
--- /dev/null
+++ b/man/getHzErrorsNASIS.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/getHzErrorsNASIS.R
+\name{getHzErrorsNASIS}
+\alias{getHzErrorsNASIS}
+\title{Check pedon horizon table for logic errors}
+\usage{
+getHzErrorsNASIS(strict = TRUE, SS = TRUE, dsn = NULL)
+}
+\arguments{
+\item{strict}{how strict should horizon boundaries be checked for
+consistency: TRUE=more | FALSE=less}
+
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: TRUE)}
+
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: NULL}
+}
+\value{
+A data.frame containing problematic records with columns:
+'peiid','pedon_id','hzdept','hzdepb','hzname'
+}
+\description{
+Check pedon horizon table for logic errors
+}
diff --git a/man/get_NOAA_GHCND.Rd b/man/get_NOAA_GHCND.Rd
index 0f0d54dc..7dc2d228 100644
--- a/man/get_NOAA_GHCND.Rd
+++ b/man/get_NOAA_GHCND.Rd
@@ -21,7 +21,7 @@ A data.frame containing the GHCND data requested (limit 1000 records)
 \description{
 Obtain daily climatic summary data for a set of station IDs, years, and datatypes.
 
-Note that typically results from the NOAA API are limited to 1000 records. However, by "chunking" up data into individual station*year*datatypeid combinations, record results generally do not exceed 365 records for daily summaries.
+Note that typically results from the NOAA API are limited to 1000 records. However, by "chunking" up data into individual station\emph{year}datatypeid combinations, record results generally do not exceed 365 records for daily summaries.
 
 In order to use this function, you must obtain an API token from this website: https://www.ncdc.noaa.gov/cdo-web/token
 }
diff --git a/man/get_colors_from_NASIS_db.Rd b/man/get_colors_from_NASIS_db.Rd
index ec052814..4bbab0a0 100644
--- a/man/get_colors_from_NASIS_db.Rd
+++ b/man/get_colors_from_NASIS_db.Rd
@@ -1,24 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_colors_from_NASIS_db.R
 \name{get_colors_from_NASIS_db}
 \alias{get_colors_from_NASIS_db}
-
 \title{Extract Soil Color Data from a local NASIS Database}
-\description{Get, format, mix, and return color data from a NASIS database.}
 \usage{
-get_colors_from_NASIS_db(SS = TRUE)
+get_colors_from_NASIS_db(SS = TRUE, dsn = NULL)
 }
 \arguments{
-  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}	
-}
-\details{This function currently works only on Windows.}
-\value{A data.frame with the results.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-
-
+\item{SS}{fetch data from Selected Set in NASIS or from the entire local
+database (default: \code{TRUE})}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame with the results.
+}
+\description{
+Get, format, mix, and return color data from a NASIS database.
+}
+\details{
+This function currently works only on Windows.
+}
 \seealso{
-\code{\link{simplifyColorData}}, \code{\link{get_hz_data_from_NASIS_db}}, \code{\link{get_site_data_from_NASIS_db}}
+\code{\link{simplifyColorData}},
+\code{\link{get_hz_data_from_NASIS_db}},
+\code{\link{get_site_data_from_NASIS_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_colors_from_pedon_db.Rd b/man/get_colors_from_pedon_db.Rd
index 99b25a8c..0284fbab 100644
--- a/man/get_colors_from_pedon_db.Rd
+++ b/man/get_colors_from_pedon_db.Rd
@@ -1,26 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_colors_from_pedon_db.R
 \name{get_colors_from_pedon_db}
 \alias{get_colors_from_pedon_db}
-
-
 \title{Extract Soil Color Data from a PedonPC Database}
-\description{Get, format, mix, and return color data from a PedonPC database.}
 \usage{
 get_colors_from_pedon_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The path to a 'pedon.mdb' database.}
+\item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\value{
+A data.frame with the results.
+}
+\description{
+Get, format, mix, and return color data from a PedonPC database.
+}
+\details{
+This function currently works only on Windows.
 }
-\details{This function currently works only on Windows.}
-\value{A data.frame with the results.}
-\author{Dylan E. Beaudette and Jay M. Skovlin}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+\code{\link{get_hz_data_from_pedon_db}},
+\code{\link{get_site_data_from_pedon_db}}
+}
+\author{
+Dylan E. Beaudette and Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_comonth_from_NASIS_db.Rd b/man/get_comonth_from_NASIS_db.Rd
index 467c943a..fd2d4407 100644
--- a/man/get_comonth_from_NASIS_db.Rd
+++ b/man/get_comonth_from_NASIS_db.Rd
@@ -1,32 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_component_data_from_NASIS_db.R
 \name{get_comonth_from_NASIS_db}
 \alias{get_comonth_from_NASIS_db}
-
 \title{Extract component month data from a local NASIS Database}
-\description{Extract component month data from a local NASIS Database.}
-
 \usage{
-get_comonth_from_NASIS_db(SS = TRUE, fill = FALSE,
-                          stringsAsFactors = default.stringsAsFactors()
-                          )
+get_comonth_from_NASIS_db(
+  SS = TRUE,
+  fill = FALSE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-
 \arguments{
-  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
-  \item{fill}{should missing "month" rows in the comonth table be filled with NA (FALSE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-}
-
-\details{This function currently works only on Windows.}
-\value{A list with the results.}
-\author{Stephen Roecker}
+\item{SS}{get data from the currently loaded Selected Set in NASIS or from
+the entire local database (default: TRUE)}
 
+\item{fill}{should missing "month" rows in the comonth table be filled with
+NA (FALSE)}
 
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the uncode() function. It does not
+convert those vectors that have set outside of uncode() (i.e. hard coded).
+The 'factory-fresh' default is TRUE, but this can be changed by setting
+options(stringsAsFactors = FALSE)}
 
-\seealso{
-\code{\link{fetchNASIS}}
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A list with the results.
+}
+\description{
+Extract component month data from a local NASIS Database.
+}
+\details{
+This function currently works only on Windows.
 }
-
 \examples{
+
 \donttest{
 if(local_NASIS_defined()) {
   # query text note data
@@ -36,7 +47,12 @@ if(local_NASIS_defined()) {
   str(cm)
 }
 }
+
+}
+\seealso{
+\code{\link{fetchNASIS}}
+}
+\author{
+Stephen Roecker
 }
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_component_data_from_NASIS_db.Rd b/man/get_component_data_from_NASIS_db.Rd
index e8c55f8e..0cacf6e0 100644
--- a/man/get_component_data_from_NASIS_db.Rd
+++ b/man/get_component_data_from_NASIS_db.Rd
@@ -1,32 +1,37 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_component_data_from_NASIS_db.R
 \name{get_component_data_from_NASIS_db}
 \alias{get_component_data_from_NASIS_db}
 \alias{get_component_restrictions_from_NASIS_db}
-
 \title{Extract component data from a local NASIS Database}
-\description{Extract component data from a local NASIS Database.}
-
 \usage{
-get_component_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())
-get_component_restrictions_from_NASIS_db(SS = TRUE)
+get_component_data_from_NASIS_db(
+  SS = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-
-
 \arguments{
-  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-}
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
-\details{This function currently works only on Windows.}
-\value{A list with the results.}
-\author{Dylan E. Beaudette, Stephen Roecker, and Jay M. Skovlin}
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have set outside of \code{uncode()} (i.e. hard coded).
+The 'factory-fresh' default is TRUE, but this can be changed by setting
+options(\code{stringsAsFactors = FALSE})}
 
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
-\seealso{
-\code{\link{fetchNASIS}}
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A list with the results.
+}
+\description{
+This function currently works only on Windows.
 }
-
 \examples{
+
 \donttest{
 if(local_NASIS_defined()) {
  # query text note data
@@ -36,7 +41,12 @@ if(local_NASIS_defined()) {
  str(fc)
 }
 }
+
+}
+\seealso{
+\code{\link{fetchNASIS}}
+}
+\author{
+Dylan E. Beaudette, Stephen Roecker, and Jay M. Skovlin
 }
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_cosoilmoist_from_NASIS.Rd b/man/get_cosoilmoist_from_NASIS.Rd
index f7f73c85..25a573e4 100644
--- a/man/get_cosoilmoist_from_NASIS.Rd
+++ b/man/get_cosoilmoist_from_NASIS.Rd
@@ -1,27 +1,51 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_cosoilmoist_from_NASIS.R
 \name{get_cosoilmoist_from_NASIS}
 \alias{get_cosoilmoist_from_NASIS}
-
 \title{Read and Flatten the Component Soil Moisture Tables}
-\description{Read and flatten the component soil moisture month tables from a local NASIS Database.}
 \usage{
-get_cosoilmoist_from_NASIS(impute = TRUE, stringsAsFactors = default.stringsAsFactors())
+get_cosoilmoist_from_NASIS(
+  SS = TRUE,
+  impute = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
 \arguments{
-  \item{impute}{replace missing (i.e. NULL) values with "Not_Populated" for categorical data, or the "RV" for numeric data or 201 cm if the "RV" is also NULL (default: TRUE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-}
-\value{A data.frame.}
-\author{S.M. Roecker}
-\details{The component soil moisture tables within NASIS house monthly data on flooding, ponding, and soil moisture status. The soil moisture status is used to specify the water table depth for components (e.g. \code{status == "Moist"}).
-}
-\note{This function currently works only on Windows.}
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
-\seealso{
-\link{fetchNASIS}, \link{get_cosoilmoist_from_NASISWebReport}, \link{get_cosoilmoist_from_SDA}, \code{get_comonth_from_SDA}
-}
+\item{impute}{replace missing (i.e. \code{NULL}) values with \code{"Not_Populated"} for
+categorical data, or the "RV" for numeric data or \code{201} cm if the "RV" is also
+\code{NULL} (default: \code{TRUE})}
 
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have set outside of \code{uncode()} (i.e. hard coded).
+The 'factory-fresh' default is TRUE, but this can be changed by setting
+options(\code{stringsAsFactors = FALSE})}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame.
+}
+\description{
+Read and flatten the component soil moisture month tables from a local NASIS
+Database.
+}
+\details{
+The component soil moisture tables within NASIS house monthly data on
+flooding, ponding, and soil moisture status. The soil moisture status is
+used to specify the water table depth for components (e.g. \code{status ==
+"Moist"}).
+}
+\note{
+This function currently works only on Windows.
+}
 \examples{
+
 \donttest{
 if(local_NASIS_defined()) {
  # load cosoilmoist (e.g. water table data)
@@ -32,6 +56,13 @@ if(local_NASIS_defined()) {
    head(test)
  }
 }
-}}
+}
+}
+\seealso{
+\link{fetchNASIS}, \link{get_cosoilmoist_from_NASISWebReport},
+\link{get_cosoilmoist_from_SDA}, \code{get_comonth_from_SDA}
+}
+\author{
+S.M. Roecker
+}
 \keyword{manip}
-
diff --git a/man/get_extended_data_from_NASIS_db.Rd b/man/get_extended_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..cec6eb86
--- /dev/null
+++ b/man/get_extended_data_from_NASIS_db.Rd
@@ -0,0 +1,57 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_extended_data_from_NASIS_db.R
+\name{get_extended_data_from_NASIS_db}
+\alias{get_extended_data_from_NASIS_db}
+\title{Extract accessory tables and summaries from a local NASIS Database}
+\usage{
+get_extended_data_from_NASIS_db(
+  SS = TRUE,
+  nullFragsAreZero = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
+}
+\arguments{
+\item{SS}{get data from the currently loaded Selected Set in NASIS or from
+the entire local database (default: \code{TRUE})}
+
+\item{nullFragsAreZero}{should fragment volumes of NULL be interpreted as 0?
+(default: TRUE), see details}
+
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
+coded).}
+
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A list with the results.
+}
+\description{
+Extract accessory tables and summaries from a local NASIS Database
+}
+\examples{
+
+\donttest{
+
+if(local_NASIS_defined()) {
+ # query extended data
+ e <- try(get_extended_data_from_NASIS_db())
+
+ # show contents of extended data
+ str(e)
+}
+
+}
+
+}
+\seealso{
+\code{\link{get_hz_data_from_NASIS_db}},
+\code{\link{get_site_data_from_NASIS_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
+}
+\keyword{manip}
diff --git a/man/get_extended_data_from_pedon_db.Rd b/man/get_extended_data_from_pedon_db.Rd
index 47e96565..c2e2dda3 100644
--- a/man/get_extended_data_from_pedon_db.Rd
+++ b/man/get_extended_data_from_pedon_db.Rd
@@ -1,25 +1,28 @@
-\name{get_extended_data_from_pedon_db}
-\alias{get_extended_data_from_pedon_db}
-
-\title{Extract accessory tables and summaries from a local pedonPC Database}
-\description{Extract accessory tables and summaries from a local pedonPC Database.}
-\usage{
-get_extended_data_from_pedon_db(dsn)
-}
-%- maybe also 'usage' for other objects documented here.
-\arguments{
-  \item{dsn}{The path to a 'pedon.mdb' database.}
-}
-\details{This function currently works only on Windows.}
-\value{A list with the results.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
-\seealso{
-\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
-}
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
-\keyword{manip}
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_extended_data_from_pedon_db.R
+\name{get_extended_data_from_pedon_db}
+\alias{get_extended_data_from_pedon_db}
+\title{Extract accessory tables and summaries from a local pedonPC Database}
+\usage{
+get_extended_data_from_pedon_db(dsn)
+}
+\arguments{
+\item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\value{
+A list with the results.
+}
+\description{
+Extract accessory tables and summaries from a local pedonPC Database.
+}
+\details{
+This function currently works only on Windows.
+}
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}},
+\code{\link{get_site_data_from_pedon_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
+}
+\keyword{manip}
diff --git a/man/get_hz_data_from_NASIS_db.Rd b/man/get_hz_data_from_NASIS_db.Rd
index 73431521..c2f3a94d 100644
--- a/man/get_hz_data_from_NASIS_db.Rd
+++ b/man/get_hz_data_from_NASIS_db.Rd
@@ -1,27 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_hz_data_from_NASIS_db.R
 \name{get_hz_data_from_NASIS_db}
 \alias{get_hz_data_from_NASIS_db}
-
 \title{Extract Horizon Data from a local NASIS Database}
-\description{Get horizon-level data from a local NASIS database.}
 \usage{
-get_hz_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())
+get_hz_data_from_NASIS_db(
+  SS = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
 \arguments{
-  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-}
-\details{This function currently works only on Windows.}
-\value{A data.frame.}
-
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-\note{NULL total rock fragment values are assumed to represent an _absence_ of rock fragments, and set to 0.}
+\item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: \code{TRUE})}
 
-%% ~Make other sections like Warning with \section{Warning }{....} ~
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
+coded).}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame.
+}
+\description{
+Get horizon-level data from a local NASIS database.
+}
+\note{
+\code{NULL} total rock fragment values are assumed to represent an \emph{absence} of rock fragments, and set to 0.
+}
 \seealso{
 \code{\link{get_hz_data_from_NASIS_db}}, \code{\link{get_site_data_from_NASIS_db}}
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
+}
 \keyword{manip}
diff --git a/man/get_hz_data_from_pedon_db.Rd b/man/get_hz_data_from_pedon_db.Rd
index 328c0389..7c204d0c 100644
--- a/man/get_hz_data_from_pedon_db.Rd
+++ b/man/get_hz_data_from_pedon_db.Rd
@@ -1,27 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_hz_data_from_pedon_db.R
 \name{get_hz_data_from_pedon_db}
 \alias{get_hz_data_from_pedon_db}
-
 \title{Extract Horizon Data from a PedonPC Database}
-\description{Get horizon-level data from a PedonPC database.}
 \usage{
 get_hz_data_from_pedon_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The path to a 'pedon.mdb' database.}
-}
-\details{This function currently works only on Windows.}
-\value{A data.frame.}
-
-\author{Dylan E. Beaudette and Jay M. Skovlin}
-\note{NULL total rock fragment values are assumed to represent an _absence_ of rock fragments, and set to 0.}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
+\item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\value{
+A data.frame.
+}
+\description{
+Get horizon-level data from a PedonPC database.
+}
+\details{
+This function currently works only on Windows.
+}
+\note{
+NULL total rock fragment values are assumed to represent an \emph{absence}
+of rock fragments, and set to 0.
+}
 \seealso{
-\code{\link{get_colors_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+\code{\link{get_colors_from_pedon_db}},
+\code{\link{get_site_data_from_pedon_db}}
+}
+\author{
+Dylan E. Beaudette and Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_lablayer_data_from_NASIS_db.Rd b/man/get_lablayer_data_from_NASIS_db.Rd
index 1b8ee4d6..eb79d5e7 100644
--- a/man/get_lablayer_data_from_NASIS_db.Rd
+++ b/man/get_lablayer_data_from_NASIS_db.Rd
@@ -1,23 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_lablayer_data_from_NASIS_db.R
 \name{get_lablayer_data_from_NASIS_db}
 \alias{get_lablayer_data_from_NASIS_db}
-
 \title{Extract lab pedon layer data from a local NASIS Database}
-\description{Get lab pedon layer-level(horizon-level) data from a local NASIS database.}
-\usage{get_lablayer_data_from_NASIS_db(SS = TRUE)}
-\arguments{
-  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+\usage{
+get_lablayer_data_from_NASIS_db(SS = TRUE, dsn = NULL)
 }
-\value{A data.frame.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
-\note{This function queries KSSL laboratory site/horizon data from a local NASIS database from the lab layer data table.}
+\arguments{
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame.
+}
+\description{
+Get lab pedon layer-level (horizon-level) data from a local NASIS database.
+}
+\note{
+This function queries KSSL laboratory site/horizon data from a local
+NASIS database from the lab layer data table.
+}
 \seealso{
-\code{\link{get_labpedon_data_from_NASIS_db}} 
+\code{\link{get_labpedon_data_from_NASIS_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
-
 \keyword{manip}
-
diff --git a/man/get_labpedon_data_from_NASIS_db.Rd b/man/get_labpedon_data_from_NASIS_db.Rd
index a3ad3471..50221321 100644
--- a/man/get_labpedon_data_from_NASIS_db.Rd
+++ b/man/get_labpedon_data_from_NASIS_db.Rd
@@ -1,23 +1,36 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_labpedon_data_from_NASIS_db.R
 \name{get_labpedon_data_from_NASIS_db}
 \alias{get_labpedon_data_from_NASIS_db}
-
 \title{Extract lab pedon data from a local NASIS Database}
-\description{Get lab pedon-level data from a local NASIS database.}
-\usage{get_labpedon_data_from_NASIS_db(SS = TRUE)}
-\arguments{
-  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+\usage{
+get_labpedon_data_from_NASIS_db(SS = TRUE, dsn = NULL)
 }
-\value{A data.frame.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
-\note{This function queries KSSL laboratory site/horizon data from a local NASIS database from the lab pedon data table.}
+\arguments{
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from
+the entire local database (default: TRUE)}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame.
+}
+\description{
+Get lab pedon-level data from a local NASIS database.
+}
+\details{
+This function currently works only on Windows, and requires a 'nasis_local'
+ODBC connection.
+}
+\note{
+This function queries KSSL laboratory site/horizon data from a local
+NASIS database from the lab pedon data table.
+}
 \seealso{
-\code{\link{get_lablayer_data_from_NASIS_db}} 
+\code{\link{get_lablayer_data_from_NASIS_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
-
 \keyword{manip}
-
diff --git a/man/get_site_data_from_NASIS_db.Rd b/man/get_site_data_from_NASIS_db.Rd
index 42869586..b98f4fd2 100644
--- a/man/get_site_data_from_NASIS_db.Rd
+++ b/man/get_site_data_from_NASIS_db.Rd
@@ -1,23 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_site_data_from_NASIS_db.R
 \name{get_site_data_from_NASIS_db}
 \alias{get_site_data_from_NASIS_db}
-
 \title{Extract Site Data from a local NASIS Database}
-\description{Get site-level data from a local NASIS database.}
-\usage{get_site_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())}
-\arguments{
-  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+\usage{
+get_site_data_from_NASIS_db(
+  SS = TRUE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-\value{A data.frame.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-\details{When multiple "site bedrock" entries are present, only the shallowest is returned by this function.}
-\note{This function currently works only on Windows.}
+\arguments{
+\item{SS}{fetch data from Selected Set in NASIS or from the entire local
+database (default: \code{TRUE})}
+
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have been set outside of \code{uncode()} (i.e. hard
+coded).}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data.frame
+}
+\description{
+Get site-level data from a local NASIS database.
+}
+\details{
+When multiple "site bedrock" entries are present, only the shallowest is
+returned by this function.
+}
 \seealso{
-\code{\link{get_hz_data_from_NASIS_db}}, 
+\code{\link{get_hz_data_from_NASIS_db}}
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
 }
-
-
-
 \keyword{manip}
-
diff --git a/man/get_site_data_from_pedon_db.Rd b/man/get_site_data_from_pedon_db.Rd
index d275630f..07a202af 100644
--- a/man/get_site_data_from_pedon_db.Rd
+++ b/man/get_site_data_from_pedon_db.Rd
@@ -1,28 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_site_data_from_pedon_db.R
 \name{get_site_data_from_pedon_db}
 \alias{get_site_data_from_pedon_db}
-
 \title{Extract Site Data from a PedonPC Database}
-\description{Get site-level data from a PedonPC database.}
 \usage{
 get_site_data_from_pedon_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The path to a 'pedon.mdb' database.}
+\item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\value{
+A data.frame.
+}
+\description{
+Get site-level data from a PedonPC database.
+}
+\note{
+This function currently works only on Windows.
 }
-
-\value{A data.frame.}
-
-\author{Dylan E. Beaudette and Jay M. Skovlin}
-\note{This function currently works only on Windows.}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_veg_from_AK_Site}}, 
+\code{\link{get_hz_data_from_pedon_db}},
+\code{\link{get_veg_from_AK_Site}},
+}
+\author{
+Dylan E. Beaudette and Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
-
diff --git a/man/get_soilseries_from_NASIS.Rd b/man/get_soilseries_from_NASIS.Rd
index 14c5f21a..947d05d7 100644
--- a/man/get_soilseries_from_NASIS.Rd
+++ b/man/get_soilseries_from_NASIS.Rd
@@ -1,26 +1,32 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_soilseries_from_NASIS.R
 \name{get_soilseries_from_NASIS}
 \alias{get_soilseries_from_NASIS}
 \alias{get_soilseries_from_NASISWebReport}
-
 \title{Get records from the Soil Classification (SC) database}
-\description{These functions return records from the Soil Classification database, either from the local NASIS database (all series) or via web report (named series only).}
-
 \usage{
-
-get_soilseries_from_NASIS(stringsAsFactors = default.stringsAsFactors())
-get_soilseries_from_NASISWebReport(soils, 
-stringsAsFactors = default.stringsAsFactors())
+get_soilseries_from_NASIS(
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-
 \arguments{
-  \item{soils}{character vector of soil series names}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-  
-}
-
-
-\value{A \code{data.frame}.}
-
-\author{Stephen Roecker}
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors? This argument is passed to the \code{uncode()} function. It does not
+convert those vectors that have set outside of \code{uncode()} (i.e. hard coded).}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A \code{data.frame}
+}
+\description{
+These functions return records from the Soil Classification database, either
+from the local NASIS database (all series) or via web report (named series
+only).
+}
+\author{
+Stephen Roecker
+}
 \keyword{manip}
diff --git a/man/get_text_notes_from_NASIS_db.Rd b/man/get_text_notes_from_NASIS_db.Rd
index 67f67309..e3c4ae3b 100644
--- a/man/get_text_notes_from_NASIS_db.Rd
+++ b/man/get_text_notes_from_NASIS_db.Rd
@@ -1,27 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_text_notes_from_NASIS_db.R
 \name{get_text_notes_from_NASIS_db}
 \alias{get_text_notes_from_NASIS_db}
-
 \title{Extract text note data from a local NASIS Database}
-\description{Extract text note data from a local NASIS Database.}
 \usage{
-get_text_notes_from_NASIS_db(SS = TRUE, fixLineEndings = TRUE)
+get_text_notes_from_NASIS_db(SS = TRUE, fixLineEndings = TRUE, dsn = NULL)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
-  \item{fixLineEndings}{convert line endings from "\\r\\n" to "\\n"}
-}
-\details{This function currently works only on Windows.}
-\value{A list with the results.}
-\author{Dylan E. Beaudette and Jay M. Skovlin}
+\item{SS}{get data from the currently loaded Selected Set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
-%% ~Make other sections like Warning with \section{Warning }{....} ~
+\item{fixLineEndings}{convert line endings from \verb{\\r\\n} to \verb{\\n}}
 
-\seealso{
-\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A \code{list} with the results.
+}
+\description{
+Extract text note data from a local NASIS Database
 }
-
 \examples{
+
 \donttest{
 if(local_NASIS_defined()) {
  # query text note data
@@ -36,7 +37,13 @@ if(local_NASIS_defined()) {
  }
 }
 }
+
+}
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}},
+\code{\link{get_site_data_from_pedon_db}}
+}
+\author{
+Dylan E. Beaudette and Jay M. Skovlin
 }
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_veg_data_from_NASIS_db.Rd b/man/get_veg_data_from_NASIS_db.Rd
index 891bb0e3..831867f0 100644
--- a/man/get_veg_data_from_NASIS_db.Rd
+++ b/man/get_veg_data_from_NASIS_db.Rd
@@ -1,21 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_data_from_NASIS_db.R
 \name{get_veg_data_from_NASIS_db}
 \alias{get_veg_data_from_NASIS_db}
-
 \title{Extract veg data from a local NASIS Database}
-\description{Extract veg data from a local NASIS Database.}
 \usage{
-get_veg_data_from_NASIS_db(SS = TRUE)
+get_veg_data_from_NASIS_db(SS = TRUE, dsn = NULL)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
-}
-\details{This function currently works only on Windows.}
-\value{A list with the results.}
-\author{Jay M. Skovlin and Dylan E. Beaudette}
-
+\item{SS}{get data from the currently loaded Selected Set in NASIS or from
+the entire local database (default: \code{TRUE})}
 
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A list with the results.
+}
+\description{
+Extract veg data from a local NASIS Database.
+}
+\details{
+This function currently works only on Windows.
+}
 \examples{
+
 \donttest{
 if(local_NASIS_defined()) {
  # query text note data
@@ -25,7 +33,9 @@ if(local_NASIS_defined()) {
  str(v)
 }
 }
+
+}
+\author{
+Jay M. Skovlin and Dylan E. Beaudette
 }
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_veg_from_AK_Site.Rd b/man/get_veg_from_AK_Site.Rd
index 9ac9cd80..438e0d66 100644
--- a/man/get_veg_from_AK_Site.Rd
+++ b/man/get_veg_from_AK_Site.Rd
@@ -1,26 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_from_AK_Site.R
 \name{get_veg_from_AK_Site}
 \alias{get_veg_from_AK_Site}
-%- Also NEED an '\alias' for EACH other topic documented here.
 \title{Retrieve Vegetation Data from an AK Site Database}
-\description{Retrieve Vegetation Data from an AK Site Database}
 \usage{
 get_veg_from_AK_Site(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{file path the the AK Site access database}
+\item{dsn}{file path the the AK Site access database}
+}
+\value{
+A data.frame with vegetation data in long format, linked to site ID.
+}
+\description{
+Retrieve Vegetation Data from an AK Site Database
+}
+\note{
+This function currently works only on Windows.
 }
-
-\value{A data.frame with vegetation data in long format, linked to site ID.}
-\author{Dylan E. Beaudette}
-\note{This function currently works only on Windows.}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+\code{\link{get_hz_data_from_pedon_db}},
+\code{\link{get_site_data_from_pedon_db}}
+}
+\author{
+Dylan E. Beaudette
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_veg_from_MT_veg_db.Rd b/man/get_veg_from_MT_veg_db.Rd
index 37413c28..9e7169bd 100644
--- a/man/get_veg_from_MT_veg_db.Rd
+++ b/man/get_veg_from_MT_veg_db.Rd
@@ -1,25 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_from_MT_veg_db.R
 \name{get_veg_from_MT_veg_db}
 \alias{get_veg_from_MT_veg_db}
-
 \title{Extract Site and Plot-level Data from a Montana RangeDB database}
-\description{Get Site and Plot-level data from a Montana RangeDB database.}
 \usage{
-get_veg_from_MT_veg_db(dsn) 
+get_veg_from_MT_veg_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+\item{dsn}{The name of the Montana RangeDB front-end database connection
+(see details).}
+}
+\value{
+A data.frame.
+}
+\description{
+Get Site and Plot-level data from a Montana RangeDB database.
+}
+\details{
+This function currently works only on Windows.
 }
-\details{This function currently works only on Windows.}
-\value{A data.frame.}
-\author{Jay M. Skovlin}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_veg_species_from_MT_veg_db}}, \code{\link{get_veg_other_from_MT_veg_db}}
+\code{\link{get_veg_species_from_MT_veg_db}},
+\code{\link{get_veg_other_from_MT_veg_db}}
+}
+\author{
+Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_veg_from_NPS_PLOTS_db.Rd b/man/get_veg_from_NPS_PLOTS_db.Rd
index 9b61585d..b290c866 100644
--- a/man/get_veg_from_NPS_PLOTS_db.Rd
+++ b/man/get_veg_from_NPS_PLOTS_db.Rd
@@ -1,20 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_from_NPS_PLOTS_db.R
 \name{get_veg_from_NPS_PLOTS_db}
 \alias{get_veg_from_NPS_PLOTS_db}
-
 \title{Retrieve Vegetation Data from an NPS PLOTS Database}
-
-\description{Used to extract species, stratum, and cover vegetation data from a backend NPS PLOTS Database.  Currently works for any Microsoft Access database with an .mdb file format.}
-
-\usage{get_veg_from_NPS_PLOTS_db(dsn)}
-
+\usage{
+get_veg_from_NPS_PLOTS_db(dsn)
+}
 \arguments{
-  \item{dsn}{file path to the NPS PLOTS access database on your system.}
+\item{dsn}{file path to the NPS PLOTS access database on your system.}
+}
+\value{
+A data.frame with vegetation data in a long format with linkage to
+NRCS soil pedon data via the site_id key field.
+}
+\description{
+Used to extract species, stratum, and cover vegetation data from a backend
+NPS PLOTS Database.  Currently works for any Microsoft Access database with
+an .mdb file format.
+}
+\note{
+This function currently only works on Windows.
+}
+\author{
+Jay M. Skovlin
 }
-
-\value{A data.frame with vegetation data in a long format with linkage to NRCS soil pedon data via the site_id key field.}
-
-\author{Jay M. Skovlin}
-
-\note{This function currently only works on Windows.}
-
 \keyword{manip}
diff --git a/man/get_veg_other_from_MT_veg_db.Rd b/man/get_veg_other_from_MT_veg_db.Rd
index 8d2bb515..86639b95 100644
--- a/man/get_veg_other_from_MT_veg_db.Rd
+++ b/man/get_veg_other_from_MT_veg_db.Rd
@@ -1,25 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_other_from_MT_veg_db.R
 \name{get_veg_other_from_MT_veg_db}
 \alias{get_veg_other_from_MT_veg_db}
-
 \title{Extract cover composition data from a Montana RangeDB database}
-\description{Get cover composition data from a Montana RangeDB database.}
 \usage{
-get_veg_other_from_MT_veg_db(dsn) 
+get_veg_other_from_MT_veg_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+\item{dsn}{The name of the Montana RangeDB front-end database connection
+(see details).}
+}
+\value{
+A data.frame.
+}
+\description{
+Get cover composition data from a Montana RangeDB database.
+}
+\details{
+This function currently works only on Windows.
 }
-\details{This function currently works only on Windows.}
-\value{A data.frame.}
-\author{Jay M. Skovlin}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_veg_from_MT_veg_db}}, \code{\link{get_veg_species_from_MT_veg_db}}
+\code{\link{get_veg_from_MT_veg_db}},
+\code{\link{get_veg_species_from_MT_veg_db}}
+}
+\author{
+Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/get_veg_species_from_MT_veg_db.Rd b/man/get_veg_species_from_MT_veg_db.Rd
index 2e6c8b2e..02cfdae2 100644
--- a/man/get_veg_species_from_MT_veg_db.Rd
+++ b/man/get_veg_species_from_MT_veg_db.Rd
@@ -1,25 +1,29 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_veg_species_from_MT_veg_db.R
 \name{get_veg_species_from_MT_veg_db}
 \alias{get_veg_species_from_MT_veg_db}
-
 \title{Extract species-level Data from a Montana RangeDB database}
-\description{Get species-level data from a Montana RangeDB database.}
 \usage{
-get_veg_species_from_MT_veg_db(dsn) 
+get_veg_species_from_MT_veg_db(dsn)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+\item{dsn}{The name of the Montana RangeDB front-end database connection
+(see details).}
+}
+\value{
+A data.frame.
+}
+\description{
+Get species-level data from a Montana RangeDB database.
+}
+\details{
+This function currently works only on Windows.
 }
-\details{This function currently works only on Windows.}
-\value{A data.frame.}
-\author{Jay M. Skovlin}
-
-%% ~Make other sections like Warning with \section{Warning }{....} ~
-
 \seealso{
-\code{\link{get_veg_from_MT_veg_db}}, \code{\link{get_veg_other_from_MT_veg_db}}
+\code{\link{get_veg_from_MT_veg_db}},
+\code{\link{get_veg_other_from_MT_veg_db}}
+}
+\author{
+Jay M. Skovlin
 }
-
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
 \keyword{manip}
diff --git a/man/loafercreek.Rd b/man/loafercreek.Rd
index cbad533a..80f7b94b 100644
--- a/man/loafercreek.Rd
+++ b/man/loafercreek.Rd
@@ -1,73 +1,67 @@
-\name{loafercreek}
-\alias{loafercreek}
-\alias{gopheridge}
-\alias{mineralKing}
-
-
-\docType{data}
-\title{Example \code{SoilProfilecollection} Objects Returned by \code{fetchNASIS}.}
-
-\description{Several examples of soil profile collections returned by \code{fetchNASIS(from='pedons')} as \code{SoilProfileCollection} objects.}
-
-\usage{
-data(loafercreek)
-data(gopheridge)
-data(mineralKing)
-}
-
-
-\examples{
-\donttest{
-if(require("aqp")) {
-# load example dataset
-  data("gopheridge")
-  
-  # what kind of object is this?
-  class(gopheridge)
-  
-  # how many profiles?
-  length(gopheridge)
-  
-  # there are 60 profiles, this calls for a split plot
-  par(mar=c(0,0,0,0), mfrow=c(2,1))
-  
-  # plot soil colors
-  plot(gopheridge[1:30, ], name='hzname', color='soil_color')
-  plot(gopheridge[31:60, ], name='hzname', color='soil_color')
-  
-  # need a larger top margin for legend
-  par(mar=c(0,0,4,0), mfrow=c(2,1))
-  # generate colors based on clay content
-  plot(gopheridge[1:30, ], name='hzname', color='clay')
-  plot(gopheridge[31:60, ], name='hzname', color='clay')
-  
-  # single row and no labels
-  par(mar=c(0,0,0,0), mfrow=c(1,1))
-  # plot soils sorted by depth to contact
-  plot(gopheridge, name='', print.id=FALSE, plot.order=order(gopheridge$bedrckdepth))
-  
-  # plot first 10 profiles
-  plot(gopheridge[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
-  
-  # add rock fragment data to plot:
-  addVolumeFraction(gopheridge[1:10, ], colname='total_frags_pct')
-  
-  # add diagnostic horizons
-  addDiagnosticBracket(gopheridge[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
-  
-  ## loafercreek
-  data("loafercreek")
-  # plot first 10 profiles
-  plot(loafercreek[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
-  
-  # add rock fragment data to plot:
-  addVolumeFraction(loafercreek[1:10, ], colname='total_frags_pct')
-  
-  # add diagnostic horizons
-  addDiagnosticBracket(loafercreek[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
-}
-}
-}
-
-
-\keyword{datasets}
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/soilDB-package.R
+\docType{data}
+\name{loafercreek}
+\alias{loafercreek}
+\alias{gopheridge}
+\alias{mineralKing}
+\title{Example \code{SoilProfilecollection} Objects Returned by \code{fetchNASIS}.}
+\description{
+Several examples of soil profile collections returned by
+\code{fetchNASIS(from='pedons')} as \code{SoilProfileCollection} objects.
+}
+\examples{
+
+\donttest{
+if(require("aqp")) {
+# load example dataset
+  data("gopheridge")
+  
+  # what kind of object is this?
+  class(gopheridge)
+  
+  # how many profiles?
+  length(gopheridge)
+  
+  # there are 60 profiles, this calls for a split plot
+  par(mar=c(0,0,0,0), mfrow=c(2,1))
+  
+  # plot soil colors
+  plot(gopheridge[1:30, ], name='hzname', color='soil_color')
+  plot(gopheridge[31:60, ], name='hzname', color='soil_color')
+  
+  # need a larger top margin for legend
+  par(mar=c(0,0,4,0), mfrow=c(2,1))
+  # generate colors based on clay content
+  plot(gopheridge[1:30, ], name='hzname', color='clay')
+  plot(gopheridge[31:60, ], name='hzname', color='clay')
+  
+  # single row and no labels
+  par(mar=c(0,0,0,0), mfrow=c(1,1))
+  # plot soils sorted by depth to contact
+  plot(gopheridge, name='', print.id=FALSE, plot.order=order(gopheridge$bedrckdepth))
+  
+  # plot first 10 profiles
+  plot(gopheridge[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+  
+  # add rock fragment data to plot:
+  addVolumeFraction(gopheridge[1:10, ], colname='total_frags_pct')
+  
+  # add diagnostic horizons
+  addDiagnosticBracket(gopheridge[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+  
+  ## loafercreek
+  data("loafercreek")
+  # plot first 10 profiles
+  plot(loafercreek[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+  
+  # add rock fragment data to plot:
+  addVolumeFraction(loafercreek[1:10, ], colname='total_frags_pct')
+  
+  # add diagnostic horizons
+  addDiagnosticBracket(loafercreek[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+}
+}
+
+}
+\keyword{datasets}
diff --git a/man/local_NASIS_defined.Rd b/man/local_NASIS_defined.Rd
index 6ef0f0fb..ce80c0d4 100644
--- a/man/local_NASIS_defined.Rd
+++ b/man/local_NASIS_defined.Rd
@@ -2,18 +2,23 @@
 % Please edit documentation in R/openNASISchannel.R
 \name{local_NASIS_defined}
 \alias{local_NASIS_defined}
-\title{Check for presence of `nasis_local` ODBC data source}
+\title{Check for presence of \code{nasis_local} ODBC data source}
 \usage{
-local_NASIS_defined()
+local_NASIS_defined(dsn = NULL)
+}
+\arguments{
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: NULL}
 }
 \value{
 logical
 }
 \description{
-Check for presence of `nasis_local` ODBC data source
+Check for presence of \code{nasis_local} ODBC data source
 }
 \examples{
 
+
 if(local_NASIS_defined()) {
   # use fetchNASIS or some other lower-level fetch function
 } else {
diff --git a/man/mix_and_clean_colors.Rd b/man/mix_and_clean_colors.Rd
new file mode 100644
index 00000000..98ceb30e
--- /dev/null
+++ b/man/mix_and_clean_colors.Rd
@@ -0,0 +1,22 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/mix_and_clean_colors.R
+\name{mix_and_clean_colors}
+\alias{mix_and_clean_colors}
+\title{Mix and Clean Colors}
+\usage{
+mix_and_clean_colors(x, wt = "pct", backTransform = FALSE)
+}
+\arguments{
+\item{x}{a \code{data.frame} object containing sRGB coordinates (\code{'r'}, \code{'g'}, \code{'b'}) in [0,1]}
+
+\item{wt}{fractional weights, usually area of hz face}
+
+\item{backTransform}{logical, should the mixed sRGB representation of soil
+color be transformed to closest Munsell chips? This is performed by}
+}
+\value{
+A data.frame containing mixed colors
+}
+\description{
+Deprecated: only used in PedonPC functionality; use \code{estimateColorMixture} instead
+}
diff --git a/man/mukey.wcs.Rd b/man/mukey.wcs.Rd
index eda579ee..0b42915e 100644
--- a/man/mukey.wcs.Rd
+++ b/man/mukey.wcs.Rd
@@ -25,8 +25,8 @@ Download chunks of the gNATSGO or gSSURGO map unit key grid via bounding-box fro
 \code{aoi} should be specified as either a \code{Spatial*}, \code{sf}, \code{sfc} or \code{bbox} object or a \code{list} containing:
 
 \describe{
-  \item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
-  \item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
+\item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
+\item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
 }
 
 The WCS query is parameterized using \code{raster::extent} derived from the above AOI specification, after conversion to the native CRS (EPSG:6350) of the gNATSGO / gSSURGO grid.
diff --git a/man/parseWebReport.Rd b/man/parseWebReport.Rd
index d25ac569..9ab03a79 100644
--- a/man/parseWebReport.Rd
+++ b/man/parseWebReport.Rd
@@ -1,30 +1,42 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/parseWebReport.R
 \name{parseWebReport}
 \alias{parseWebReport}
-
 \title{Parse contents of a web report, based on supplied arguments.}
-\description{Parse contents of a web report, based on supplied arguments.}
 \usage{
 parseWebReport(url, args, index = 1)
 }
-
 \arguments{
-  \item{url}{Base URL to a LIMS/NASIS web report.}
-  \item{args}{List of named arguments to send to report, see details.}
-  \item{index}{Integer index specifying the table to return, or, NULL for a list of tables}
-}
-
-\details{Report argument names can be inferred by inspection of the HTML source associated with any given web report.}
+\item{url}{Base URL to a LIMS/NASIS web report.}
 
-\value{A \code{data.frame} object in the case of a single integer passed to \code{index}, a \code{list} object in the case of an integer vector or NULL passed to \code{index}.}
-
-\author{D.E. Beaudette and S.M. Roecker}
-
-\keyword{ IO }
-
-\note{Most web reports are for internal use only.}
+\item{args}{List of named arguments to send to report, see details.}
 
+\item{index}{Integer index specifying the table to return, or, NULL for a
+list of tables}
+}
+\value{
+A \code{data.frame} object in the case of a single integer passed to
+\code{index}, a \code{list} object in the case of an integer vector or NULL
+passed to \code{index}.
+}
+\description{
+Parse contents of a web report, based on supplied arguments.
+}
+\details{
+Report argument names can be inferred by inspection of the HTML source
+associated with any given web report.
+}
+\note{
+Most web reports are for internal use only.
+}
 \examples{
+
 \donttest{
 # pending
 }
+
+}
+\author{
+D.E. Beaudette and S.M. Roecker
 }
+\keyword{IO}
diff --git a/man/processSDA_WKT.Rd b/man/processSDA_WKT.Rd
index 0c43b8bd..4bb56dbc 100644
--- a/man/processSDA_WKT.Rd
+++ b/man/processSDA_WKT.Rd
@@ -7,7 +7,8 @@
 processSDA_WKT(d, g = "geom", p4s = "+proj=longlat +datum=WGS84")
 }
 \arguments{
-\item{d}{\code{data.frame} returned by \code{SDA_query}, containing WKT representation of geometry}
+\item{d}{\code{data.frame} returned by \code{SDA_query}, containing WKT
+representation of geometry}
 
 \item{g}{name of column in \code{d} containing WKT geometry}
 
@@ -17,13 +18,16 @@ processSDA_WKT(d, g = "geom", p4s = "+proj=longlat +datum=WGS84")
 A \code{Spatial*} object.
 }
 \description{
-This is a helper function, commonly used with \code{SDA_query} to extract WKT (well-known text) representation of geometry to an sp-class object.
+This is a helper function, commonly used with \code{SDA_query} to extract
+WKT (well-known text) representation of geometry to an sp-class object.
 }
 \details{
-The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov}. See the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for detailed examples.
+The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov}.
+See the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for detailed examples.
 }
 \note{
-This function requires the `httr`, `jsonlite`, `XML`, and `rgeos` packages.
+This function requires the \code{httr}, \code{jsonlite}, \code{XML},
+and \code{rgeos} packages.
 }
 \author{
 D.E. Beaudette
diff --git a/man/siblings.Rd b/man/siblings.Rd
index 47a62b91..0c36e4e6 100644
--- a/man/siblings.Rd
+++ b/man/siblings.Rd
@@ -1,45 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/siblings.R
 \name{siblings}
 \alias{siblings}
-
 \title{Lookup siblings and cousins for a given soil series.}
-\description{Lookup siblings and cousins for a given soil series, from the current fiscal year SSURGO snapshot via SoilWeb.}
 \usage{
-siblings(s, only.major=FALSE, component.data = FALSE, cousins = FALSE)
+siblings(s, only.major = FALSE, component.data = FALSE, cousins = FALSE)
 }
-
 \arguments{
-  \item{s}{character vector, the name of a single soil series, case-insensitive.}
-  \item{only.major}{logical, should only return siblings that are major components}
-  \item{component.data}{logical, should component data for siblings (and optionally cousins) be returned?}
-  \item{cousins}{logical, should siblings-of-siblings (cousins) be returned?}
-}
-
-\details{The siblings of any given soil series are defined as those soil series (major and minor component) that share a parent map unit with the named series (as a major component). Cousins are siblings of siblings. Data are sourced from SoilWeb which maintains a copy of the current SSURGO snapshot.}
+\item{s}{character vector, the name of a single soil series,
+case-insensitive.}
 
-\value{
-\describe{
-	\item{sib}{\code{data.frame} containing siblings, major component flag, and number of co-occurrences}
-	\item{sib.data}{\code{data.frame} containing sibling component data}
-	\item{cousins}{\code{data.frame} containing cousins, major component flag, and number of co-occurrences}
-	\item{cousin.data}{\code{data.frame} containing cousin component data}
-	} 
-}
+\item{only.major}{logical, should only return siblings that are major
+components}
 
-\references{
-\href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{soilDB Soil Series Query Functionality}
+\item{component.data}{logical, should component data for siblings (and
+optionally cousins) be returned?}
 
-\href{http://ncss-tech.github.io/AQP/soilDB/siblings.html}{Related tutorial.}
+\item{cousins}{logical, should siblings-of-siblings (cousins) be returned?}
 }
-
-\author{
-D.E. Beaudette
+\value{
+\describe{ \item{sib}{\code{data.frame} containing siblings, major
+component flag, and number of co-occurrences}
+\item{sib.data}{\code{data.frame} containing sibling component data}
+\item{cousins}{\code{data.frame} containing cousins, major component flag,
+and number of co-occurrences} \item{cousin.data}{\code{data.frame}
+containing cousin component data} }
 }
-
-\seealso{
-\link{OSDquery}, \link{siblings}, \link{fetchOSD}
+\description{
+Lookup siblings and cousins for a given soil series, from the current fiscal
+year SSURGO snapshot via SoilWeb.
+}
+\details{
+The siblings of any given soil series are defined as those soil series
+(major and minor component) that share a parent map unit with the named
+series (as a major component). Cousins are siblings of siblings. Data are
+sourced from SoilWeb which maintains a copy of the current SSURGO snapshot.
 }
-
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
     curl::has_internet()) {
@@ -54,7 +52,18 @@ if(requireNamespace("curl") &
     x$sib
 }
 }
-}
-
-\keyword{ manip }
 
+}
+\references{
+\itemize{
+\item \href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{Soil Series Query Functions}
+\item \href{http://ncss-tech.github.io/AQP/soilDB/siblings.html}{Soil "Siblings" Tutorial}
+}
+}
+\seealso{
+\link{OSDquery}, \link{siblings}, \link{fetchOSD}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/man/simplifyColorData.Rd b/man/simplifyColorData.Rd
index acdd2b6f..fba5aaf1 100644
--- a/man/simplifyColorData.Rd
+++ b/man/simplifyColorData.Rd
@@ -1,38 +1,59 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/simplifyColorData.R
 \name{simplifyColorData}
 \alias{simplifyColorData}
-\alias{mix_and_clean_colors}
-
 \title{Simplify Color Data by ID}
-\description{Simplify multiple Munsell color observations associated with each horizon.}
 \usage{
 simplifyColorData(d, id.var = "phiid", wt = "colorpct", bt = FALSE)
-mix_and_clean_colors(x, wt='pct', backTransform = FALSE)
 }
-
 \arguments{
-  \item{d}{a \code{data.frame} object, typically returned from NASIS, see details}
-  \item{id.var}{character vector with the name of the column containing an ID that is unique among all horizons in \code{d}}
-  \item{x}{a \code{data.frame} object containing sRGB coordinates associated with a group of colors to mix}
-  \item{wt}{a character vector with the name of the column containing color weights for mixing}
-  \item{bt}{logical, should the mixed sRGB representation of soil color be transformed to closest Munsell chips? This is performed by \code{aqp::rgb2Munsell}}  
-  \item{backTransform}{logical, should the mixed sRGB representation of soil color be transformed to closest Munsell chips? This is performed by \code{aqp::rgb2Munsell}}
-}
-
-\details{
-This function is mainly intended for the processing of NASIS pedon/horizon data which may or may not contain multiple colors per horizon/moisture status combination. \code{simplifyColorData} will "mix" multiple colors associated with horizons in \code{d}, according to IDs specified by \code{id.var}, using "weights" (area percentages) specified by the \code{wt} argument to \code{mix_and_clean_colors}.
-
-Note that this function doesn't actually simulate the mixture of pigments on a surface, rather, "mixing" is approximated via weighted average in the CIELAB colorspace.
+\item{d}{a \code{data.frame} object, typically returned from NASIS, see
+details}
 
-The \code{simplifyColorData} function can be applied to data sources other than NASIS by careful use of the \code{id.var} and \code{wt} arguments. However, \code{d} must contain Munsell colors split into columns named "colorhue", "colorvalue", and "colorchroma". In addition, the moisture state ("Dry" or "Moist") must be specified in a column named "colormoistst".
+\item{id.var}{character vector with the name of the column containing an ID
+that is unique among all horizons in \code{d}}
 
-The \code{mix_and_clean_colors} function can be applied to arbitrary data sources as long as \code{x} contains sRGB coordinates in columns named "r", "g", and "b". This function should be applied to chunks of rows within which color mixtures make sense.
+\item{wt}{a character vector with the name of the column containing color
+weights for mixing}
 
-There are examples in \href{http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html}{the KSSL data tutorial} and \href{http://ncss-tech.github.io/AQP/soilDB/mixing-soil-color-data.html}{the soil color mixing tutorial}.
+\item{bt}{logical, should the mixed sRGB representation of soil color be
+transformed to closest Munsell chips? This is performed by
+\code{aqp::rgb2Munsell}
+\code{aqp::rgb2Munsell}}
+}
+\description{
+Simplify multiple Munsell color observations associated with each horizon.
+}
+\details{
+This function is mainly intended for the processing of NASIS pedon/horizon
+data which may or may not contain multiple colors per horizon/moisture
+status combination. \code{simplifyColorData} will "mix" multiple colors
+associated with horizons in \code{d}, according to IDs specified by
+\code{id.var}, using "weights" (area percentages) specified by the \code{wt}
+argument to \code{mix_and_clean_colors}.
+
+Note that this function doesn't actually simulate the mixture of pigments on
+a surface, rather, "mixing" is approximated via weighted average in the
+CIELAB colorspace.
+
+The \code{simplifyColorData} function can be applied to data sources other
+than NASIS by careful use of the \code{id.var} and \code{wt} arguments.
+However, \code{d} must contain Munsell colors split into columns named
+"colorhue", "colorvalue", and "colorchroma". In addition, the moisture state
+("Dry" or "Moist") must be specified in a column named "colormoistst".
+
+The \code{mix_and_clean_colors} function can be applied to arbitrary data
+sources as long as \code{x} contains sRGB coordinates in columns named "r",
+"g", and "b". This function should be applied to chunks of rows within which
+color mixtures make sense.
+
+Examples:
+\itemize{
+\item \href{http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html}{KSSL data}
+\item \href{http://ncss-tech.github.io/AQP/soilDB/mixing-soil-color-data.html}{soil color mixing tutorial}
+}
+}
+\author{
+D.E. Beaudette
 }
-
-
-\author{D.E. Beaudette}
-
-
 \keyword{manip}
-
diff --git a/man/simplifyFragmentData.Rd b/man/simplifyFragmentData.Rd
new file mode 100644
index 00000000..8f6f2072
--- /dev/null
+++ b/man/simplifyFragmentData.Rd
@@ -0,0 +1,44 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/simplfyFragmentData.R
+\name{simplifyFragmentData}
+\alias{simplifyFragmentData}
+\alias{simplfyFragmentData}
+\alias{simplifyArtifactData}
+\title{Simplify Coarse Fraction Data}
+\usage{
+simplifyFragmentData(rf, id.var, nullFragsAreZero = TRUE)
+}
+\arguments{
+\item{rf}{a \code{data.frame} object, typically returned from NASIS, see
+details}
+
+\item{id.var}{character vector with the name of the column containing an ID
+that is unique among all horizons in \code{rf}}
+
+\item{nullFragsAreZero}{should fragment volumes of NULL be interpreted as 0?
+(default: TRUE), see details}
+}
+\description{
+Simplify multiple coarse fraction (>2mm) records by horizon.
+}
+\details{
+This function is mainly intended for the processing of NASIS pedon/horizon
+data which contains multiple coarse fragment descriptions per horizon.
+\code{simplifyFragmentData} will "sieve out" coarse fragments into the USDA
+classes, split into hard and para- fragments.
+
+The \code{simplifyFragmentData} function can be applied to data sources
+other than NASIS by careful use of the \code{id.var} argument. However,
+\code{rf} must contain coarse fragment volumes in the column "fragvol",
+fragment size (mm) in columns "fragsize_l", "fragsize_r", "fragsize_h", and
+fragment cementation class in "fraghard".
+
+Examples:
+\itemize{
+\item \href{http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html}{KSSL data}
+}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/man/soilDB-package.Rd b/man/soilDB-package.Rd
index 930f7ae7..810866af 100644
--- a/man/soilDB-package.Rd
+++ b/man/soilDB-package.Rd
@@ -1,15 +1,22 @@
-\name{soilDB-package}
-\alias{soilDB.env}
-\alias{soilDB-package}
-\alias{soilDB}
-\docType{package}
-\title{Soil Database Interface}
-\description{This package provides methods for extracting soils information from local PedonPC and AK Site databases (MS Access format), local NASIS databases (MS SQL Server), and the SDA webservice. Currently USDA-NCSS data sources are supported, however, there are plans to develop interfaces to outside systems such as the Global Soil Mapping project.}
-\details{
-It can be difficult to locate all of the dependencies required for sending/processing SOAP requests, especially on UNIX-like operating systems. Windows binary packages for the dependencies can be found \href{http://www.stats.ox.ac.uk/pub/RWin/bin/windows/contrib/2.15/}{here}. See \code{\link{fetchPedonPC}} for a simple wrapper function that should suffice for typical site/pedon/hz queries. An introduction to the soilDB package can be found \href{https://r-forge.r-project.org/scm/viewvc.php/*checkout*/docs/soilDB/soilDB-Intro.html?root=aqp}{here}.
-}
-\author{J.M. Skovlin and D.E. Beaudette}
-\keyword{package}
-\seealso{\code{\link{fetchPedonPC}, \link{fetchNASIS}, \link{SDA_query}, \link{loafercreek}}}
-
-
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/soilDB-package.R
+\docType{package}
+\name{soilDB-package}
+\alias{soilDB-package}
+\alias{soilDB.env}
+\alias{soilDB}
+\title{Soil Database Interface}
+\description{
+This package provides methods for extracting soils information from local
+PedonPC and AK Site databases (MS Access format), local NASIS databases (MS
+SQL Server), and the SDA web service. Currently USDA-NCSS data sources are
+supported, however, there are plans to develop interfaces to outside systems
+such as the Global Soil Mapping project.
+}
+\seealso{
+\code{\link{fetchPedonPC}, \link{fetchNASIS}, \link{SDA_query}, \link{loafercreek}}
+}
+\author{
+J.M. Skovlin, D.E. Beaudette, S.M Roecker, A.G. Brown
+}
+\keyword{package}
diff --git a/man/uncode.Rd b/man/uncode.Rd
index 43d81272..0a577a7d 100644
--- a/man/uncode.Rd
+++ b/man/uncode.Rd
@@ -1,39 +1,76 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/uncode.R
 \name{uncode}
-\alias{metadata}
 \alias{uncode}
+\alias{metadata}
 \alias{code}
-
 \title{Convert coded values returned from NASIS and SDA queries to factors}
-\description{These functions convert the coded values returned from NASIS or SDA to factors (e.g. 1 = Alfisols) using the metadata tables from NASIS. For SDA the metadata is pulled from a static snapshot in the soilDB package (/data/metadata.rda).}
 \usage{
-uncode(df, invert = FALSE, db = "NASIS", 
-       droplevels = FALSE,
-       stringsAsFactors = default.stringsAsFactors()
-       )
-code(df, ...)
+uncode(
+  df,
+  invert = FALSE,
+  db = "NASIS",
+  droplevels = FALSE,
+  stringsAsFactors = default.stringsAsFactors(),
+  dsn = NULL
+)
 }
-%- maybe also 'usage' for other objects documented here.
 \arguments{
-  \item{df}{data.frame}
-  \item{invert}{converts the code labels back to their coded values (FALSE)}
-  \item{db}{label specifying the soil database the data is coming from, which indicates whether or not to query metadata from local NASIS database ("NASIS") or use soilDB-local snapshot ("LIMS" or "SDA")}
-  \item{droplevels}{logical: indicating whether to drop unused levels in classifying factors. This is useful when a class has large number of unused classes, which can waste space in tables and figures.}
-  \item{stringsAsFactors}{logical: should character vectors be converted to factors? The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
-  \item{\dots}{arguments passed on to \code{uncode}}
-  }
-  
-\details{These functions convert the coded values returned from NASIS into their plain text representation. It duplicates the functionality of the CODELABEL function found in NASIS. This function is primarily intended to be used internally by other soilDB R functions, in order to minimizes the need to manually convert values. 
+\item{df}{data.frame}
+
+\item{invert}{converts the code labels back to their coded values (\code{FALSE})}
 
-The function works by iterating through the column names in a data frame and looking up whether they match any of the ColumnPhysicalNames found in the metadata domain tables. If matches are found then the columns coded values are converted to their corresponding factor levels. Therefore it is not advisable to reuse column names from NASIS unless the contents match the range of values and format found in NASIS. Otherwise uncode() will convert their values to NA.
+\item{db}{label specifying the soil database the data is coming from, which
+indicates whether or not to query metadata from local NASIS database
+("NASIS") or use soilDB-local snapshot ("LIMS" or "SDA")}
 
-When data is being imported from NASIS, the metadata tables are sourced directly from NASIS. When data is being imported from SDA or the NASIS Web Reports, the metadata is pulled from a static snapshot in the soilDB package.
+\item{droplevels}{logical: indicating whether to drop unused levels in
+classifying factors. This is useful when a class has large number of unused
+classes, which can waste space in tables and figures.}
 
-Beware the default is to return the values as factors rather than strings. While strings are generally preferable, factors make plotting more convenient. Generally the factor level ordering returned by uncode() follows the naturally ordering of categories that would be expected (e.g. sand, silt, clay).
+\item{stringsAsFactors}{logical: should character vectors be converted to
+factors?}
+
+\item{dsn}{Optional: path to local SQLite database containing NASIS
+table structure; default: \code{NULL}}
+}
+\value{
+A data frame with the results.
+}
+\description{
+These functions convert the coded values returned from NASIS or SDA to
+factors (e.g. 1 = Alfisols) using the metadata tables from NASIS. For SDA
+the metadata is pulled from a static snapshot in the soilDB package
+(/data/metadata.rda).
 }
+\details{
+These functions convert the coded values returned from NASIS into their
+plain text representation. It duplicates the functionality of the CODELABEL
+function found in NASIS. This function is primarily intended to be used
+internally by other soilDB R functions, in order to minimizes the need to
+manually convert values.
 
-\value{A data frame with the results.}
-\author{Stephen Roecker}
+The function works by iterating through the column names in a data frame and
+looking up whether they match any of the ColumnPhysicalNames found in the
+metadata domain tables. If matches are found then the columns coded values
+are converted to their corresponding factor levels. Therefore it is not
+advisable to reuse column names from NASIS unless the contents match the
+range of values and format found in NASIS. Otherwise uncode() will convert
+their values to NA.
+
+When data is being imported from NASIS, the metadata tables are sourced
+directly from NASIS. When data is being imported from SDA or the NASIS Web
+Reports, the metadata is pulled from a static snapshot in the soilDB
+package.
+
+Beware the default is to return the values as factors rather than strings.
+While strings are generally preferable, factors make plotting more
+convenient. Generally the factor level ordering returned by uncode() follows
+the naturally ordering of categories that would be expected (e.g. sand,
+silt, clay).
+}
 \examples{
+
 \donttest{
 if(requireNamespace("curl") &
     curl::has_internet() &
@@ -41,13 +78,15 @@ if(requireNamespace("curl") &
   # query component by nationalmusym
   comp <- fetchSDA(WHERE = "nationalmusym = '2vzcp'")
   s <- site(comp)
-  
+
   # use SDA uncoding domain via db argument
   s <- uncode(s,  db="SDA")
   levels(s$taxorder)
 }
 }
+
+}
+\author{
+Stephen Roecker
 }
-% Add one or more standard keywords, see file 'KEYWORDS' in the
-% R documentation directory.
-\keyword{manip}% use one of  RShowDoc("KEYWORDS")
+\keyword{manip}
diff --git a/man/us_ss_timeline.Rd b/man/us_ss_timeline.Rd
index f18b8557..c0e8df07 100644
--- a/man/us_ss_timeline.Rd
+++ b/man/us_ss_timeline.Rd
@@ -1,33 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/soilDB-package.R
+\docType{data}
 \name{us_ss_timeline}
 \alias{us_ss_timeline}
-\docType{data}
-
-\title{
-Timeline of US Published Soil Surveys
-}
-
-\description{
-This dataset contains the years of each US Soil Survey was published.
-}
-
-\usage{data("us_ss_timeline")}
-
+\title{Timeline of US Published Soil Surveys}
 \format{
-  A data frame with 5209 observations on the following 5 variables.
-  \describe{
-    \item{\code{ssa}}{Soil Survey name, a character vector}
-    \item{\code{year}}{year of publication, a numeric vector}
-    \item{\code{pdf}}{does a pdf exists, a logical vector}
-    \item{\code{state}}{State abbreviation, a character vector}
-  }
+A data.frame with 5209 observations on the following 5 variables.
+\itemize{
+\item \code{"ssa"}: Soil Survey name, a character vector
+\item \code{"year"}: Year of publication, a numeric vector
+\item \code{"pdf"}: Does a manuscript PDF document exist? a logical vector
+\item \code{"state"}: State abbreviation, a character vector
 }
-
-\details{
-This data was web scraped from the NRCS Soils Website. The scraping procedure and a example plot are included in the examples section below.
 }
-
 \source{
 https://www.nrcs.usda.gov/wps/portal/nrcs/soilsurvey/soils/survey/state/
 }
-
+\description{
+This dataset contains the years of each US Soil Survey was published.
+}
+\details{
+This data was web scraped from the NRCS Soils Website. The scraping
+procedure and a example plot are included in the examples section below.
+}
 \keyword{datasets}
diff --git a/man/waterDayYear.Rd b/man/waterDayYear.Rd
index f28b3fb1..c087a42d 100644
--- a/man/waterDayYear.Rd
+++ b/man/waterDayYear.Rd
@@ -1,38 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/waterDayYear.R
 \name{waterDayYear}
 \alias{waterDayYear}
-
 \title{Compute Water Day and Year}
-\description{Compute "water" day and year, based on the end of the typical or legal dry season. This is September 30 in California.}
-
 \usage{
 waterDayYear(d, end = "09-30")
 }
-
 \arguments{
-  \item{d}{anything the can be safely converted to \code{PPOSIXlt}}
-  \item{end}{"MM-DD" notation for end of water year}
-}
-
-\details{This function doesn't know about leap-years. Probably worth checking.}
+\item{d}{anything the can be safely converted to \code{PPOSIXlt}}
 
+\item{end}{"MM-DD" notation for end of water year}
+}
 \value{
-A \code{data.frame} object with the following
-  \item{wy}{the "water year"}
-  \item{wd}{the "water day"}
+A \code{data.frame} object with the following \item{wy}{the "water
+year"} \item{wd}{the "water day"}
 }
-
-\references{Ideas borrowed from:
-\url{https://github.com/USGS-R/dataRetrieval/issues/246} and
-\url{https://stackoverflow.com/questions/48123049/create-day-index-based-on-water-year}
+\description{
+Compute "water" day and year, based on the end of the typical or legal dry
+season. This is September 30 in California.
+}
+\details{
+This function doesn't know about leap-years. Probably worth checking.
 }
-
-\author{D.E. Beaudette}
-
-
 \examples{
+
 # try it
 waterDayYear('2019-01-01')
-}
-
-\keyword{ manip }% use one of  RShowDoc("KEYWORDS")
 
+}
+\references{
+Ideas borrowed from:
+\url{https://github.com/USGS-R/dataRetrieval/issues/246} and
+\url{https://stackoverflow.com/questions/48123049/create-day-index-based-on-water-year}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/misc/DBI-uncoding-test.R b/misc/DBI-uncoding-test.R
new file mode 100644
index 00000000..78a41111
--- /dev/null
+++ b/misc/DBI-uncoding-test.R
@@ -0,0 +1,29 @@
+# uncoding from a static local NASIS instance
+library(aqp)
+library(soilDB)
+library(tibble)
+
+dsn <- NULL #"~/workspace/NASISlite/nasis_local.db"
+SS <- FALSE
+stringsAsFactors <- FALSE
+
+# check connection
+local_NASIS_defined(dsn = dsn)
+
+# create connection
+conn <- NASIS(dsn)
+
+# bare uncode against a local db
+tibble(head(uncode(dbQueryNASIS(conn,
+                                "SELECT phiid, bounddistinct, boundtopo FROM phorizon"),
+                                dsn = dsn)))
+
+# fetchNASIS test
+f <- fetchNASIS(dsn = dsn)
+
+# verify codes properly converted to labels
+f
+
+# get_comonth with fill=TRUE
+
+get_comonth_from_NASIS_db(fill = TRUE, dsn = dsn)
diff --git a/misc/NASIS-RV-copedon.R b/misc/NASIS-RV-copedon.R
new file mode 100644
index 00000000..9ada1185
--- /dev/null
+++ b/misc/NASIS-RV-copedon.R
@@ -0,0 +1,140 @@
+# make RV copedon snapshot work with fetchNASIS / SQLite backend
+
+library(soilDB)
+
+SS <- FALSE
+nullFragsAreZero <- TRUE
+stringsAsFactors <- FALSE
+
+dsn <- "C:/Geodata/soils/NASIS-data_patched.sqlite"
+copedon_rv_data <- "C:/Geodata/soils/copedon-data.rds"
+copedon <- readRDS(copedon_rv_data)
+
+### 
+### BEGIN -- PATCHES TO THE NASIS-data.sqlite "Pedon Snapshot" for fetchNASIS
+### 
+# res1 <- dbQueryNASIS(soilDB:::.openNASISchannel(dsn),
+#                      "SELECT geomfname, geomfiid, geomftiidref FROM geomorfeat")
+# res2 <- dbQueryNASIS(soilDB:::.openNASISchannel(dsn),
+#                      "SELECT geomftname, geomftiid FROM geomorfeattype")
+# 
+# # # get the geomorphic features tables in the SQLite snapshot
+# library(DBI)
+# con <- DBI::dbConnect(RSQLite::SQLite(), dsn)
+# DBI::dbWriteTable(con, "geomorfeat", res1, overwrite = TRUE)
+# DBI::dbWriteTable(con, "geomorfeattype", res2, overwrite = TRUE)
+# DBI::dbDisconnect(con)
+
+# # fix the standard lat/long/gps names truncated to 16 characters 
+# con <- DBI::dbConnect(RSQLite::SQLite(), dsn)
+# sitetab <- DBI::dbReadTable(con, "site")
+# sitetab$longstddecimaldegrees <- sitetab$longstddecimalde
+# sitetab$latstddecimaldegrees <- sitetab$latstddecimaldeg
+# sitetab$gpspositionalerror <- sitetab$gpspositionalerr
+# DBI::dbWriteTable(con, "site", sitetab, overwrite = TRUE)
+# DBI::dbDisconnect(con)
+
+# # fix the petaxhistory record ID name truncated to 16 characters 
+# con <- DBI::dbConnect(RSQLite::SQLite(), dsn)
+# petaxmtab <- DBI::dbReadTable(con, "petaxhistmoistcl")
+# petaxmtab$pedtaxhistoryiidref <- petaxmtab$pedtaxhistoryiid
+# DBI::dbWriteTable(con, "petaxhistmoistcl", petaxmtab, overwrite = TRUE)
+# petaxotab <- DBI::dbReadTable(con, "petxhistfmother")
+# petaxotab$pedtaxhistoryiidref <- petaxotab$pedtaxhistoryiid
+# DBI::dbWriteTable(con, "petxhistfmother", petaxotab, overwrite = TRUE)
+# DBI::dbDisconnect(con)
+
+# # fix the different names in MetadataDomainDetail
+# con <- DBI::dbConnect(RSQLite::SQLite(), dsn)
+# metatab <- DBI::dbReadTable(con, "MetadataDomainDetail")
+# metatab$DomainID <- metatab$domain_id
+# metatab$ChoiceValue <- metatab$choice_id
+# metatab$ChoiceName <- metatab$choice_label
+# DBI::dbWriteTable(con, "MetadataDomainDetail", metatab, overwrite = TRUE)
+# DBI::dbDisconnect(con)
+
+###
+### END PATCHES TO PEDON SNAPSHOT for fetchNASIS
+###
+
+system.time(f <- fetchNASIS(
+                SS = SS,
+                dsn = dsn,
+                rmHzErrors = FALSE
+              ))
+# TODO: handle uncoding options
+
+library(aqp)
+aqp_df_class(f) <- "data.table"
+f <- rebuildSPC(f)
+save(f, file = "C:/Geodata/soils/fetchNASIS-data-2.rda")
+# load("C:/Geodata/soils/fetchNASIS-data-2.rda")
+
+system.time(good.ids <- checkHzDepthLogic(f, fast = TRUE))
+save(good.ids, file = "C:/Geodata/soils/fetchNASIS-data-goodids-2.rda")
+
+f.sub <- subset(f, good.ids$valid)
+all(good.ids$valid)
+site(f.sub) <- good.ids
+all(f.sub$valid)
+
+## from full set, you can do subset operations on any site level var
+mollisols <- subset(f, f$taxorder == "mollisols")
+
+## here, we match peiid against a lookup table of RV component pedon peiids
+f.cp <- subset(f.sub, profile_id(f.sub) %in% unique(copedon$peiid))
+
+library(dplyr)
+
+site(f.cp) %>%
+  count(taxgrtgroup) %>%
+  filter(n > 30)
+
+# calculate the expected MTR for ~27k RV copedons
+res <- profileApply(f.cp, mollic.thickness.requirement, clay.attr = 'clay')
+f.cp$mollic_thickness_requirement <- res
+
+## 27790 elements
+save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon-2.rda")
+
+copedon_rv_data <- "C:/Geodata/soils/copedon-data.rds"
+copedon <- readRDS(copedon_rv_data)
+copedon$peiid <- as.character(copedon$peiid)
+copedon$phiid <- as.character(copedon$phiid)
+
+# add mixed moist color information
+horizons(f.cp)$phiid <- as.character(horizons(f.cp)$phiid)
+horizons(f.cp) <- copedon[,c("phiid","peiid",
+                             "mxhue_moist","mxvalue_moist","mxchroma_moist",
+                             "mxhue_dry","mxvalue_dry","mxchroma_dry")]
+
+f.cp$m_hue <- f.cp$mxhue_moist
+f.cp$m_value <- f.cp$mxvalue_moist
+f.cp$m_chroma <- f.cp$mxchroma_moist
+f.cp$d_hue <- f.cp$mxhue_dry
+f.cp$d_value <- f.cp$mxvalue_dry
+f.cp$d_chroma <- f.cp$mxchroma_dry
+f.cp$soil_color <- with(horizons(f.cp), munsell2rgb(m_hue, m_value, m_chroma))
+save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon-2.rda")
+
+f.cp$is_mollic_color <- hasDarkColors(f.cp, d_value = NA)
+
+darkColorInterval <- function(p) {
+  mss <- getMineralSoilSurfaceDepth(p)
+  p.sub <- glom(p, mss, estimateSoilDepth(p))
+  if (!inherits(p.sub, 'SoilProfileCollection')) {
+    return(data.frame(peiid = profile_id(p),
+                        mineral_surface = mss,
+                        darkness_depth = NA))
+  }
+  return(data.frame(peiid = profile_id(p),
+                    mineral_surface = mss,
+                    darkness_depth = getSurfaceHorizonDepth(p.sub, pattern = "TRUE", 
+                                                            hzdesgn = "is_mollic_color"
+          )))
+}
+dcdf <- profileApply(f.cp, darkColorInterval, frameify = TRUE)
+f.cp$mineral_surface <- NULL
+f.cp$darkness_depth <- NULL
+site(f.cp) <- dcdf
+save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon-2.rda")
diff --git a/misc/NASIS-lite.R b/misc/NASIS-lite.R
new file mode 100644
index 00000000..6f066e31
--- /dev/null
+++ b/misc/NASIS-lite.R
@@ -0,0 +1,263 @@
+# the following script has been deprecated in favor of 
+#   NASIS-RV-copedon.R
+#   MollicEpipedonThickness.Rmd (SoilTaxonomy/misc/presentations)
+
+# devtools::install()
+
+library(soilDB)
+
+# SS <- FALSE
+# nullFragsAreZero <- TRUE
+# stringsAsFactors <- FALSE
+# 
+# nasislite_path <- "C:/Geodata/soils/NASIS-data.sqlite"
+# copedon_rv_data <- "C:/Geodata/soils/copedon-data.rds"
+# copedon <- readRDS(copedon_rv_data)
+
+# res1 <- dbQueryNASIS(soilDB:::.openNASISchannel(nasislite_path),
+#                      "SELECT geomfname, geomfiid, geomftiidref FROM geomorfeat")
+# res2 <- dbQueryNASIS(soilDB:::.openNASISchannel(nasislite_path),
+#                      "SELECT geomftname, geomftiid FROM geomorfeattype")
+
+# # quick hack to get the geomorphic features tables in there
+# library(DBI)
+# con <- DBI::dbConnect(RSQLite::SQLite(), "C:/Geodata/soils/NASIS-data.sqlite")
+# DBI::dbWriteTable(con, "geomorfeat", res1, overwrite = TRUE)
+# DBI::dbWriteTable(con, "geomorfeattype", res2, overwrite = TRUE)
+# DBI::dbDisconnect(con)
+
+# extended_data <- get_extended_data_from_NASIS_db(SS = SS,
+#                                                  dsn = nasislite_path,
+#                                                  nullFragsAreZero = nullFragsAreZero,
+#                                                  stringsAsFactors = stringsAsFactors)
+# f <- fetchNASIS(SS = SS, dsn = nasislite_path)
+# 
+# library(aqp)
+# aqp_df_class(f) <- "data.table"
+# f <- rebuildSPC(f)
+# save(f, file = "C:/Geodata/soils/fetchNASIS-data.rda")
+# load("C:/Geodata/soils/fetchNASIS-data.rda")
+
+# good.ids <- checkHzDepthLogic(f)
+# save(good.ids, file = "C:/Geodata/soils/fetchNASIS-data-goodids.rda")
+
+# f.sub <- subset(f, good.ids$valid)
+# all(good.ids$valid)
+ 
+## from full set, you can do subset operations on any site level var
+# mollisols <- subset(f, f$taxorder == "Mollisols")
+
+## here, we match peiid against a lookup table of RV component pedon peiids
+# f.cp <- subset(f, profile_id(f) %in% unique(copedon$peiid))
+
+# library(dplyr)
+# 
+# site(f.cp) %>%
+#   count(taxgrtgroup) %>%
+#   filter(n > 30)
+
+# calculate the expected MTR for ~27k RV copedons
+# res <- profileApply(f.cp, mollic.thickness.requirement, clay.attr = 'clay')
+# f.cp$mollic_thickness_requirement <- res
+
+## 27790 elements
+# save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon.rda")
+# 
+# 
+# color analysis
+# library(aqp)
+load("C:/Geodata/soils/fetchNASIS-rv-copedon.rda")
+# 
+# copedon_rv_data <- "C:/Geodata/soils/copedon-data.rds"
+# copedon <- readRDS(copedon_rv_data)
+# copedon$peiid <- as.character(copedon$peiid)
+# copedon$phiid <- as.character(copedon$phiid)
+# 
+# # add mixed moist color information
+# horizons(f.cp)$phiid <- as.character(horizons(f.cp)$phiid)
+# horizons(f.cp) <- copedon[,c("phiid","peiid",
+#                              "mxhue_moist","mxvalue_moist","mxchroma_moist",
+#                              "mxhue_dry","mxvalue_dry","mxchroma_dry")]
+
+# f.cp$m_hue <- f.cp$mxhue_moist
+# f.cp$m_value <- f.cp$mxvalue_moist
+# f.cp$m_chroma <- f.cp$mxchroma_moist
+# f.cp$d_hue <- f.cp$mxhue_dry
+# f.cp$d_value <- f.cp$mxvalue_dry
+# f.cp$d_chroma <- f.cp$mxchroma_dry
+# f.cp$soil_color <- with(horizons(f.cp), munsell2rgb(m_hue, m_value, m_chroma))
+# save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon.rda")
+
+# f.cp$is_mollic_color <- hasDarkColors(f.cp, d_value = NA)
+# 
+# darkColorInterval <- function(p) {
+#   mss <- getMineralSoilSurfaceDepth(p)
+#   p.sub <- glom(p, mss, estimateSoilDepth(p))
+#   if (!inherits(p.sub, 'SoilProfileCollection')) {
+#     return(data.frame(peiid = profile_id(p),
+#                         mineral_surface = mss,
+#                         darkness_depth = NA))
+#   }
+#   return(data.frame(peiid = profile_id(p),
+#                     mineral_surface = mss,
+#                     darkness_depth = getSurfaceHorizonDepth(p.sub, pattern = "TRUE", hzdesgn = "is_mollic_color"
+#           )))
+# }
+# dcdf <- profileApply(f.cp, darkColorInterval, frameify = TRUE)
+# f.cp$mineral_surface <- NULL
+# f.cp$darkness_depth <- NULL
+# site(f.cp) <- dcdf
+# save(f.cp, file = "C:/Geodata/soils/fetchNASIS-rv-copedon.rda")
+
+load("C:/Geodata/soils/fetchNASIS-rv-copedon.rda")
+
+library(aqp)
+library(sf)
+library(ggplot2)
+library(rnaturalearth)
+
+dat <- site(f.cp)
+
+vplotdat <- subset(dat, dat$mollic_thickness_requirement >= 18)
+vioplot::vioplot(vplotdat$mollic_thickness_requirement ~ vplotdat$taxmoistcl)
+
+do.call('rbind', lapply(split(dat, dat$taxmoistcl), function(x) {
+    quantile(x$mollic_thickness_requirement, na.rm = TRUE, probs = c(0.4,0.5,0.6,0.7,0.8,0.9))
+  }))
+
+library(dplyr, warn.conflicts = FALSE)
+dat <- dat %>% mutate(mtr_group = case_when(mollic_thickness_requirement < 18 ~ "10 cm",
+                                            mollic_thickness_requirement == 18 ~ "18 cm",
+                                            mollic_thickness_requirement > 18 &
+                                              mollic_thickness_requirement < 25 ~ "18 to 25 cm",
+                                            mollic_thickness_requirement == 25 ~ "25 cm"))
+dsplit <- split(dat, dat$mtr_group)
+
+datfilt <- dat[!is.na(dat$mtr_group), ]
+datfilt$met_mollic_moist <- (datfilt$darkness_depth >= datfilt$mollic_thickness_requirement)
+datfilt$met_20cm_moist <- (datfilt$darkness_depth >= 20)
+datfilt$met_25cm_moist <- (datfilt$darkness_depth >= 25)
+datfilt$met_30cm_moist <- (datfilt$darkness_depth >= 30)
+
+dat.sp <- datfilt[,c("peiid", "x_std", "y_std", "mtr_group",
+                     "met_mollic_moist",
+                     "met_20cm_moist", "met_25cm_moist", "met_30cm_moist")]
+
+dat.sp <- st_as_sf(dat.sp[complete.cases(dat.sp),], coords = c("x_std", "y_std"), crs = 4326)
+
+plot(subset(dat.sp[dat.sp$peiid %in% datfilt$peiid,'met_mollic_moist'], met_mollic_moist == TRUE))
+
+world <- ne_countries(scale = "medium", returnclass = "sf")
+world$.id <- 1
+world <- merge(world, data.frame(id = 1, mtr_group = unique(datfilt$mtr_group)))
+
+usa <- subset(world, admin == "United States of America")
+dat.sp <- st_crop(dat.sp, usa)
+
+ggplot(data = datfilt, aes(group = mtr_group)) +
+  facet_wrap(~ mtr_group, nrow = 2) +
+  scale_color_viridis_d(direction = -1) + 
+  ggtitle(sprintf('Mollic Epipedon Thickness Requirements in NASIS Component Representative Pedons (n = %s)', 
+                  nrow(dat.sp))) +
+  geom_sf(data = usa, fill = "#93dfb8") +
+  geom_sf(data =  dat.sp[dat.sp$peiid %in% dsplit[[1]]$peiid,], 
+          size = 0.005, aes(color = met_mollic_moist)) +
+  geom_sf(data =  dat.sp[dat.sp$peiid %in% dsplit[[2]]$peiid,], 
+          size = 0.005, aes(color = met_mollic_moist)) +
+  geom_sf(data =  dat.sp[dat.sp$peiid %in% dsplit[[3]]$peiid,], 
+          size = 0.005, aes(color = met_mollic_moist)) +
+  geom_sf(data =  dat.sp[dat.sp$peiid %in% dsplit[[4]]$peiid,], 
+          size = 0.005, aes(color = met_mollic_moist)) +
+  guides(colour = guide_legend(override.aes = list(size = 4))) + 
+  labs(color = "Meets Moist Color\n") +
+  coord_sf(crs = st_crs(2163), xlim = c(-2500000, 2500000), ylim = c(-2300000, 730000))
+
+
+datfilt_wasmollic <- filter(datfilt, met_mollic_moist == TRUE)
+dat.sp_wasmollic <- filter(dat.sp, dat.sp$peiid %in% datfilt_wasmollic$peiid)
+ggplot(data = datfilt_wasmollic, aes(group = mtr_group)) +
+  facet_wrap(~ mtr_group, nrow = 2) +
+  scale_color_viridis_d(direction = -1) + 
+  ggtitle(sprintf('Mollic Epipedon Thickness Requirements in NASIS Component Representative Pedons (n = %s)', 
+                  nrow(dat.sp_wasmollic))) +
+  geom_sf(data = usa, fill = "#93dfb8") +
+  geom_sf(data =  dat.sp_wasmollic[dat.sp_wasmollic$peiid %in% dsplit[[1]]$peiid,],
+          size = 0.005, aes(color = met_25cm_moist)) +
+  geom_sf(data =  dat.sp_wasmollic[dat.sp_wasmollic$peiid %in% dsplit[[2]]$peiid,],
+          size = 0.005, aes(color = met_25cm_moist)) +
+  geom_sf(data =  dat.sp_wasmollic[dat.sp_wasmollic$peiid %in% dsplit[[3]]$peiid,],
+          size = 0.005, aes(color = met_25cm_moist)) +
+  geom_sf(data =  dat.sp_wasmollic[dat.sp_wasmollic$peiid %in% dsplit[[4]]$peiid,],
+          size = 0.005, aes(color = met_25cm_moist)) +
+  guides(colour = guide_legend(override.aes = list(size = 4))) + 
+  labs(color = "Meets Moist Color (25cm)\n") +
+  coord_sf(crs = st_crs(2163), xlim = c(-2500000, 2500000), ylim = c(-2300000, 730000))
+
+res <- subset(datfilt_wasmollic, !met_25cm_moist)
+sort(table(res$taxgrtgroup))
+to.update <- subset(f.cp, peiid %in% res$peiid)
+
+taxonnames <- sort(table(to.update$taxonname))
+
+
+plot(subset(to.update, taxonname %in% names(taxonnames[taxonnames >= 2])))
+
+plot_series <- function(series_name) {
+  print(series_name)
+  if (series_name != "SND") {
+    ful <- subset(f.cp, taxonname == series_name)
+    upd <- subset(to.update, taxonname == series_name)
+    osd <- try(fetchOSD(series_name))
+    if (!is.null(osd)) {
+      ser <- aqp::combine(osd, subset(ful, !(peiid %in% upd$peiid)), upd)
+      ser$threshold[ser$darkness_depth >= 25] <- "Meets Requirement"
+      ser$threshold[ser$darkness_depth < 25] <- "Does Not Meet Requirement"
+      ser$threshold[is.na(ser$threshold)] <- "OSD"
+      groupedProfilePlot(ser, groups = "threshold", cex.names = 0.6, max.depth = 200, print.id=FALSE)
+      abline(h = 25, lty=3, lwd=1, col="red")
+      title(sprintf("%s (%s)", series_name, osd$family))
+    } else {
+      stop(osd)
+    }
+  }
+}
+
+lapply(tail(names(taxonnames[taxonnames >= 2]), 20), plot_series)
+
+update_mukeys <- unique(copedon[copedon$peiid %in% to.update$peiid, 'nationalmusym'])
+
+mupoly <- read_sf("E:/Geodata/soils/MLRA_2_SON_FY2021.gdb", "mupolygon")
+
+mupoly_sub <- subset(mupoly, mupoly$NATMUSYM %in% update_mukeys)
+area <- st_area(st_union(mupoly_sub$Shape))
+units(area) <- "acres"
+area
+
+plot(mupoly_sub$Shape)
+
+mupoly_sub <- merge(mupoly_sub, copedon, all.x=TRUE)
+res <- subset(f.cp, peiid %in% mupoly_sub$peiid)
+site(res) <- mupoly_sub[,c("compname","comppct_r","peiid")]
+
+res$compname
+res_moll <- subset(res, taxorder == "Mollisols")
+
+res_moll$mollic_thickness_requirement - res_moll$darkness_depth
+(25 - res_moll$darkness_depth) > 0
+
+res_moll20 <- subset(res_moll, (20 - res_moll$darkness_depth) > 0)
+
+plot(res_moll20, label = "compname")
+abline(h=18)
+
+
+res_inc <- subset(res, taxorder == "Inceptisols")
+
+res_inc$mollic_thickness_requirement - res_inc$darkness_depth
+(25 - res_inc$darkness_depth) > 0
+
+res_inc20 <- subset(res_inc, (20 - res_inc$darkness_depth) > 0)
+
+plot(res_inc20, label = "compname")
+abline(h=18)
+res_inc20$taxgrtgroup
diff --git a/misc/ca750-newpedons.R b/misc/ca750-newpedons.R
new file mode 100644
index 00000000..9577e1ef
--- /dev/null
+++ b/misc/ca750-newpedons.R
@@ -0,0 +1,86 @@
+# new CA750 data
+library(soilDB)
+library(sharpshootR)
+library(sf)
+library(raster)
+
+# use DBI connection to create a NASIS table list object and select some columns
+s.sub <- soilDB::createStaticNASIS("site_View_1")[["site_View_1"]][, c(
+  "usiteid",
+  "plssmeridian",
+  "plsstownship",
+  "plssrange",
+  "plsssection",
+  "plsssdetails",
+  "elev",
+  "slope",
+  "aspect"
+)]
+
+# rename some columns to match PLSS2LL() data.frame format
+colnames(s.sub) <- c("id","m","t","r","s","plsssdetails",
+                     "elev", "slope", "aspect")
+
+# add meridian section style etc
+s.sub$plsssdetails[grepl("meters", s.sub$plsssdetails)] <- ""
+s.sub$qq <- ""#sapply(strsplit(s.sub$plsssdetails, ","), function(x) trimws(x[1]))
+s.sub$q <- ""#sapply(strsplit(s.sub$plsssdetails, ","), function(x) trimws(x[2]))
+
+s.sub$m <- "CA21"
+s.sub$type <- "SN"
+
+is_corner <- grepl("corner$|section$|corner of$", s.sub$plsssdetails)
+corners <- gsub("(.*) corner$|(.*) section$|(.*) corner of$", "\\1\\2\\3", s.sub$plsssdetails)
+
+s.complete <- s.sub[complete.cases(s.sub) & 
+                      !s.sub$t == "NA" &
+                      !s.sub$r == "NA" & !(s.sub$r == "E") &
+                      !s.sub$s == "NA" &
+                      !s.sub$q == "NA",]
+
+s.complete$t <- paste0("T", trimws(s.complete$t))
+s.complete$r <- paste0("R", trimws(s.complete$r))
+
+spad <- paste0(ifelse(nchar(s.complete$s) == 0, "", "00"), s.complete$s)
+
+s.complete$s <- substr(spad, nchar(spad)-1, nchar(spad))
+s.complete$plssid <- sharpshootR::formatPLSS(p = s.complete)
+s.complete$plssid <- gsub("(.*A)\\d+ meters.*", "\\1", s.complete$plssid)
+s.complete <- s.complete[!grepl("NA", s.complete$plssid),]
+
+# fix(s.complete)
+res <- PLSS2LL(s.complete)
+
+# convert the longlat points from PLSS to sf
+pts <- st_as_sf(res[complete.cases(res),], 
+                coords = c("lon", "lat"), 
+                crs = st_crs(4326))
+
+# get SSA polygon
+bdy <- fetchSDA_spatial("CA750", geom.src = "sapolygon")
+
+# get raster of mukeys
+mukeyras <- mukey.wcs(ssa)
+
+# get data from SDA
+ssurgo <- SDA_query(sprintf(
+                    "SELECT * FROM legend 
+                    INNER JOIN mapunit ON legend.lkey = mapunit.lkey
+                    WHERE mukey IN %s", format_SQL_in_statement(unique(values(ras)))))
+
+# get just target survey area
+ssurgo.sub <- subset(ssurgo, areasymbol == "CA750")
+
+# get the numbers only out
+ssurgo.sub$musym <- as.numeric(gsub("(\\d+)|.*","\\1", ssurgo.sub$musym))
+
+# new raster attribute table based on numeric musyms used in (most of) CA750
+rat <- merge(levels(mukeyras)[[1]], ssurgo.sub[,c('mukey',"musym")], 
+             by.x = 'ID', by.y = 'mukey', 
+             sort = FALSE, all.x = TRUE, incomparables = NA)
+levels(mukeyras) <- rat
+
+# make a map
+plot(mukeyras, "musym")
+plot(st_transform(st_as_sf(bdy), crs(mukeyras))$geometry, add = TRUE)
+plot(st_transform(pts, crs(mukeyras))$geometry, add = TRUE)
diff --git a/misc/man-deprecated/ISSR800.wcs.Rd b/misc/man-deprecated/ISSR800.wcs.Rd
new file mode 100644
index 00000000..772a0bd6
--- /dev/null
+++ b/misc/man-deprecated/ISSR800.wcs.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/ISSR800.R
+\name{ISSR800.wcs}
+\alias{ISSR800.wcs}
+\title{ISSR-800 Web Coverage Service (WCS)}
+\usage{
+ISSR800.wcs(aoi, var, res = 800, quiet = FALSE)
+}
+\arguments{
+\item{aoi}{area of interest (AOI) defined using a \code{Spatial*}, a \code{sf}, \code{sfc} or \code{bbox} object or a \code{list}, see details}
+
+\item{var}{ISSR-800 grid name, see details}
+
+\item{res}{grid resolution, units of meters. The native resolution of ISSR-800 grids (this WCS) is 800m.}
+
+\item{quiet}{logical, passed to \code{download.file} to enable / suppress URL and progress bar for download.}
+}
+\value{
+\code{raster} object containing indexed map unit keys and associated raster attribute table
+}
+\description{
+Intermediate-scale gridded (800m) soil property and interpretation maps from aggregated SSURGO and STATSGO data. These maps were developed by USDA-NRCS-SPSD staff in collaboration with UCD-LAWR. Originally for educational use and \href{https://casoilresource.lawr.ucdavis.edu/soil-properties/}{interactive thematic maps}, these data are a suitable alternative to gridded STATSGO-derived thematic soil maps. The full size grids can be \href{https://casoilresource.lawr.ucdavis.edu/soil-properties/download.php}{downloaded here}.
+}
+\details{
+\code{aoi} should be specified as either a \code{Spatial*}, \code{sf}, \code{sfc} or \code{bbox} object or a \code{list} containing:
+
+\describe{
+\item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
+\item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
+}
+
+The WCS query is parameterized using \code{raster::extent} derived from the above AOI specification, after conversion to the native CRS (EPSG:6350) of the ISSR-800 grids.
+
+Variables available from this WCS can be queried using \code{WCS_details(wcs = 'ISSR800')}.
+}
+\note{
+There are still some issues to be resolved related to the encoding of NA Variables with a natural zero (e.g. SAR) have 0 set to NA.
+}
+\author{
+D.E. Beaudette and A.G. Brown
+}
diff --git a/misc/man-deprecated/KSSL_VG_model.Rd b/misc/man-deprecated/KSSL_VG_model.Rd
new file mode 100644
index 00000000..017007fe
--- /dev/null
+++ b/misc/man-deprecated/KSSL_VG_model.Rd
@@ -0,0 +1,61 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/KSSL_VG_model.R
+\name{KSSL_VG_model}
+\alias{KSSL_VG_model}
+\title{Develop a Water Retention Curve from KSSL Data}
+\usage{
+KSSL_VG_model(VG_params, phi_min = 10^-6, phi_max = 10^8, pts = 100)
+}
+\arguments{
+\item{VG_params}{\code{data.frame} or \code{list} object with the parameters of the van Genuchten model, see details}
+
+\item{phi_min}{lower limit for water potential in kPa}
+
+\item{phi_max}{upper limit for water potential in kPa}
+
+\item{pts}{number of points to include in estimated water retention curve}
+}
+\value{
+A list with the following components:
+\describe{
+\item{VG_curve}{estimated water retention curve: paired estimates of water potential (phi) and water content (theta)}
+\item{VG_function}{spline function for converting water potential (phi, units of kPa) to estimated volumetric water content (theta, units of percent, range: \{0, 1\})}
+\item{VG_inverse_function}{spline function for converting volumetric water content (theta, units of percent, range: \{0, 1\}) to estimated water potential (phi, units of kPa)}
+}
+}
+\description{
+Water retention curve modeling via van Genuchten model and KSSL data.
+}
+\details{
+This function was developed to work with measured or estimated parameters of the \href{https://en.wikipedia.org/wiki/Water_retention_curve}{van Genuchten model}, as generated by the \href{https://www.ars.usda.gov/pacific-west-area/riverside-ca/agricultural-water-efficiency-and-salinity-research-unit/docs/model/rosetta-model/}{Rosetta model}. As such, \code{VG_params} should have the following format and conventions:
+\describe{
+\item{theta_r}{saturated water content, values should be in the range of \{0, 1\}}
+\item{theta_s}{residual water content, values should be in the range of \{0, 1\}}
+\item{alpha}{related to the inverse of the air entry suction, function expects log10-transformed values with units of cm}
+\item{npar}{index of pore size distribution, function expects log10-transformed values with units of 1/cm}
+}
+}
+\note{
+A practical example is given in the \href{http://ncss-tech.github.io/AQP/soilDB/fetchSCAN-demo.html}{fetchSCAN tutorial}.
+}
+\examples{
+
+# basic example
+d <- data.frame(
+  theta_r = 0.0337216, 
+  theta_s = 0.4864061, 
+  alpha = -1.581517, 
+  npar = 0.1227247
+)
+
+vg <- KSSL_VG_model(d)
+
+str(vg)
+
+}
+\references{
+\href{https://en.wikipedia.org/wiki/Water_retention_curve}{water retention curve estimation}
+}
+\author{
+D.E. Beaudette
+}
diff --git a/misc/man-deprecated/OSDquery.Rd b/misc/man-deprecated/OSDquery.Rd
new file mode 100644
index 00000000..ab000356
--- /dev/null
+++ b/misc/man-deprecated/OSDquery.Rd
@@ -0,0 +1,97 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/OSDquery.R
+\name{OSDquery}
+\alias{OSDquery}
+\title{Full text searching of the USDA-NRCS Official Series Descriptions}
+\usage{
+OSDquery(
+  mlra = "",
+  taxonomic_class = "",
+  typical_pedon = "",
+  brief_narrative = "",
+  ric = "",
+  use_and_veg = "",
+  competing_series = "",
+  geog_location = "",
+  geog_assoc_soils = ""
+)
+}
+\arguments{
+\item{mlra}{a comma-delimited list of MLRA to search ('17,18,22A')}
+
+\item{taxonomic_class}{search family level classification}
+
+\item{typical_pedon}{search typical pedon section}
+
+\item{brief_narrative}{search brief narrative}
+
+\item{ric}{search range in characteristics section}
+
+\item{use_and_veg}{search use and vegetation section}
+
+\item{competing_series}{search competing series section}
+
+\item{geog_location}{search geographic setting section}
+
+\item{geog_assoc_soils}{search geographically associated soils section}
+}
+\value{
+a \code{data.frame} object containing soil series names that match patterns supplied as arguments.
+}
+\description{
+This is a rough example of how chunks of text parsed from OSD records can be made search-able with the \href{https://www.postgresql.org/docs/9.5/textsearch.html}{PostgreSQL fulltext indexing} and query system (\href{https://www.postgresql.org/docs/9.5/datatype-textsearch.html}{syntax details}). Each search field (except for the "brief narrative" and MLRA) corresponds with a section header in an OSD. The results may not include every OSD due to formatting errors and typos. Results are scored based on the number of times search terms match words in associated sections. This is the R API corresponding to \href{https://casoilresource.lawr.ucdavis.edu/osd-search/}{this webpage}.
+}
+\details{
+See \href{https://casoilresource.lawr.ucdavis.edu/osd-search/}{this webpage} for more information.
+\itemize{
+\item family level taxa are derived from SC database, not parsed OSD records
+\item MLRA are derived via spatial intersection (SSURGO x MLRA polygons)
+\item MLRA-filtering is only possible for series used in the current SSURGO snapshot (component name)
+\item logical AND: \code{&}
+\item logical OR: \code{|}
+\item wildcard, e.g. rhy-something \verb{rhy:*}
+\item search terms with spaces need doubled single quotes: \verb{''san joaquin''}
+\item combine search terms into a single expression: \verb{(grano:* | granite)}
+}
+
+Related documentation can be found in the following tutorials
+\itemize{
+\item \href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{overview of all soil series query functions}
+\item \href{https://ncss-tech.github.io/AQP/soilDB/competing-series.html}{competing soil series}
+\item \href{https://ncss-tech.github.io/AQP/soilDB/siblings.html}{siblings}
+}
+}
+\note{
+SoilWeb maintains a snapshot of the Official Series Description data.
+}
+\examples{
+
+
+\donttest{
+if(requireNamespace("curl") &
+   curl::has_internet() &
+   require(aqp)) {
+  
+  # find all series that list Pardee as a geographically associated soil.
+  s <- OSDquery(geog_assoc_soils = 'pardee')
+  
+  # get data for these series
+  x <- fetchOSD(s$series, extended = TRUE, colorState = 'dry')
+  
+  # simple figure
+  par(mar=c(0,0,1,1))
+  plot(x$SPC)
+}
+}
+
+}
+\references{
+\url{https://www.nrcs.usda.gov/wps/portal/nrcs/detailfull/soils/home/?cid=nrcs142p2_053587}
+}
+\seealso{
+\code{\link{fetchOSD}, \link{siblings}, \link{fetchOSD}}
+}
+\author{
+D.E. Beaudette
+}
+\keyword{manip}
diff --git a/misc/man-deprecated/ROSETTA.Rd b/misc/man-deprecated/ROSETTA.Rd
new file mode 100644
index 00000000..b56cf4fd
--- /dev/null
+++ b/misc/man-deprecated/ROSETTA.Rd
@@ -0,0 +1,92 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/ROSETTA.R
+\name{ROSETTA}
+\alias{ROSETTA}
+\title{ROSETTA Model API}
+\usage{
+ROSETTA(x, vars, v = c("1", "2", "3"), chunkSize = 10000, conf = NULL)
+}
+\arguments{
+\item{x}{a \code{data.frame} of required soil properties, may contain other columns, see details}
+
+\item{vars}{character vector of column names in \code{x} containing relevant soil property values, see details}
+
+\item{v}{ROSETTA model version number: '1', '2', or '3', see details and references.}
+
+\item{chunkSize}{number of records per API call}
+
+\item{conf}{configuration passed to \code{httr::POST()} such as \code{verbose()}.}
+}
+\value{
+a \code{data.frame} object with the following columns:
+\itemize{
+\item \code{...}: pre-existing columns from \code{x}
+\item \code{theta_r}: residual volumetric water content (cm^3/cm^3)
+\item \code{theta_s}: saturated volumetric water content (cm^3/cm^3)
+\item \code{alpha}: related to the inverse of the air entry suction, log10-transformed values with units of cm
+\item \code{npar}: index of pore size distribution, log10-transformed values with units of 1/cm
+\item \code{ksat}: saturated hydraulic conductivity, log10-transformed values with units of cm/day
+\item \code{.rosetta.model}: best-available model selection (-1 signifies that prediction was not possible due to missing values in \code{x})
+\item \code{.rosetta.version}: ROSETTA algorithm version, selected via function argument \code{v}
+}
+}
+\description{
+A simple interface to the \href{https://www.ars.usda.gov/pacific-west-area/riverside-ca/agricultural-water-efficiency-and-salinity-research-unit/docs/model/rosetta-model/}{ROSETTA model} for predicting hydraulic parameters from soil properties. The ROSETTA API was developed by Dr. Todd Skaggs (USDA-ARS) and links to the work of Zhang and Schaap, (2017). See the \href{http://ncss-tech.github.io/AQP/soilDB/ROSETTA-API.html}{related tutorial} for additional examples.
+}
+\details{
+Soil properties supplied in \code{x} must be described, in order, via \code{vars} argument. The API does not use the names but column ordering must follow: sand, silt, clay, bulk density, volumetric water content at 33kPa (1/3 bar), and volumetric water content at 1500 kPa (15 bar).
+
+The ROSETTA model relies on a minimum of 3 soil properties, with increasing (expected) accuracy as additional properties are included:
+\itemize{
+\item required, \code{sand}, \code{silt}, \code{clay}: USDA soil texture separates (percentages) that sum to 100\\%
+\item optional, \verb{bulk density} (any moisture basis): mass per volume after accounting for >2mm fragments, units of gm/cm3
+\item optional, \verb{volumetric water content at 33 kPa}: roughly "field capacity" for most soils, units of cm^3/cm^3
+\item optional, \verb{volumetric water content at 1500 kPa}: roughly "permanent wilting point" for most plants, units of cm^3/cm^3
+}
+
+Column names not specified in \code{vars} are retained in the output.
+
+Three versions of the ROSETTA model are available, selected using \code{v = 1}, \code{v = 2}, or \code{v = 3}.
+\itemize{
+\item \strong{version 1}: Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}.
+\item \strong{version 2}: Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}.
+\item \strong{version 3}: Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}
+}
+}
+\note{
+Input data should not contain columns names that will conflict with the ROSETTA API results: \code{theta_r}, \code{theta_s}, \code{alpha}, \code{npar}, \code{ksat}.
+}
+\references{
+Consider using the interactive version, with copy/paste functionality at: \url{https://www.handbook60.org/rosetta}.
+
+Rosetta Model Home Page: \url{https://www.ars.usda.gov/pacific-west-area/riverside-ca/agricultural-water-efficiency-and-salinity-research-unit/docs/model/rosetta-model/}.
+
+Python ROSETTA model: \url{http://www.u.arizona.edu/~ygzhang/download.html}.
+
+Yonggen Zhang, Marcel G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology. 547: 39-53. \doi{10.1016/j.jhydrol.2017.01.004}.
+
+Kosugi, K. 1999. General model for unsaturated hydraulic conductivity for soils with lognormal pore-size distribution. Soil Sci. Soc. Am. J. 63:270-277.
+
+Mualem, Y. 1976. A new model predicting the hydraulic conductivity of unsaturated porous media. Water Resour. Res. 12:513-522.
+
+Schaap, M.G. and W. Bouten. 1996. Modeling water retention curves of sandy soils using neural networks. Water Resour. Res. 32:3033-3040.
+
+Schaap, M.G., Leij F.J. and van Genuchten M.Th. 1998. Neural network analysis for hierarchical prediction of soil water retention and saturated hydraulic conductivity. Soil Sci. Soc. Am. J. 62:847-855.
+
+Schaap, M.G., and F.J. Leij, 1998. Database Related Accuracy and Uncertainty of Pedotransfer Functions, Soil Science 163:765-779.
+
+Schaap, M.G., F.J. Leij and M. Th. van Genuchten. 1999. A bootstrap-neural network approach to predict soil hydraulic parameters. In: van Genuchten, M.Th., F.J. Leij, and L. Wu (eds), Proc. Int. Workshop, Characterization and Measurements of the Hydraulic Properties of Unsaturated Porous Media, pp 1237-1250, University of California, Riverside, CA.
+
+Schaap, M.G., F.J. Leij, 1999, Improved prediction of unsaturated hydraulic conductivity with the Mualem-van Genuchten, Submitted to Soil Sci. Soc. Am. J.
+
+van Genuchten, M.Th. 1980. A closed-form equation for predicting the hydraulic conductivity of unsaturated soils. Soil Sci. Am. J. 44:892-898.
+
+Schaap, M.G., F.J. Leij, and M.Th. van Genuchten. 2001. ROSETTA: a computer program for estimating soil hydraulic parameters with hierarchical pedotransfer functions. Journal of Hydrology 251(3-4): 163-176. doi: \doi{10.1016/S0022-1694(01)00466-8}.
+
+Schaap, M.G., A. Nemes, and M.T. van Genuchten. 2004. Comparison of Models for Indirect Estimation of Water Retention and Available Water in Surface Soils. Vadose Zone Journal 3(4): 1455-1463. doi: \doi{10.2136/vzj2004.1455}.
+
+Zhang, Y., and M.G. Schaap. 2017. Weighted recalibration of the Rosetta pedotransfer model with improved estimates of hydraulic parameter distributions and summary statistics (Rosetta3). Journal of Hydrology 547: 39-53. doi: \doi{10.1016/j.jhydrol.2017.01.004}.
+}
+\author{
+D.E. Beaudette, Todd Skaggs (ARS), Richard Reid
+}
diff --git a/misc/man-deprecated/SCAN_SNOTEL_metadata.Rd b/misc/man-deprecated/SCAN_SNOTEL_metadata.Rd
new file mode 100644
index 00000000..bd2c1b89
--- /dev/null
+++ b/misc/man-deprecated/SCAN_SNOTEL_metadata.Rd
@@ -0,0 +1,34 @@
+\name{SCAN_SNOTEL_metadata}
+\alias{SCAN_SNOTEL_metadata}
+\alias{state_FIPS_codes}
+
+\docType{data}
+
+\title{SCAN and SNOTEL Station Metadata}
+
+\description{SCAN and SNOTEL station metadata, a work in progress.}
+
+\usage{data("SCAN_SNOTEL_metadata")}
+
+\format{
+  A data frame with 1092 observations on the following 12 variables.
+  \describe{
+    \item{\code{Name}}{station name}
+    \item{\code{Site}}{station ID}
+    \item{\code{State}}{state}
+    \item{\code{Network}}{sensor network: SCAN / SNOTEL}
+    \item{\code{County}}{county}
+    \item{\code{Elevation_ft}}{station elevation in feet}
+    \item{\code{Latitude}}{latitude of station}
+    \item{\code{Longitude}}{longitude of station}
+    \item{\code{HUC}}{associated watershed}
+    \item{\code{climstanm}}{climate station name (TODO: remove this column)}
+    \item{\code{upedonid}}{associated user pedon ID}
+    \item{\code{pedlabsampnum}}{associated lab sample ID}
+  }
+}
+
+\details{These data have been compiled from several sources and represent a progressive effort to organize SCAN/SNOTEL station metadata. Therefore, some records may be missing or incorrect. Details on this effort can be found at the associated GitHub issue page: \url{https://github.com/ncss-tech/soilDB/issues/61}.}
+
+
+\keyword{datasets}
diff --git a/misc/man-deprecated/SDA_spatialQuery.Rd b/misc/man-deprecated/SDA_spatialQuery.Rd
new file mode 100644
index 00000000..42b32f72
--- /dev/null
+++ b/misc/man-deprecated/SDA_spatialQuery.Rd
@@ -0,0 +1,187 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/SDA-spatial.R
+\name{SDA_spatialQuery}
+\alias{SDA_spatialQuery}
+\alias{SDA_make_spatial_query}
+\alias{SDA_query_features}
+\title{SDA Spatial Query}
+\usage{
+SDA_spatialQuery(
+  geom,
+  what = "mukey",
+  geomIntersection = FALSE,
+  db = c("SSURGO", "STATSGO")
+)
+}
+\arguments{
+\item{geom}{a Spatial* object, with valid CRS. May contain multiple features.}
+
+\item{what}{a character vector specifying what to return. 'mukey': \code{data.frame} with intersecting map unit keys and names, \code{geom} overlapping or intersecting map unit polygons}
+
+\item{geomIntersection}{logical; \code{FALSE}: overlapping map unit polygons returned, \code{TRUE}: intersection of \code{geom} + map unit polygons is returned.}
+
+\item{db}{a character vector identifying the Soil Geographic Databases
+('SSURGO' or 'STATSGO') to query. Option \var{STATSGO} currently works
+only in combination with \code{what = "geom"}.}
+}
+\value{
+A \code{data.frame} if \code{what = 'mukey'}, otherwise \code{SpatialPolygonsDataFrame} object.
+}
+\description{
+Query SDA (SSURGO / STATSGO) records via spatial intersection with supplied geometries. Input can be SpatialPoints, SpatialLines, or SpatialPolygons objects with a valid CRS. Map unit keys, overlapping polygons, or the spatial intersection of \code{geom} + SSURGO / STATSGO polygons can be returned. See details.
+}
+\details{
+Queries for map unit keys are always more efficient vs. queries for overlapping or intersecting (i.e. least efficient) features. \code{geom} is converted to GCS / WGS84 as needed. Map unit keys are always returned when using \code{what = "geom"}.
+
+There is a 100,000 record limit and 32Mb JSON serializer limit, per query.
+
+SSURGO (detailed soil survey, typically 1:24,000 scale) and STATSGO (generalized soil survey, 1:250,000 scale) data are stored together within SDA. This means that queries that don't specify an area symbol may result in a mixture of SSURGO and STATSGO records. See the examples below and the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for details.
+}
+\note{
+Row-order is not preserved across features in \code{geom} and returned object. Use \code{sp::over()} or similar functionality to extract from results. Polygon area in acres is computed server-side when \code{what = 'geom'} and \code{geomIntersection = TRUE}.
+}
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+   curl::has_internet() & 
+   requireNamespace("sp") &
+   requireNamespace("raster") 
+   ) {
+
+library(aqp)
+library(sp)
+library(raster)
+
+## query at a point
+
+# example point
+p <- SpatialPoints(cbind(x = -119.72330, y = 36.92204), 
+                   proj4string = CRS('+proj=longlat +datum=WGS84'))
+
+# query map unit records at this point
+res <- SDA_spatialQuery(p, what = 'mukey')
+
+# convert results into an SQL "IN" statement
+# useful when there are multiple intersecting records
+mu.is <- format_SQL_in_statement(res$mukey)
+
+# composite SQL WHERE clause
+sql <- sprintf("mukey IN \%s", mu.is)
+
+# get commonly used map unit / component / chorizon records
+# as a SoilProfileCollection object
+# confusing but essential: request that results contain `mukey`
+# with `duplicates = TRUE`
+x <- fetchSDA(sql, duplicates = TRUE)
+
+# safely set texture class factor levels
+# by making a copy of this column
+# this will save in lieu of textures in the original
+# `texture` column
+horizons(x)$texture.class <- factor(x$texture, levels = SoilTextureLevels())
+
+# graphical depiction of the result
+plotSPC(x, color='texture.class', label='compname', 
+        name='hzname', cex.names = 1, width=0.25, 
+        plot.depth.axis=FALSE, hz.depths=TRUE, 
+        name.style='center-center'
+)
+
+
+
+## query mukey + geometry that intersect with a bounding box
+
+# define a bounding box: xmin, xmax, ymin, ymax
+#
+#         +-------------------(ymax, xmax)
+#         |                        |
+#         |                        |
+#     (ymin, xmin) ----------------+
+b <- c(-119.747629, -119.67935, 36.912019, 36.944987)
+
+# convert bounding box to WKT
+bbox.sp <-as(extent(b), 'SpatialPolygons')
+proj4string(bbox.sp) <- '+proj=longlat +datum=WGS84'
+
+# results contain associated map unit keys (mukey)
+# return SSURGO polygons, after intersection with provided BBOX
+ssurgo.geom <- SDA_spatialQuery(
+  bbox.sp, 
+  what = 'geom', 
+  db = 'SSURGO', 
+  geomIntersection = TRUE
+)
+
+# return STATSGO polygons, after intersection with provided BBOX
+statsgo.geom <- SDA_spatialQuery(
+  bbox.sp, 
+  what = 'geom', 
+  db = 'STATSGO', 
+  geomIntersection = TRUE
+)
+
+# inspect results
+par(mar = c(0,0,3,1))
+plot(ssurgo.geom, border = 'royalblue')
+plot(statsgo.geom, lwd = 2, border = 'firebrick', add = TRUE)
+plot(bbox.sp, lwd = 3, add = TRUE)
+legend(
+  x = 'topright', 
+  legend = c('BBOX', 'STATSGO', 'SSURGO'), 
+  lwd = c(3, 2, 1),
+  col = c('black', 'firebrick', 'royalblue'),
+)
+
+
+# quick reminder that STATSGO map units often contain many components
+# format an SQL IN statement using the first STATSGO mukey
+mu.is <- format_SQL_in_statement(statsgo.geom$mukey[1])
+
+# composite SQL WHERE clause
+sql <- sprintf("mukey IN \%s", mu.is)
+
+# get commonly used map unit / component / chorizon records
+# as a SoilProfileCollection object
+x <- fetchSDA(sql)
+
+# tighter figure margins
+par(mar = c(0,0,3,1))
+
+
+# organize component sketches by national map unit symbol
+# color horizons via awc
+# adjust legend title
+# add alternate label (vertical text) containing component percent
+# move horizon names into the profile sketches
+# make profiles wider
+groupedProfilePlot(
+  x, 
+  groups = 'nationalmusym', 
+  label = 'compname', 
+  color = 'awc_r', 
+  col.label = 'Available Water Holding Capacity (cm / cm)',
+  alt.label = 'comppct_r',
+  name.style = 'center-center',
+  width = 0.3
+)
+
+
+mtext(
+  'STATSGO (1:250,000) map units contain a lot of components!', 
+  side = 1, 
+  adj = 0, 
+  line = -1.5, 
+  at = 0.25, 
+  font = 4
+)
+ }
+}
+
+}
+\seealso{
+\code{\link{SDA_query}}
+}
+\author{
+D.E. Beaudette, A.G. Brown, D.R. Schlaepfer
+}
+\keyword{manip}
diff --git a/man/SSURGO_spatial_query.Rd b/misc/man-deprecated/SSURGO_spatial_query.Rd
similarity index 100%
rename from man/SSURGO_spatial_query.Rd
rename to misc/man-deprecated/SSURGO_spatial_query.Rd
diff --git a/misc/man-deprecated/STRplot.Rd b/misc/man-deprecated/STRplot.Rd
new file mode 100644
index 00000000..61995c12
--- /dev/null
+++ b/misc/man-deprecated/STRplot.Rd
@@ -0,0 +1,41 @@
+\name{STRplot}
+\alias{STRplot}
+
+\title{Graphical Description of US Soil Taxonomy Soil Temperature Regimes}
+\description{Graphical Description of US Soil Taxonomy Soil Temperature Regimes}
+
+\usage{
+STRplot(mast, msst, mwst, permafrost = FALSE, pt.cex = 2.75, leg.cex = 0.85)
+}
+
+\arguments{
+  \item{mast}{single value or vector of mean annual soil temperature (deg C)}
+  \item{msst}{single value or vector of mean summer soil temperature (deg C)}
+  \item{mwst}{single value of mean winter soil temperature (deg C)}
+  \item{permafrost}{logical: permafrost presence / absence}
+  \item{pt.cex}{symbol size}
+  \item{leg.cex}{legend size}
+}
+
+\details{
+\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Related tutorial}.
+}
+
+\references{
+Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. U.S. Department of Agriculture, Natural Resources Conservation Service, National Soil Survey Center, Lincoln, Nebraska.
+}
+
+\author{D.E. Beaudette}
+
+
+\seealso{
+\code{\link{estimateSTR}}
+}
+
+\examples{
+par(mar=c(4,1,0,1))
+STRplot(mast = 0:25, msst = 10, mwst = 1)
+}
+
+\keyword{ hplot }% use one of  RShowDoc("KEYWORDS")
+
diff --git a/misc/man-deprecated/WCS_details.Rd b/misc/man-deprecated/WCS_details.Rd
new file mode 100644
index 00000000..225a7ccf
--- /dev/null
+++ b/misc/man-deprecated/WCS_details.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/WCS-utils.R
+\name{WCS_details}
+\alias{WCS_details}
+\title{Web Coverage Services Details}
+\usage{
+WCS_details(wcs = c("mukey", "ISSR800"))
+}
+\arguments{
+\item{wcs}{a WCS label ('mukey' or 'ISSR800')}
+}
+\value{
+a \code{data.frame}
+}
+\description{
+List variables or databases provided by soilDB web coverage service (WCS) abstraction. These lists will be expanded in future versions.
+}
+\examples{
+
+WCS_details(wcs = 'ISSR800')
+}
diff --git a/misc/man-deprecated/createStaticNASIS.Rd b/misc/man-deprecated/createStaticNASIS.Rd
new file mode 100644
index 00000000..f1c95933
--- /dev/null
+++ b/misc/man-deprecated/createStaticNASIS.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/createStaticNASIS.R
+\name{createStaticNASIS}
+\alias{createStaticNASIS}
+\title{Create a memory or file-based instance of NASIS database (for selected tables)}
+\usage{
+createStaticNASIS(
+  tables = NULL,
+  SS = FALSE,
+  systables = FALSE,
+  static_path = NULL,
+  output_path = NULL
+)
+}
+\arguments{
+\item{tables}{Character vector of target tables. Default: \code{NULL} is all tables meeting the following criteria.}
+
+\item{SS}{Logical. Include "selected set" tables (ending with suffix \code{"_View1"}). Default: \code{FALSE}}
+
+\item{systables}{Logical. Include "system" tables (starting with prefix \code{"system"}). Default: \code{FALSE}}
+
+\item{static_path}{Optional: path to SQLite database containing NASIS table structure; Default: \code{NULL}}
+
+\item{output_path}{Optional: path to new/existing SQLite database to write tables to. Default: \code{NULL} returns table results as named list.}
+}
+\value{
+A named list of results from calling \code{dbQueryNASIS} for all columns in each NASIS table.
+}
+\description{
+Create a memory or file-based instance of NASIS database (for selected tables)
+}
+\examples{
+
+\dontrun{
+ str(createStaticNASIS(tables = c("calculation","formtext")))
+}
+
+}
diff --git a/misc/man-deprecated/dbConnectNASIS.Rd b/misc/man-deprecated/dbConnectNASIS.Rd
new file mode 100644
index 00000000..5697bbca
--- /dev/null
+++ b/misc/man-deprecated/dbConnectNASIS.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dbQueryNASIS.R
+\name{dbConnectNASIS}
+\alias{dbConnectNASIS}
+\title{Create a connection to a local NASIS database}
+\usage{
+dbConnectNASIS(static_path = NULL)
+}
+\arguments{
+\item{static_path}{Optional: path to SQLite database containing NASIS table structure; Default: \code{NULL}}
+}
+\value{
+A \code{DBIConnection} object, as returned by \code{DBI::dbConnect()}.
+}
+\description{
+Create a connection to a local NASIS database
+}
diff --git a/misc/man-deprecated/dbQueryNASIS.Rd b/misc/man-deprecated/dbQueryNASIS.Rd
new file mode 100644
index 00000000..7965d3ff
--- /dev/null
+++ b/misc/man-deprecated/dbQueryNASIS.Rd
@@ -0,0 +1,23 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/dbQueryNASIS.R
+\name{dbQueryNASIS}
+\alias{dbQueryNASIS}
+\title{Send queries to a NASIS DBIConnection}
+\usage{
+dbQueryNASIS(conn, q, close = TRUE, ...)
+}
+\arguments{
+\item{conn}{A \code{DBIConnection} object, as returned by \code{DBI::dbConnect()}.}
+
+\item{q}{A statement to execute using \code{DBI::dbGetQuery}}
+
+\item{close}{Close connection after query? Default: \code{TRUE}}
+
+\item{...}{Additional arguments to \code{DBI::dbGetQuery}}
+}
+\value{
+Result of \code{DBI::dbGetQuery}
+}
+\description{
+Send queries to a NASIS DBIConnection
+}
diff --git a/misc/man-deprecated/dot-dump_NASIS_table.Rd b/misc/man-deprecated/dot-dump_NASIS_table.Rd
new file mode 100644
index 00000000..eac48870
--- /dev/null
+++ b/misc/man-deprecated/dot-dump_NASIS_table.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/createStaticNASIS.R
+\name{.dump_NASIS_table}
+\alias{.dump_NASIS_table}
+\title{Method for "dumping" contents of an entire NASIS table}
+\usage{
+.dump_NASIS_table(table_name, static_path = NULL)
+}
+\arguments{
+\item{table_name}{Character name of table.}
+
+\item{static_path}{Optional: path to SQLite database containing NASIS table structure; Default: \code{NULL}}
+}
+\value{
+A data.frame or other result of \code{DBI::dbGetQuery}
+}
+\description{
+Method for "dumping" contents of an entire NASIS table
+}
diff --git a/misc/man-deprecated/estimateColorMixture.Rd b/misc/man-deprecated/estimateColorMixture.Rd
new file mode 100644
index 00000000..4de22f10
--- /dev/null
+++ b/misc/man-deprecated/estimateColorMixture.Rd
@@ -0,0 +1,27 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/estimateColorMixture.R
+\name{estimateColorMixture}
+\alias{estimateColorMixture}
+\title{Estimate color mixtures using weighted average of CIELAB color coordinates}
+\usage{
+estimateColorMixture(x, wt = "pct", backTransform = FALSE)
+}
+\arguments{
+\item{x}{data.frame, typically from NASIS containing at least CIE LAB ('L', 'A', 'B') and some kind of weight}
+
+\item{wt}{fractional weights, usually area of hz face}
+
+\item{backTransform}{logical, should the mixed sRGB representation of soil color be transformed to closest Munsell chips? This is performed by aqp::rgb2Munsell default: \code{FALSE}}
+}
+\value{
+A data.frame containing estimated color mixture
+}
+\description{
+Estimate color mixtures using weighted average of CIELAB color coordinates
+}
+\note{
+See \code{\link[aqp]{mixMunsell}} for a more realistic (but slower) simulation of subtractive mixing of pigments.
+}
+\author{
+D.E. Beaudette
+}
diff --git a/misc/man-deprecated/estimateSTR.Rd b/misc/man-deprecated/estimateSTR.Rd
new file mode 100644
index 00000000..1cc735fc
--- /dev/null
+++ b/misc/man-deprecated/estimateSTR.Rd
@@ -0,0 +1,44 @@
+\name{estimateSTR}
+\alias{estimateSTR}
+
+\title{Estimate Soil Temperature Regime}
+\description{Estimate soil temperature regime (STR) based on mean annual soil temperature (MAST), mean summer temperature (MSST), mean winter soil temperature (MWST), presence of O horizons, saturated conditions, and presence of permafrost. Several assumptions are made when O horizon  or saturation are undefined.}
+
+\usage{
+estimateSTR(mast, mean.summer, mean.winter, O.hz = NA, saturated = NA, permafrost = FALSE)
+}
+
+\arguments{
+  \item{mast}{vector of mean annual soil temperature (deg C)}
+  \item{mean.summer}{vector of mean summer soil temperature (deg C)}
+  \item{mean.winter}{vector of mean winter soil temperature (deg C)}
+  \item{O.hz}{logical vector of O horizon presence / absence}
+  \item{saturated}{logical vector of seasonal saturation}
+  \item{permafrost}{logical vector of permafrost presence / absence}
+}
+
+\details{
+\href{http://ncss-tech.github.io/AQP/soilDB/STR-eval.html}{Related tutorial}.
+}
+
+\value{Vector of soil temperature regimes.}
+
+\references{
+Soil Survey Staff. 2015. Illustrated guide to soil taxonomy. U.S. Department of Agriculture, Natural Resources Conservation Service, National Soil Survey Center, Lincoln, Nebraska.
+}
+
+\author{D.E. Beaudette}
+
+
+\seealso{
+\code{\link{STRplot}}
+}
+
+\examples{
+# simple example
+estimateSTR(mast=17, mean.summer = 22, mean.winter = 12)
+
+}
+
+\keyword{ manip }% use one of  RShowDoc("KEYWORDS")
+
diff --git a/misc/man-deprecated/fetchGDB.Rd b/misc/man-deprecated/fetchGDB.Rd
new file mode 100644
index 00000000..ee031d50
--- /dev/null
+++ b/misc/man-deprecated/fetchGDB.Rd
@@ -0,0 +1,83 @@
+\name{fetchGDB}
+\alias{fetchGDB}
+\alias{get_legend_from_GDB}
+\alias{get_mapunit_from_GDB}
+\alias{get_component_from_GDB}
+
+\title{Load and Flatten Data from SSURGO file geodatabases}
+\description{Functions to load and flatten commonly used tables and from SSURGO file geodatabases, and create soil profile collection objects (SPC).}
+\usage{
+fetchGDB(dsn = "gNATSGO_CONUS.gdb",
+         WHERE = NULL,
+         childs = TRUE,
+         droplevels = TRUE,
+         stringsAsFactors = TRUE
+         )
+
+
+get_legend_from_GDB(dsn = "gNATSGO_CONUS.gdb",
+                    WHERE = NULL,
+                    droplevels = TRUE,
+                    stringsAsFactors = TRUE,
+                    stats = FALSE
+                    )
+
+get_mapunit_from_GDB(dsn = "gNATSGO_CONUS.gdb",
+                     WHERE = NULL,
+                     droplevels = TRUE,
+                     stringsAsFactors = TRUE,
+                     stats = FALSE
+                     )
+
+get_component_from_GDB(dsn = "gNATSGO_CONUS.gdb",
+                       WHERE = NULL,
+                       childs = FALSE,
+                       droplevels = TRUE,
+                       stringsAsFactors = TRUE
+                       )
+
+}
+
+
+\arguments{
+  \item{dsn}{data source name (interpretation varies by driver - for some drivers, dsn is a file name, but may also be a folder, or contain the name and access credentials of a database); in case of GeoJSON, dsn may be the character string holding the geojson data. It can also be an open database connection.}
+  \item{WHERE}{text string formatted as an SQL WHERE clause (default: FALSE)}
+  \item{childs}{logical; if FALSE parent material and geomorphic child tables are not flattened and appended}
+  \item{droplevels}{logical: indicating whether to drop unused levels in classifying factors. This is useful when a class has large number of unused classes, which can waste space in tables and figures.}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+  \item{stats}{Return extended summary statistics (for legend or mapunit only)}
+  }
+
+
+\details{These functions return data from SSURGO file geodatabases with the use of a simple text string that formatted as an SQL WHERE clause (e.g. \code{WHERE = "areasymbol = 'IN001'"}. Any columns within the target table can be specified (except for fetchGDB() currently, which only targets the legend with the WHERE clause).
+}
+\value{A \code{data.frame} or \code{SoilProfileCollection} object.}
+\author{Stephen Roecker}
+
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\examples{
+\donttest{
+
+## replace `dsn` with path to your own geodatabase (SSURGO OR gNATSGO)
+##
+##
+##  download CONUS gNATSGO from here:
+##    https://www.nrcs.usda.gov/wps/portal/nrcs/detail/soils/survey/geo/?cid=nrcseprd1464625
+##
+##
+# dsn <- "D:/geodata/soils/gNATSGO_CONUS.gdb"
+
+# le <- get_legend_from_GDB(dsn = dsn, WHERE = "areasymbol LIKE '\%'")
+
+# mu <- get_mapunit_from_GDB(dsn = dsn, WHERE = "muname LIKE 'Miami\%'")
+
+# co <- get_component_from_GDB(dsn, WHERE = "compname = 'Miami'
+#                              AND majcompflag = 'Yes'", childs = FALSE)
+
+# f_in_GDB <- fetchGDB(WHERE = "areasymbol LIKE 'IN\%'")
+
+}
+}
+\keyword{manip}
diff --git a/misc/man-deprecated/fetchHenry.Rd b/misc/man-deprecated/fetchHenry.Rd
new file mode 100644
index 00000000..a9f680f0
--- /dev/null
+++ b/misc/man-deprecated/fetchHenry.Rd
@@ -0,0 +1,63 @@
+\name{fetchHenry}
+\alias{fetchHenry}
+\alias{month2season}
+\alias{summarizeSoilTemperature}
+
+\title{Download Data from the Henry Mount Soil Temperature and Water Database}
+
+\description{This function is a front-end to the REST query functionality of the Henry Mount Soil Temperature and Water Database.}
+
+\usage{
+fetchHenry(what='all', usersiteid = NULL, project = NULL, sso = NULL,
+gran = "day", start.date = NULL, stop.date = NULL, 
+pad.missing.days = TRUE, soiltemp.summaries = TRUE)
+}
+
+\arguments{
+  \item{what}{type of data to return: 'sensors': sensor metadata only | 'soiltemp': sensor metadata + soil temperature data | 'soilVWC': sensor metadata + soil moisture data | 'airtemp': sensor metadata + air temperature data | 'waterlevel': sensor metadata + water level data |'all': sensor metadata + all sensor data}
+  \item{usersiteid}{(optional) filter results using a NASIS user site ID}
+  \item{project}{(optional) filter results using a project ID}
+  \item{sso}{(optional) filter results using a soil survey office code}
+  \item{gran}{data granularity: "day", "week", "month", "year"; returned data are averages}
+  \item{start.date}{(optional) starting date filter}
+  \item{stop.date}{(optional) ending date filter}
+  \item{pad.missing.days}{should missing data ("day" granularity) be filled with NA? see details}
+  \item{soiltemp.summaries}{should soil temperature ("day" granularity only) be summarized? see details}
+}
+
+\details{Filling missing days with NA is useful for computing and index of how complete the data are, and for estimating (mostly) unbiased MAST and seasonal mean soil temperatures. Summaries are computed by first averaging over Julian day, then averaging over all days of the year (MAST) or just those days that occur within "summer" or "winter". This approach makes it possible to estimate summaries in the presence of missing data. The quality of summaries should be weighted by the number of "functional years" (number of years with non-missing data after combining data by Julian day) and "complete years" (number of years of data with >= 365 days of non-missing data).}
+
+\value{a list containing:
+ \item{sensors}{a \code{SpatialPointsDataFrame} object containing site-level information}
+ \item{soiltemp}{a \code{data.frame} object containing soil temperature timeseries data}
+ \item{soilVWC}{a \code{data.frame} object containing soil moisture timeseries data}
+ \item{airtemp}{a \code{data.frame} object containing air temperature timeseries data}
+ \item{waterlevel}{a \code{data.frame} object containing water level timeseries data}
+}
+
+\author{D.E. Beaudette}
+\note{This function and the back-end database are very much a work in progress.}
+
+\seealso{\code{\link{fetchSCAN}}}
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet() &
+    require(lattice)) {
+    
+  # get CA630 data as daily averages
+  x <- fetchHenry(project='CA630', gran = 'day')
+  
+  # inspect data gaps
+  levelplot(factor(!is.na(sensor_value)) ~ doy * factor(year) | name, 
+  data=x$soiltemp, col.regions=c('grey', 'RoyalBlue'), cuts=1, 
+  colorkey=FALSE, as.table=TRUE, scales=list(alternating=3), 
+  par.strip.text=list(cex=0.75), strip=strip.custom(bg='yellow'), 
+  xlab='Julian Day', ylab='Year')
+
+}
+}
+}
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/fetchKSSL.Rd b/misc/man-deprecated/fetchKSSL.Rd
new file mode 100644
index 00000000..69803095
--- /dev/null
+++ b/misc/man-deprecated/fetchKSSL.Rd
@@ -0,0 +1,89 @@
+\name{fetchKSSL}
+\alias{fetchKSSL}
+\title{Fetch KSSL Data}
+\description{Download soil characterization and morphologic data via BBOX, MLRA, or soil series name query, from the KSSL database.}
+
+\usage{fetchKSSL(series=NA, bbox=NA, mlra=NA, pedlabsampnum=NA, 
+pedon_id=NA, pedon_key=NA, returnMorphologicData=FALSE, returnGeochemicalData=FALSE,
+simplifyColors=FALSE, progress=TRUE)}
+
+\arguments{
+  \item{series}{vector of soil series names, case insensitive}
+  \item{bbox}{a single bounding box in WGS84 geographic coordinates e.g. \code{c(-120, 37, -122, 38)}}
+  \item{mlra}{vector of MLRA IDs, e.g. "18" or "22A"}
+  \item{pedlabsampnum}{vector of KSSL pedon lab sample number}
+  \item{pedon_id}{vector of user pedon ID}
+  \item{pedon_key}{vector of KSSL internal pedon ID}
+  \item{returnMorphologicData}{logical, optionally request basic morphologic data, see details section}
+  \item{returnGeochemicalData}{logical, optionally request geochemical, optical and XRD/thermal data, see details section}
+  \item{simplifyColors}{logical, simplify colors (from morphologic data) and join with horizon data}
+  \item{progress}{logical, optionally give progress when iterating over multiple requests}
+}
+
+
+\details{This is an experimental interface to a subset for the most commonly used data from a snapshot of KSSL (lab characterization) and NASIS (morphologic) data. 
+
+Series-queries are case insensitive. Series name is based on the "correlated as" field (from KSSL snapshot) when present.  The "sampled as" classification was promoted to "correlated as" if the "correlated as" classification was missing.
+
+When \code{returnMorphologicData} is TRUE, the resulting object is a list. The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} object) is stored in the named element "SPC". The additional elements are basic morphologic data: soil color, rock fragment volume, pores, structure, and redoximorphic features. There is a 1:many relationship between the horizon data in "SPC" and the additional dataframes in \code{morph}. See examples for ideas on how to "flatten" these tables.
+
+When \code{returnGeochemicalData} is TRUE, the resulting object is a list. The standard output from \code{fetchKSSL} (\code{SoilProfileCollection} object) is stored in the named element "SPC". The additional elements are geochemical and mineralogy analysis tables, specifically: geochemical/elemental analyses "geochem", optical mineralogy "optical", and X-ray diffraction / thermal "xrd_thermal". \code{returnGeochemicalData} will include additional dataframes \code{geochem}, \code{optical}, and \code{xrd_thermal} in list result. 
+
+Setting \code{simplifyColors=TRUE} will automatically flatten the soil color data and join to horizon level attributes.
+
+Function arguments (\code{series}, \code{mlra}, etc.) are fully vectorized except for \code{bbox}.
+}
+
+\value{a \code{SoilProfileCollection} object when \code{returnMorphologicData} is FALSE, otherwise a list.}
+
+\author{D.E. Beaudette and A.G. Brown}
+\note{SoilWeb maintains a snapshot of these KSSL and NASIS data. The SoilWeb snapshot was developed using methods described here: \url{https://github.com/dylanbeaudette/process-kssl-snapshot}. Please use the link below for the live data.}
+
+\references{
+\url{http://ncsslabdatamart.sc.egov.usda.gov/}
+}
+
+\seealso{\code{\link{fetchOSD}}}
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet()) {
+    
+    library(aqp)
+    library(plyr)
+    library(reshape2)
+    
+    # search by series name
+    s <- fetchKSSL(series='auburn')
+    
+    # search by bounding-box
+    # s <- fetchKSSL(bbox=c(-120, 37, -122, 38))
+    
+    # how many pedons
+    length(s)
+    
+    # plot 
+    plotSPC(s, name='hzn_desgn', max.depth=150)
+    
+    ##
+    ## morphologic data
+    ##
+    
+    # get lab and morphologic data
+    s <- fetchKSSL(series='auburn', returnMorphologicData = TRUE)
+    
+    # extract SPC
+    pedons <- s$SPC
+    
+    ## automatically simplify color data
+    s <- fetchKSSL(series='auburn', returnMorphologicData = TRUE, simplifyColors=TRUE)
+    
+    # check
+    par(mar=c(0,0,0,0))
+    plot(pedons, color='moist_soil_color', print.id=FALSE)
+    
+}
+}
+}
+
+\keyword{utilities}
diff --git a/misc/man-deprecated/fetchNASIS.Rd b/misc/man-deprecated/fetchNASIS.Rd
new file mode 100644
index 00000000..df4ef50e
--- /dev/null
+++ b/misc/man-deprecated/fetchNASIS.Rd
@@ -0,0 +1,79 @@
+\name{fetchNASIS}
+\alias{fetchNASIS}
+\alias{getHzErrorsNASIS}
+\alias{get_phorizon_from_NASIS_db}
+\alias{get_component_copm_data_from_NASIS_db}
+\alias{get_component_horizon_data_from_NASIS_db}
+\alias{get_component_correlation_data_from_NASIS_db}
+\alias{get_component_copm_data_from_NASIS_db}
+\alias{get_component_cogeomorph_data_from_NASIS_db}
+\alias{get_component_esd_data_from_NASIS_db}
+\alias{get_component_otherveg_data_from_NASIS_db}
+\alias{get_copedon_from_NASIS_db}
+
+\alias{get_legend_from_NASIS}
+\alias{get_lmuaoverlap_from_NASIS}
+\alias{get_mapunit_from_NASIS}
+\alias{get_projectmapunit_from_NASIS}
+\alias{get_component_diaghz_from_NASIS_db}
+\alias{get_mutext_from_NASIS_db}
+\alias{get_phfmp_from_NASIS_db}
+\alias{get_RMF_from_NASIS_db}
+\alias{get_concentrations_from_NASIS_db}
+
+\alias{fetchVegdata}
+\alias{get_vegplot_from_NASIS_db}
+\alias{get_vegplot_location_from_NASIS_db}
+\alias{get_vegplot_species_from_NASIS_db}
+\alias{get_vegplot_textnote_from_NASIS_db}
+\alias{get_vegplot_transect_from_NASIS_db}
+\alias{get_vegplot_transpecies_from_NASIS_db}
+\alias{get_vegplot_tree_si_details_from_NASIS_db}
+\alias{get_vegplot_tree_si_summary_from_NASIS_db}
+\alias{get_vegplot_trhi_from_NASIS_db}
+
+
+
+\title{Fetch commonly used site/pedon/horizon or component data from NASIS.}
+\description{Fetch commonly used site/pedon/horizon data or component from NASIS, returned as a SoilProfileCollection object.}
+
+\usage{
+fetchNASIS(from = 'pedons', url = NULL, SS=TRUE, rmHzErrors=TRUE, nullFragsAreZero=TRUE, 
+                  soilColorState='moist', lab=FALSE, fill = FALSE,
+                  stringsAsFactors = default.stringsAsFactors()
+                  )
+
+getHzErrorsNASIS(strict=TRUE)
+}
+
+\arguments{
+  \item{from}{determines what objects should fetched? ('pedons' | 'components' | 'pedon_report')}
+  \item{url}{string specifying the url for the NASIS pedon_report (default: NULL)}
+  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+  \item{rmHzErrors}{should pedons with horizonation errors be removed from the results? (default: TRUE)}
+  \item{nullFragsAreZero}{should fragment volumes of NULL be interpreted as 0? (default: TRUE), see details}
+  \item{soilColorState}{which colors should be used to generate the convenience field 'soil_color'? ('moist' | 'dry')}
+  \item{lab}{should the phlabresults child table be fetched with site/pedon/horizon data (default: FALSE)}  
+  \item{fill}{(fetchNASIS(from='components') only: include component records without horizon data in result? (default: FALSE)}
+  \item{strict}{how strict should horizon boundaries be checked for consistency: TRUE=more | FALSE=less}
+}
+
+\value{a SoilProfileCollection class object}
+\author{D. E. Beaudette, J. M. Skovlin, and S.M. Roecker}
+
+\details{This function imports data from NASIS into R as a SoilProfileCollection object. It "flattens" NASIS pedon and component tables, including their child tables, into several more easily manageable data frames. Primarily these functions access the local NASIS database using an ODBC connection. However using the fetchNASIS() argument from = "pedon_report", data can be read from the NASIS Report 'fetchNASIS', as either a txt file or url. The primary purpose of fetchNASIS(from = "pedon_report") is to facilitate importing datasets larger than 8000+ pedons/components.
+
+The value of \code{nullFragsAreZero} will have a significant impact on the rock fragment fractions returned by \code{fetchNASIS}. Set \code{nullFragsAreZero = FALSE} in those cases where there are many data-gaps and NULL rock fragment values should be interpreted as NULLs. Set \code{nullFragsAreZero = TRUE} in those cases where NULL rock fragment values should be interpreted as 0.
+
+This function attempts to do most of the boilerplate work when extracting site/pedon/horizon or component data from a local NASIS database. Pedons that are missing horizon data, or have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. Pedons with combination horizons (e.g. B/C) are erroneously marked as errors due to the way in which they are stored in NASIS as two overlapping horizon records.
+
+See \code{\link{getHzErrorsNASIS}} for a simple approach to identifying pedons with problematic horizonation.
+
+See the \href{http://ncss-tech.github.io/AQP/soilDB/NASIS-component-data.html}{NASIS component tutorial}, and \href{http://ncss-tech.github.io/AQP/soilDB/fetchNASIS-mini-tutorial.html}{NASIS pedon tutorial} for more information.}
+
+
+  
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/fetchNASISLabData.Rd b/misc/man-deprecated/fetchNASISLabData.Rd
new file mode 100644
index 00000000..7bbb1d28
--- /dev/null
+++ b/misc/man-deprecated/fetchNASISLabData.Rd
@@ -0,0 +1,20 @@
+\name{fetchNASISLabData}
+\alias{fetchNASISLabData}
+
+
+\title{Fetch lab data used site/horizon data from a PedonPC database.}
+\description{Fetch KSSL laboratory pedon/horizon layer data from a local NASIS database, return as a SoilProfileCollection object.}
+
+\usage{fetchNASISLabData(SS = TRUE)}
+\arguments{
+  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+}
+\value{a SoilProfileCollection class object}
+\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
+\author{J.M. Skovlin and D.E. Beaudette}
+\note{This function attempts to do most of the boilerplate work when extracting KSSL laboratory site/horizon data from a local NASIS database. Lab pedons that have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. See \code{\link{getHzErrorsNASIS}} for a simple approach to identifying pedons with problematic horizonation.}
+
+\seealso{\code{\link{get_labpedon_data_from_NASIS_db}}}
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/fetchNASISWebReport.Rd b/misc/man-deprecated/fetchNASISWebReport.Rd
new file mode 100644
index 00000000..37a5bc19
--- /dev/null
+++ b/misc/man-deprecated/fetchNASISWebReport.Rd
@@ -0,0 +1,158 @@
+\name{fetchNASISWebReport}
+\alias{fetchNASISWebReport}
+\alias{get_project_from_NASISWebReport}
+\alias{get_progress_from_NASISWebReport}
+\alias{get_project_correlation_from_NASISWebReport}
+\alias{get_legend_from_NASISWebReport}
+\alias{get_mapunit_from_NASISWebReport}
+\alias{get_projectmapunit_from_NASISWebReport}
+\alias{get_projectmapunit2_from_NASISWebReport}
+\alias{get_component_from_NASISWebReport}
+\alias{get_chorizon_from_NASISWebReport}
+\alias{get_cosoilmoist_from_NASISWebReport}
+\alias{get_sitesoilmoist_from_NASISWebReport}
+\alias{get_lmuaoverlap_from_NASISWebReport}
+
+\title{Extract component tables from a the NASIS Web Reports}
+\description{Get, format, impute, and return component tables.}
+\usage{
+fetchNASISWebReport(projectname, rmHzErrors = FALSE, fill = FALSE,
+                    stringsAsFactors = default.stringsAsFactors()
+                    )
+get_progress_from_NASISWebReport(mlrassoarea, fiscalyear, projecttypename)
+get_project_from_NASISWebReport(mlrassoarea, fiscalyear)
+get_project_correlation_from_NASISWebReport(mlrassoarea, fiscalyear, projectname)
+get_projectmapunit_from_NASISWebReport(projectname, 
+                                       stringsAsFactors = default.stringsAsFactors()
+                                       )
+get_projectmapunit2_from_NASISWebReport(mlrassoarea, fiscalyear, projectname, 
+                                        stringsAsFactors = default.stringsAsFactors()
+                                        )
+get_legend_from_NASISWebReport(mlraoffice,
+                               areasymbol,
+                               droplevels = TRUE,
+                               stringsAsFactors = default.stringsAsFactors()
+                               )
+get_mapunit_from_NASISWebReport(areasymbol,
+                                droplevels = TRUE,
+                                stringsAsFactors = default.stringsAsFactors()
+                                )
+get_component_from_NASISWebReport(projectname, 
+                                  stringsAsFactors = default.stringsAsFactors()
+                                  )
+get_chorizon_from_NASISWebReport(projectname, fill = FALSE, 
+                                 stringsAsFactors = default.stringsAsFactors()
+                                 )
+get_cosoilmoist_from_NASISWebReport(projectname, impute = TRUE, 
+                                    stringsAsFactors = default.stringsAsFactors()
+                                    )
+get_sitesoilmoist_from_NASISWebReport(usiteid)
+}
+
+
+\arguments{
+  \item{projectname}{text string vector of project names to be inserted into a SQL WHERE clause (default: NA)}
+  \item{mlraoffice}{text string value identifying the MLRA Regional Soil Survey Office group name inserted into a SQL WHERE clause (default: NA)}
+  \item{mlrassoarea}{text string value identifying the MLRA Soil Survey Office areasymbol symbol inserted into a SQL WHERE clause (default: NA)}
+  \item{fiscalyear}{text string value identifying the fiscal year inserted into a SQL WHERE clause (default: NA)}
+  \item{projecttypename}{text string value identifying the project type name inserted into a SQL WHERE clause (default: NA)}
+  \item{areasymbol}{text string value identifying the area symbol (e.g. "IN001" or "IN\%") inserted into a SQL WHERE clause (default: NA)}
+  \item{usiteid}{text string value identifying the user site id inserted into a SQL WHERE clause (default: NA)}
+  \item{impute}{replace missing (i.e. NULL) values with "Not_Populated" for categorical data, or the "RV" for numeric data or 201 cm if the "RV" is also NULL (default: TRUE)}
+  \item{fill}{should rows with missing component ids be removed NA (FALSE)}
+  \item{rmHzErrors}{should pedons with horizonation errors be removed from the results? (default: FALSE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+  \item{droplevels}{logical: indicating whether to drop unused levels in classifying factors. This is useful when a class has large number of unused classes, which can waste space in tables and figures.}
+}
+
+
+\value{A data.frame or list with the results.}
+\author{Stephen Roecker}
+
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+
+\examples{
+\donttest{
+
+
+if (requireNamespace("curl") &
+    curl::has_internet() &
+    require("aqp") &
+    require("ggplot2") & 
+    require("gridExtra")
+) {
+  # query soil components by projectname
+  test = fetchNASISWebReport(
+    "EVAL - MLRA 111A - Ross silt loam, 0 to 2 percent slopes, frequently flooded"
+  )
+  test = test$spc
+  
+  # profile plot
+  plot(test)
+  
+  # convert the data for depth plot
+  clay_slice = horizons(slice(test, 0:200 ~ claytotal_l + claytotal_r + claytotal_h))
+  names(clay_slice) <- gsub("claytotal_", "", names(clay_slice))
+  
+  om_slice = horizons(slice(test, 0:200 ~ om_l + om_r + om_h))
+  names(om_slice) = gsub("om_", "", names(om_slice))
+  
+  test2 = rbind(data.frame(clay_slice, var = "clay"),
+                data.frame(om_slice, var = "om")
+  )
+  
+  h = merge(test2, site(test)[c("dmuiid", "coiid", "compname", "comppct_r")],
+            by = "coiid", 
+            all.x = TRUE
+  )
+  
+  # depth plot of clay content by soil component
+  gg_comp <- function(x) {
+    ggplot(x) +
+      geom_line(aes(y = r, x = hzdept_r)) +
+      geom_line(aes(y = r, x = hzdept_r)) +
+      geom_ribbon(aes(ymin = l, ymax = h, x = hzdept_r), alpha = 0.2) +
+      xlim(200, 0) +
+      xlab("depth (cm)") +
+      facet_grid(var ~ dmuiid + paste(compname, comppct_r)) +
+      coord_flip()
+  }
+  g1 <- gg_comp(subset(h, var == "clay"))
+  g2 <- gg_comp(subset(h, var == "om"))
+  
+  grid.arrange(g1, g2)
+  
+  
+  # query cosoilmoist (e.g. water table data) by mukey
+  # NA depths are interpreted as (???) with impute=TRUE argument
+  x <- get_cosoilmoist_from_NASISWebReport(
+    "EVAL - MLRA 111A - Ross silt loam, 0 to 2 percent slopes, frequently flooded"
+  )
+  
+  ggplot(x, aes(x = as.integer(month), y = dept_r, lty = status)) +
+    geom_rect(aes(xmin = as.integer(month), xmax = as.integer(month) + 1,
+                  ymin = 0, ymax = max(x$depb_r),
+                  fill = flodfreqcl)) +
+    geom_line(cex = 1) +
+    geom_point() +
+    geom_ribbon(aes(ymin = dept_l, ymax = dept_h), alpha = 0.2) +
+    ylim(max(x$depb_r), 0) +
+    xlab("month") + ylab("depth (cm)") +
+    scale_x_continuous(breaks = 1:12, labels = month.abb, name="Month") +
+    facet_wrap(~ paste0(compname, ' (', comppct_r , ')')) +
+    ggtitle(paste0(x$nationalmusym[1], 
+                   ': Water Table Levels from Component Soil Moisture Month Data'))
+  
+  
+}
+
+
+
+}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/fetchPedonPC.Rd b/misc/man-deprecated/fetchPedonPC.Rd
new file mode 100644
index 00000000..b768c092
--- /dev/null
+++ b/misc/man-deprecated/fetchPedonPC.Rd
@@ -0,0 +1,26 @@
+\name{fetchPedonPC}
+\alias{fetchPedonPC}
+\alias{getHzErrorsPedonPC}
+
+\title{Fetch commonly used site/horizon data from a PedonPC v.5 database.}
+\description{Fetch commonly used site/horizon data from a version 5.x PedonPC database, return as a SoilProfileCollection object.}
+
+\usage{
+fetchPedonPC(dsn)
+getHzErrorsPedonPC(dsn, strict=TRUE)
+}
+
+\arguments{
+  \item{dsn}{The path to a PedonPC version 5.x database}
+  \item{strict}{should horizonation by strictly enforced? (TRUE)}
+}
+
+\details{This function currently works only on Windows.}
+\value{a SoilProfileCollection class object}
+\author{D. E. Beaudette and J. M. Skovlin}
+\note{This function attempts to do most of the boilerplate work when extracting site/horizon data from a PedonPC or local NASIS database. Pedons that have errors in their horizonation are excluded from the returned object, however, their IDs are printed on the console. See \code{\link{getHzErrorsPedonPC}} for a simple approach to identifying pedons with problematic horizonation. Records from the 'taxhistory' table are selected based on 1) most recent record, or 2) record with the least amount of missing data.}
+
+\seealso{\code{\link{get_hz_data_from_pedon_db}}}
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/fetchSCAN.Rd b/misc/man-deprecated/fetchSCAN.Rd
new file mode 100644
index 00000000..4d4169cc
--- /dev/null
+++ b/misc/man-deprecated/fetchSCAN.Rd
@@ -0,0 +1,54 @@
+\name{fetchSCAN}
+\alias{fetchSCAN}
+\alias{SCAN_sensor_metadata}
+\alias{SCAN_site_metadata}
+
+
+\title{Fetch SCAN Data}
+\description{Query soil/climate data from USDA-NRCS SCAN Stations (experimental)}
+
+\usage{
+# get SCAN data
+fetchSCAN(site.code, year, report='SCAN', req=NULL)
+
+# get sensor metadata for one or more sites
+SCAN_sensor_metadata(site.code)
+
+# get site metadata for one or more sites
+SCAN_site_metadata(site.code)
+}
+
+\arguments{
+  \item{site.code}{a vector of site codes}
+  \item{year}{a vector of years}
+  \item{report}{report name, single value only}
+  \item{req}{list of SCAN request parameters, for backwards-compatibility only}
+}
+
+\details{See \href{http://ncss-tech.github.io/AQP/soilDB/fetchSCAN-demo.html}{the fetchSCAN tutorial for details.} These functions require the `httr` and `rvest` libraries.}
+
+\note{\code{SCAN_sensor_metadata()} is known to crash on 32bit R / libraries (Windows).}
+
+\value{a \code{data.frame} object}
+\references{https://www.wcc.nrcs.usda.gov/index.html}
+\author{D.E. Beaudette}
+
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet()) {
+    
+    # get data: new interface
+    x <- fetchSCAN(site.code=c(356, 2072), year=c(2015, 2016))
+    str(x)
+    
+    # get sensor metadata
+    m <- SCAN_sensor_metadata(site.code=c(356, 2072))
+    
+    # get site metadata
+    m <- SCAN_site_metadata(site.code=c(356, 2072))
+}
+}
+}
+\keyword{manip}
+
diff --git a/man/fetchSDA_component.Rd b/misc/man-deprecated/fetchSDA_component.Rd
similarity index 100%
rename from man/fetchSDA_component.Rd
rename to misc/man-deprecated/fetchSDA_component.Rd
diff --git a/misc/man-deprecated/fetchSDA_spatial.Rd b/misc/man-deprecated/fetchSDA_spatial.Rd
new file mode 100644
index 00000000..9e616724
--- /dev/null
+++ b/misc/man-deprecated/fetchSDA_spatial.Rd
@@ -0,0 +1,74 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchSDA_spatial.R
+\name{fetchSDA_spatial}
+\alias{fetchSDA_spatial}
+\title{Query Soil Data Access and Return Spatial Data}
+\usage{
+fetchSDA_spatial(
+  x,
+  by.col = "mukey",
+  method = "feature",
+  geom.src = "mupolygon",
+  db = "SSURGO",
+  add.fields = NULL,
+  chunk.size = 10,
+  verbose = TRUE
+)
+}
+\arguments{
+\item{x}{A vector of MUKEYs / national mapunit symbols (for mupolygon geometry); OR legend keys (LKEY) / area symbols (for sapolygon geometry)}
+
+\item{by.col}{Column name containing mapunit identifier \code{"mukey"}, \code{"nmusym"}, or \code{"areasymbol"} for \code{geom.src} \code{sapolygon}; default is inferred from \code{is.numeric(x) == TRUE} for \code{mukey} or \code{lkey} and (\code{nationalmusym} or \code{areasymbol} otherwise.}
+
+\item{method}{geometry result type: \code{"feature"} returns polygons, \code{"bbox"} returns the bounding box of each polygon, and \code{"point"} returns a single point within each polygon.}
+
+\item{geom.src}{Either \code{mupolygon} or \code{sapolygon}}
+
+\item{db}{Default: SSURGO. When \code{geom.src} is \code{mupolygon}, use STATSGO polygon geometry instead of SSURGO by setting \code{db = "STATSGO"}}
+
+\item{add.fields}{Column names from \code{mapunit} or \code{legend} table to add to result. Must specify parent table name as the prefix \code{mapunit} before column name e.g. \code{mapunit.muname}.}
+
+\item{chunk.size}{How many queries should spatial request be divided into? Necessary for large results. Default: 10}
+
+\item{verbose}{Print messages?}
+}
+\value{
+A Spatial*DataFrame corresponding to SDA spatial data for all symbols requested. Default result contains geometry with attribute table containing unique feature ID, symbol and area symbol plus additional fields in result specified with \code{add.fields}.
+}
+\description{
+This is a high-level "fetch" method to facilitate spatial queries to Soil Data Access (SDA) based on mapunit key (\code{mukey}) and national mapunit symbol (\code{nationalmusym}) for \code{mupolygon} (SSURGO) or \code{gsmmupolygon} (STATSGO) geometry OR legend key (\code{lkey}) and area symbols (\code{areasymbol}) for \code{sapolygon} (Soil Survey Area; SSA) geometry).
+
+A Soil Data Access spatial query is made returning geometry and key identifying information about the mapunit or area of interest. Additional columns from the mapunit or legend table can be included using \code{add.fields} argument.
+
+This function automatically "chunks" the input vector (using \code{soilDB::makeChunks}) of mapunit identifiers to minimize the likelihood of exceeding the SDA data request size. The number of chunks varies with the \code{chunk.size} setting and the length of your input vector. If you are working with many mapunits and/or large extents, you may need to decrease this number in order to have more chunks.
+
+Querying regions with complex mapping may require smaller \code{chunk.size}. Numerically adjacent IDs in the input vector may share common qualities (say, all from same soil survey area or region) which could cause specific chunks to perform "poorly" (slow or error) no matter what the chunk size is. Shuffling the order of the inputs using \code{sample} may help to eliminate problems related to this, depending on how you obtained your set of MUKEY/nationalmusym to query. One could feasibly use \code{muacres} as a heuristic to adjust for total acreage within chunks.
+}
+\details{
+Note that STATSGO data are fetched using \code{CLIPAREASYMBOL = 'US'} to avoid duplicating state and national subsets of the geometry.
+}
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+   curl::has_internet()) {
+
+   # get spatial data for a single mukey
+    single.mukey <- fetchSDA_spatial(x = "2924882")
+
+    # demonstrate fetching full extent (multi-mukey) of national musym
+    full.extent.nmusym <- fetchSDA_spatial(x = "2x8l5", by = "nmusym")
+
+    # compare extent of nmusym to single mukey within it
+    if(require(sp)) {
+     plot(full.extent.nmusym, col = "RED",border=0)
+     plot(single.mukey, add = TRUE, col = "BLUE", border=0)
+    }
+
+    # demo adding a field (`muname`) to attribute table of result
+    head(fetchSDA_spatial(x = "2x8l5", by="nmusym", add.fields="muname"))
+}
+}
+}
+\author{
+Andrew G. Brown
+}
diff --git a/misc/man-deprecated/fetchSoilGrids.Rd b/misc/man-deprecated/fetchSoilGrids.Rd
new file mode 100644
index 00000000..22623381
--- /dev/null
+++ b/misc/man-deprecated/fetchSoilGrids.Rd
@@ -0,0 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchSoilGrids.R
+\name{fetchSoilGrids}
+\alias{fetchSoilGrids}
+\title{Fetch SoilGrids 250m properties information from point locations}
+\usage{
+fetchSoilGrids(locations, loc.names = c("id", "lat", "lon"))
+}
+\arguments{
+\item{locations}{A \code{data.frame} containing 3 columns referring to site ID, latitude and longitude.}
+
+\item{loc.names}{Optional: Column names referring to site ID, latitude and longitude. Default: \code{c("id","lat","lon")}}
+}
+\value{
+A SoilProfileCollection
+}
+\description{
+This function obtains SoilGrids properties information (250m raster resolution) given a \code{data.frame} containing site IDs, latitudes and longitudes.
+}
+\details{
+The depth intervals returned are: \code{"0-5cm", "5-15cm", "15-30cm", "30-60cm", "60-100cm", "100-200cm"} and the properties returned are \code{"bdod", "cec", "cfvo", "clay", "nitrogen", "phh2o", "sand", "silt", "soc"} -- each with 5th, 50th, 95th, mean and uncertainty values. Point data requests are made through \code{properties/query} endpoint of the SoilGrids v2.0 REST API: https://rest.soilgrids.org/soilgrids/v2.0/docs
+}
+\examples{
+\donttest{
+ if(requireNamespace("curl") &
+   curl::has_internet()) {
+  
+  library(aqp)
+
+  your.points <- data.frame(id  = c("A", "B"), 
+                           lat = c(37.9, 38.1), 
+                           lon = c(-120.3, -121.5), 
+                           stringsAsFactors = FALSE)
+
+  x <- fetchSoilGrids(your.points)
+ 
+  plotSPC(x, name = NA, color = "socQ50")
+ }
+}
+}
+\author{
+Andrew G. Brown
+}
diff --git a/misc/man-deprecated/filter_geochem.Rd b/misc/man-deprecated/filter_geochem.Rd
new file mode 100644
index 00000000..ceeb69ed
--- /dev/null
+++ b/misc/man-deprecated/filter_geochem.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/filter_KSSL.R
+\name{filter_geochem}
+\alias{filter_geochem}
+\title{Filter KSSL Geochemical Table}
+\usage{
+filter_geochem(
+  geochem,
+  columns = NULL,
+  prep_code = NULL,
+  major_element_method = NULL,
+  trace_element_method = NULL
+)
+}
+\arguments{
+\item{geochem}{geochemical data, as returned by fetchKSSL}
+
+\item{columns}{Column name(s) to include in result}
+
+\item{prep_code}{Character vector of prep code(s) to include in result.}
+
+\item{major_element_method}{Character vector of major element method(s) to include in result.}
+
+\item{trace_element_method}{Character vector of trace element method(s) to include in result.}
+}
+\value{
+A data.frame, subsetted according to the constraints specified in arguments.
+}
+\description{
+A function to subset KSSL "geochem" / elemental analysis result table to obtain rows/columns based on: column name, preparation code, major / trace element method.
+}
+\author{
+Andrew G. Brown.
+}
diff --git a/misc/man-deprecated/format_SQL_in_statement.Rd b/misc/man-deprecated/format_SQL_in_statement.Rd
new file mode 100644
index 00000000..2a6c9568
--- /dev/null
+++ b/misc/man-deprecated/format_SQL_in_statement.Rd
@@ -0,0 +1,62 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/SDA_query.R
+\name{format_SQL_in_statement}
+\alias{format_SQL_in_statement}
+\title{Format vector of values into a string suitable for an SQL \code{IN} statement.}
+\usage{
+format_SQL_in_statement(x)
+}
+\arguments{
+\item{x}{A character vector.}
+}
+\value{
+A character vector (unit length) containing concatenated group syntax for use in SQL \code{IN}, with unique value found in \code{x}.
+}
+\description{
+Concatenate a vector to SQL \code{IN}-compatible syntax: \code{letters[1:3]} becomes \code{('a','b','c')}. Values in \code{x} are first passed through \code{unique()}.
+}
+\note{
+Only \code{character} output is supported.
+}
+\examples{
+
+\donttest{
+
+library(aqp)
+
+# get some mukeys
+q <- "select top(2) mukey from mapunit;"
+mukeys <- SDA_query(q)
+
+# format for use in an SQL IN statement
+mukey.inst <- format_SQL_in_statement(mukeys$mukey)
+mukey.inst
+
+# make a more specific query: for component+horizon data, just for those mukeys
+q2 <- sprintf("SELECT * FROM mapunit
+               INNER JOIN component ON mapunit.mukey = component.mukey
+               INNER JOIN chorizon ON component.cokey = chorizon.cokey
+               WHERE mapunit.mukey IN \%s;", mukey.inst)
+# do the query                
+res <- SDA_query(q2) 
+
+# build a SoilProfileCollection from horizon-level records
+depths(res) <- cokey ~ hzdept_r + hzdepb_r
+
+# normalize mapunit/component level attributes to site-level for plot
+site(res) <- ~ muname + mukey + compname + comppct_r + taxclname
+
+# make a nice label
+res$labelname <- sprintf("\%s (\%s\%s)", res$compname, res$comppct_r, "\%")
+
+# major components only
+res <- filter(res, comppct_r >= 85)
+
+# inspect plot of result
+par(mar=c(0,0,0,0))
+groupedProfilePlot(res, groups = "mukey", color = "hzname", cex.names=0.8,
+                   id.style = "side", label = "labelname")
+}
+
+   
+}
diff --git a/misc/man-deprecated/getHzErrorsNASIS.Rd b/misc/man-deprecated/getHzErrorsNASIS.Rd
new file mode 100644
index 00000000..f8567159
--- /dev/null
+++ b/misc/man-deprecated/getHzErrorsNASIS.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/getHzErrorsNASIS.R
+\name{getHzErrorsNASIS}
+\alias{getHzErrorsNASIS}
+\title{Check pedon horizon table for logic errors}
+\usage{
+getHzErrorsNASIS(strict = TRUE, SS = TRUE, static_path = NULL)
+}
+\arguments{
+\item{strict}{how strict should horizon boundaries be checked for consistency: TRUE=more | FALSE=less}
+
+\item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+
+\item{static_path}{Optional: path to local SQLite database containing NASIS table structure; default: NULL}
+}
+\value{
+A data.frame containing problematic records with columns: 'peiid','pedon_id','hzdept','hzdepb','hzname'
+}
+\description{
+Check pedon horizon table for logic errors
+}
diff --git a/misc/man-deprecated/get_NOAA_GHCND.Rd b/misc/man-deprecated/get_NOAA_GHCND.Rd
new file mode 100644
index 00000000..7dc2d228
--- /dev/null
+++ b/misc/man-deprecated/get_NOAA_GHCND.Rd
@@ -0,0 +1,38 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchNOAA.R
+\name{get_NOAA_GHCND}
+\alias{get_NOAA_GHCND}
+\title{Get Global Historical Climatology Network Daily (GHCND) data from NOAA API for given datatype(s), station IDs and years.}
+\usage{
+get_NOAA_GHCND(stations, years, datatypeids, apitoken)
+}
+\arguments{
+\item{stations}{Station ID (e.g. \code{GHCND:USC00388786})}
+
+\item{years}{One or more years (e.g. 2017:2020)}
+
+\item{datatypeids}{One or more NOAA GHCND data type IDs (e.g \code{c("PRCP","SNOW")})}
+
+\item{apitoken}{API key token for NOAA NCDC web services (https://www.ncdc.noaa.gov/cdo-web/token)}
+}
+\value{
+A data.frame containing the GHCND data requested (limit 1000 records)
+}
+\description{
+Obtain daily climatic summary data for a set of station IDs, years, and datatypes.
+
+Note that typically results from the NOAA API are limited to 1000 records. However, by "chunking" up data into individual station\emph{year}datatypeid combinations, record results generally do not exceed 365 records for daily summaries.
+
+In order to use this function, you must obtain an API token from this website: https://www.ncdc.noaa.gov/cdo-web/token
+}
+\examples{
+
+#' ## in order to use this function, you must obtain an API token from this website:
+##  https://www.ncdc.noaa.gov/cdo-web/token
+
+# get_NOAA_GHCND(c("GHCND:USC00388786", "GHCND:USC00388787"),
+#                years = 2017:2020,
+#                datatypeids = c("PRCP","SNOW"),
+#                apitoken = "yourtokenhere")
+
+}
diff --git a/misc/man-deprecated/get_NOAA_stations_nearXY.Rd b/misc/man-deprecated/get_NOAA_stations_nearXY.Rd
new file mode 100644
index 00000000..489448f7
--- /dev/null
+++ b/misc/man-deprecated/get_NOAA_stations_nearXY.Rd
@@ -0,0 +1,34 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/fetchNOAA.R
+\name{get_NOAA_stations_nearXY}
+\alias{get_NOAA_stations_nearXY}
+\title{Query the NOAA API to get station data near a given latitude and longitude}
+\usage{
+get_NOAA_stations_nearXY(lat, lng, apitoken, bbox = 1)
+}
+\arguments{
+\item{lat}{Latitude}
+
+\item{lng}{Longitude}
+
+\item{apitoken}{API key token for NOAA NCDC web service}
+
+\item{bbox}{Optional: Dimension of the bounding box centered at \code{lat}, \code{lng}.}
+}
+\value{
+data.frame containing station information for all stations within a bounding box around \code{lat}, \code{lng}.
+}
+\description{
+Query the NOAA API to get station data (limit 1000 records) near a point. Default extent is plus or minus 0.5 degrees (bounding box) (with \code{bbox = 1}) around the specified point [lat, lng].
+
+In order to use this function, you must obtain an API token from this website: https://www.ncdc.noaa.gov/cdo-web/token
+}
+\examples{
+
+## in order to use this function, you must obtain an API token from this website:
+##  https://www.ncdc.noaa.gov/cdo-web/token
+
+# stations <- get_NOAA_stations_nearXY(lat = 37, lng = -120,
+#                                      apitoken = "yourtokenhere")
+
+}
diff --git a/misc/man-deprecated/get_colors_from_NASIS_db.Rd b/misc/man-deprecated/get_colors_from_NASIS_db.Rd
new file mode 100644
index 00000000..ec052814
--- /dev/null
+++ b/misc/man-deprecated/get_colors_from_NASIS_db.Rd
@@ -0,0 +1,24 @@
+\name{get_colors_from_NASIS_db}
+\alias{get_colors_from_NASIS_db}
+
+\title{Extract Soil Color Data from a local NASIS Database}
+\description{Get, format, mix, and return color data from a NASIS database.}
+\usage{
+get_colors_from_NASIS_db(SS = TRUE)
+}
+\arguments{
+  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}	
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame with the results.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+
+
+
+\seealso{
+\code{\link{simplifyColorData}}, \code{\link{get_hz_data_from_NASIS_db}}, \code{\link{get_site_data_from_NASIS_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_colors_from_pedon_db.Rd b/misc/man-deprecated/get_colors_from_pedon_db.Rd
new file mode 100644
index 00000000..99b25a8c
--- /dev/null
+++ b/misc/man-deprecated/get_colors_from_pedon_db.Rd
@@ -0,0 +1,26 @@
+\name{get_colors_from_pedon_db}
+\alias{get_colors_from_pedon_db}
+
+
+\title{Extract Soil Color Data from a PedonPC Database}
+\description{Get, format, mix, and return color data from a PedonPC database.}
+\usage{
+get_colors_from_pedon_db(dsn)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame with the results.}
+\author{Dylan E. Beaudette and Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_comonth_from_NASIS_db.Rd b/misc/man-deprecated/get_comonth_from_NASIS_db.Rd
new file mode 100644
index 00000000..467c943a
--- /dev/null
+++ b/misc/man-deprecated/get_comonth_from_NASIS_db.Rd
@@ -0,0 +1,42 @@
+\name{get_comonth_from_NASIS_db}
+\alias{get_comonth_from_NASIS_db}
+
+\title{Extract component month data from a local NASIS Database}
+\description{Extract component month data from a local NASIS Database.}
+
+\usage{
+get_comonth_from_NASIS_db(SS = TRUE, fill = FALSE,
+                          stringsAsFactors = default.stringsAsFactors()
+                          )
+}
+
+\arguments{
+  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
+  \item{fill}{should missing "month" rows in the comonth table be filled with NA (FALSE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+}
+
+\details{This function currently works only on Windows.}
+\value{A list with the results.}
+\author{Stephen Roecker}
+
+
+
+\seealso{
+\code{\link{fetchNASIS}}
+}
+
+\examples{
+\donttest{
+if(local_NASIS_defined()) {
+  # query text note data
+  cm <- try(get_comonth_from_NASIS_db())
+
+  # show structure of component month data
+  str(cm)
+}
+}
+}
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_component_data_from_NASIS_db.Rd b/misc/man-deprecated/get_component_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..e8c55f8e
--- /dev/null
+++ b/misc/man-deprecated/get_component_data_from_NASIS_db.Rd
@@ -0,0 +1,42 @@
+\name{get_component_data_from_NASIS_db}
+\alias{get_component_data_from_NASIS_db}
+\alias{get_component_restrictions_from_NASIS_db}
+
+\title{Extract component data from a local NASIS Database}
+\description{Extract component data from a local NASIS Database.}
+
+\usage{
+get_component_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())
+get_component_restrictions_from_NASIS_db(SS = TRUE)
+}
+
+
+\arguments{
+  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+}
+
+\details{This function currently works only on Windows.}
+\value{A list with the results.}
+\author{Dylan E. Beaudette, Stephen Roecker, and Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{fetchNASIS}}
+}
+
+\examples{
+\donttest{
+if(local_NASIS_defined()) {
+ # query text note data
+ fc <- try(get_component_data_from_NASIS_db())
+
+ # show structure of component data returned
+ str(fc)
+}
+}
+}
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_cosoilmoist_from_NASIS.Rd b/misc/man-deprecated/get_cosoilmoist_from_NASIS.Rd
new file mode 100644
index 00000000..f7f73c85
--- /dev/null
+++ b/misc/man-deprecated/get_cosoilmoist_from_NASIS.Rd
@@ -0,0 +1,37 @@
+\name{get_cosoilmoist_from_NASIS}
+\alias{get_cosoilmoist_from_NASIS}
+
+\title{Read and Flatten the Component Soil Moisture Tables}
+\description{Read and flatten the component soil moisture month tables from a local NASIS Database.}
+\usage{
+get_cosoilmoist_from_NASIS(impute = TRUE, stringsAsFactors = default.stringsAsFactors())
+}
+\arguments{
+  \item{impute}{replace missing (i.e. NULL) values with "Not_Populated" for categorical data, or the "RV" for numeric data or 201 cm if the "RV" is also NULL (default: TRUE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+}
+\value{A data.frame.}
+\author{S.M. Roecker}
+\details{The component soil moisture tables within NASIS house monthly data on flooding, ponding, and soil moisture status. The soil moisture status is used to specify the water table depth for components (e.g. \code{status == "Moist"}).
+}
+\note{This function currently works only on Windows.}
+
+\seealso{
+\link{fetchNASIS}, \link{get_cosoilmoist_from_NASISWebReport}, \link{get_cosoilmoist_from_SDA}, \code{get_comonth_from_SDA}
+}
+
+
+\examples{
+\donttest{
+if(local_NASIS_defined()) {
+ # load cosoilmoist (e.g. water table data)
+ test <- try(get_cosoilmoist_from_NASIS())
+
+ # inspect
+ if(!inherits(test, 'try-error')) {
+   head(test)
+ }
+}
+}}
+\keyword{manip}
+
diff --git a/man/get_extended_data_from_NASIS.Rd b/misc/man-deprecated/get_extended_data_from_NASIS.Rd
similarity index 100%
rename from man/get_extended_data_from_NASIS.Rd
rename to misc/man-deprecated/get_extended_data_from_NASIS.Rd
diff --git a/misc/man-deprecated/get_extended_data_from_pedon_db.Rd b/misc/man-deprecated/get_extended_data_from_pedon_db.Rd
new file mode 100644
index 00000000..d4c58064
--- /dev/null
+++ b/misc/man-deprecated/get_extended_data_from_pedon_db.Rd
@@ -0,0 +1,25 @@
+\name{get_extended_data_from_pedon_db}
+\alias{get_extended_data_from_pedon_db}
+
+\title{Extract accessory tables and summaries from a local pedonPC Database}
+\description{Extract accessory tables and summaries from a local pedonPC Database.}
+\usage{
+get_extended_data_from_pedon_db(dsn)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\details{This function currently works only on Windows.}
+\value{A list with the results.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_hz_data_from_NASIS_db.Rd b/misc/man-deprecated/get_hz_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..73431521
--- /dev/null
+++ b/misc/man-deprecated/get_hz_data_from_NASIS_db.Rd
@@ -0,0 +1,27 @@
+\name{get_hz_data_from_NASIS_db}
+\alias{get_hz_data_from_NASIS_db}
+
+\title{Extract Horizon Data from a local NASIS Database}
+\description{Get horizon-level data from a local NASIS database.}
+\usage{
+get_hz_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())
+}
+\arguments{
+  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame.}
+
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+\note{NULL total rock fragment values are assumed to represent an _absence_ of rock fragments, and set to 0.}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_NASIS_db}}, \code{\link{get_site_data_from_NASIS_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_hz_data_from_pedon_db.Rd b/misc/man-deprecated/get_hz_data_from_pedon_db.Rd
new file mode 100644
index 00000000..328c0389
--- /dev/null
+++ b/misc/man-deprecated/get_hz_data_from_pedon_db.Rd
@@ -0,0 +1,27 @@
+\name{get_hz_data_from_pedon_db}
+\alias{get_hz_data_from_pedon_db}
+
+\title{Extract Horizon Data from a PedonPC Database}
+\description{Get horizon-level data from a PedonPC database.}
+\usage{
+get_hz_data_from_pedon_db(dsn)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The path to a 'pedon.mdb' database.}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame.}
+
+\author{Dylan E. Beaudette and Jay M. Skovlin}
+\note{NULL total rock fragment values are assumed to represent an _absence_ of rock fragments, and set to 0.}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_colors_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_lablayer_data_from_NASIS_db.Rd b/misc/man-deprecated/get_lablayer_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..1b8ee4d6
--- /dev/null
+++ b/misc/man-deprecated/get_lablayer_data_from_NASIS_db.Rd
@@ -0,0 +1,23 @@
+\name{get_lablayer_data_from_NASIS_db}
+\alias{get_lablayer_data_from_NASIS_db}
+
+\title{Extract lab pedon layer data from a local NASIS Database}
+\description{Get lab pedon layer-level(horizon-level) data from a local NASIS database.}
+\usage{get_lablayer_data_from_NASIS_db(SS = TRUE)}
+\arguments{
+  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+}
+\value{A data.frame.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
+\note{This function queries KSSL laboratory site/horizon data from a local NASIS database from the lab layer data table.}
+
+\seealso{
+\code{\link{get_labpedon_data_from_NASIS_db}} 
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/get_labpedon_data_from_NASIS_db.Rd b/misc/man-deprecated/get_labpedon_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..a3ad3471
--- /dev/null
+++ b/misc/man-deprecated/get_labpedon_data_from_NASIS_db.Rd
@@ -0,0 +1,23 @@
+\name{get_labpedon_data_from_NASIS_db}
+\alias{get_labpedon_data_from_NASIS_db}
+
+\title{Extract lab pedon data from a local NASIS Database}
+\description{Get lab pedon-level data from a local NASIS database.}
+\usage{get_labpedon_data_from_NASIS_db(SS = TRUE)}
+\arguments{
+  \item{SS}{fetch data from the currently loaded selected set in NASIS or from the entire local database (default: TRUE)}
+}
+\value{A data.frame.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+\details{This function currently works only on Windows, and requires a 'nasis_local' ODBC connection.}
+\note{This function queries KSSL laboratory site/horizon data from a local NASIS database from the lab pedon data table.}
+
+\seealso{
+\code{\link{get_lablayer_data_from_NASIS_db}} 
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/get_site_data_from_NASIS_db.Rd b/misc/man-deprecated/get_site_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..42869586
--- /dev/null
+++ b/misc/man-deprecated/get_site_data_from_NASIS_db.Rd
@@ -0,0 +1,23 @@
+\name{get_site_data_from_NASIS_db}
+\alias{get_site_data_from_NASIS_db}
+
+\title{Extract Site Data from a local NASIS Database}
+\description{Get site-level data from a local NASIS database.}
+\usage{get_site_data_from_NASIS_db(SS = TRUE, stringsAsFactors = default.stringsAsFactors())}
+\arguments{
+  \item{SS}{fetch data from Selected Set in NASIS or from the entire local database (default: TRUE)}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have been set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+}
+\value{A data.frame.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+\details{When multiple "site bedrock" entries are present, only the shallowest is returned by this function.}
+\note{This function currently works only on Windows.}
+
+\seealso{
+\code{\link{get_hz_data_from_NASIS_db}}, 
+}
+
+
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/get_site_data_from_pedon_db.Rd b/misc/man-deprecated/get_site_data_from_pedon_db.Rd
new file mode 100644
index 00000000..d275630f
--- /dev/null
+++ b/misc/man-deprecated/get_site_data_from_pedon_db.Rd
@@ -0,0 +1,28 @@
+\name{get_site_data_from_pedon_db}
+\alias{get_site_data_from_pedon_db}
+
+\title{Extract Site Data from a PedonPC Database}
+\description{Get site-level data from a PedonPC database.}
+\usage{
+get_site_data_from_pedon_db(dsn)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The path to a 'pedon.mdb' database.}
+}
+
+\value{A data.frame.}
+
+\author{Dylan E. Beaudette and Jay M. Skovlin}
+\note{This function currently works only on Windows.}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_veg_from_AK_Site}}, 
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
+
diff --git a/misc/man-deprecated/get_soilseries_from_NASIS.Rd b/misc/man-deprecated/get_soilseries_from_NASIS.Rd
new file mode 100644
index 00000000..14c5f21a
--- /dev/null
+++ b/misc/man-deprecated/get_soilseries_from_NASIS.Rd
@@ -0,0 +1,26 @@
+\name{get_soilseries_from_NASIS}
+\alias{get_soilseries_from_NASIS}
+\alias{get_soilseries_from_NASISWebReport}
+
+\title{Get records from the Soil Classification (SC) database}
+\description{These functions return records from the Soil Classification database, either from the local NASIS database (all series) or via web report (named series only).}
+
+\usage{
+
+get_soilseries_from_NASIS(stringsAsFactors = default.stringsAsFactors())
+get_soilseries_from_NASISWebReport(soils, 
+stringsAsFactors = default.stringsAsFactors())
+}
+
+\arguments{
+  \item{soils}{character vector of soil series names}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? This argument is passed to the uncode() function. It does not convert those vectors that have set outside of uncode() (i.e. hard coded). The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+  
+}
+
+
+\value{A \code{data.frame}.}
+
+\author{Stephen Roecker}
+
+\keyword{manip}
diff --git a/misc/man-deprecated/get_text_notes_from_NASIS_db.Rd b/misc/man-deprecated/get_text_notes_from_NASIS_db.Rd
new file mode 100644
index 00000000..67f67309
--- /dev/null
+++ b/misc/man-deprecated/get_text_notes_from_NASIS_db.Rd
@@ -0,0 +1,42 @@
+\name{get_text_notes_from_NASIS_db}
+\alias{get_text_notes_from_NASIS_db}
+
+\title{Extract text note data from a local NASIS Database}
+\description{Extract text note data from a local NASIS Database.}
+\usage{
+get_text_notes_from_NASIS_db(SS = TRUE, fixLineEndings = TRUE)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
+  \item{fixLineEndings}{convert line endings from "\\r\\n" to "\\n"}
+}
+\details{This function currently works only on Windows.}
+\value{A list with the results.}
+\author{Dylan E. Beaudette and Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+}
+
+\examples{
+\donttest{
+if(local_NASIS_defined()) {
+ # query text note data
+ t <- try(get_text_notes_from_NASIS_db())
+
+ # show contents text note data, includes: siteobs, site, pedon, horizon level text notes data.
+ str(t)
+
+ # view text categories for site text notes
+ if(!inherits(t, 'try-error')) {
+  table(t$site_text$textcat)
+ }
+}
+}
+}
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_data_from_NASIS_db.Rd b/misc/man-deprecated/get_veg_data_from_NASIS_db.Rd
new file mode 100644
index 00000000..891bb0e3
--- /dev/null
+++ b/misc/man-deprecated/get_veg_data_from_NASIS_db.Rd
@@ -0,0 +1,31 @@
+\name{get_veg_data_from_NASIS_db}
+\alias{get_veg_data_from_NASIS_db}
+
+\title{Extract veg data from a local NASIS Database}
+\description{Extract veg data from a local NASIS Database.}
+\usage{
+get_veg_data_from_NASIS_db(SS = TRUE)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{SS}{get data from the currently loaded Selected Set in NASIS or from the entire local database (default: TRUE)}
+}
+\details{This function currently works only on Windows.}
+\value{A list with the results.}
+\author{Jay M. Skovlin and Dylan E. Beaudette}
+
+
+\examples{
+\donttest{
+if(local_NASIS_defined()) {
+ # query text note data
+ v <- try(get_veg_from_NASIS_db())
+
+ # show contents veg data returned
+ str(v)
+}
+}
+}
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_from_AK_Site.Rd b/misc/man-deprecated/get_veg_from_AK_Site.Rd
new file mode 100644
index 00000000..9ac9cd80
--- /dev/null
+++ b/misc/man-deprecated/get_veg_from_AK_Site.Rd
@@ -0,0 +1,26 @@
+\name{get_veg_from_AK_Site}
+\alias{get_veg_from_AK_Site}
+%- Also NEED an '\alias' for EACH other topic documented here.
+\title{Retrieve Vegetation Data from an AK Site Database}
+\description{Retrieve Vegetation Data from an AK Site Database}
+\usage{
+get_veg_from_AK_Site(dsn)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{file path the the AK Site access database}
+}
+
+\value{A data.frame with vegetation data in long format, linked to site ID.}
+\author{Dylan E. Beaudette}
+\note{This function currently works only on Windows.}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_hz_data_from_pedon_db}}, \code{\link{get_site_data_from_pedon_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_from_MT_veg_db.Rd b/misc/man-deprecated/get_veg_from_MT_veg_db.Rd
new file mode 100644
index 00000000..37413c28
--- /dev/null
+++ b/misc/man-deprecated/get_veg_from_MT_veg_db.Rd
@@ -0,0 +1,25 @@
+\name{get_veg_from_MT_veg_db}
+\alias{get_veg_from_MT_veg_db}
+
+\title{Extract Site and Plot-level Data from a Montana RangeDB database}
+\description{Get Site and Plot-level data from a Montana RangeDB database.}
+\usage{
+get_veg_from_MT_veg_db(dsn) 
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame.}
+\author{Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_veg_species_from_MT_veg_db}}, \code{\link{get_veg_other_from_MT_veg_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_from_NPS_PLOTS_db.Rd b/misc/man-deprecated/get_veg_from_NPS_PLOTS_db.Rd
new file mode 100644
index 00000000..9b61585d
--- /dev/null
+++ b/misc/man-deprecated/get_veg_from_NPS_PLOTS_db.Rd
@@ -0,0 +1,20 @@
+\name{get_veg_from_NPS_PLOTS_db}
+\alias{get_veg_from_NPS_PLOTS_db}
+
+\title{Retrieve Vegetation Data from an NPS PLOTS Database}
+
+\description{Used to extract species, stratum, and cover vegetation data from a backend NPS PLOTS Database.  Currently works for any Microsoft Access database with an .mdb file format.}
+
+\usage{get_veg_from_NPS_PLOTS_db(dsn)}
+
+\arguments{
+  \item{dsn}{file path to the NPS PLOTS access database on your system.}
+}
+
+\value{A data.frame with vegetation data in a long format with linkage to NRCS soil pedon data via the site_id key field.}
+
+\author{Jay M. Skovlin}
+
+\note{This function currently only works on Windows.}
+
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_other_from_MT_veg_db.Rd b/misc/man-deprecated/get_veg_other_from_MT_veg_db.Rd
new file mode 100644
index 00000000..8d2bb515
--- /dev/null
+++ b/misc/man-deprecated/get_veg_other_from_MT_veg_db.Rd
@@ -0,0 +1,25 @@
+\name{get_veg_other_from_MT_veg_db}
+\alias{get_veg_other_from_MT_veg_db}
+
+\title{Extract cover composition data from a Montana RangeDB database}
+\description{Get cover composition data from a Montana RangeDB database.}
+\usage{
+get_veg_other_from_MT_veg_db(dsn) 
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame.}
+\author{Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_veg_from_MT_veg_db}}, \code{\link{get_veg_species_from_MT_veg_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/get_veg_species_from_MT_veg_db.Rd b/misc/man-deprecated/get_veg_species_from_MT_veg_db.Rd
new file mode 100644
index 00000000..2e6c8b2e
--- /dev/null
+++ b/misc/man-deprecated/get_veg_species_from_MT_veg_db.Rd
@@ -0,0 +1,25 @@
+\name{get_veg_species_from_MT_veg_db}
+\alias{get_veg_species_from_MT_veg_db}
+
+\title{Extract species-level Data from a Montana RangeDB database}
+\description{Get species-level data from a Montana RangeDB database.}
+\usage{
+get_veg_species_from_MT_veg_db(dsn) 
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{dsn}{The name of the Montana RangeDB front-end database connection (see details).}
+}
+\details{This function currently works only on Windows.}
+\value{A data.frame.}
+\author{Jay M. Skovlin}
+
+%% ~Make other sections like Warning with \section{Warning }{....} ~
+
+\seealso{
+\code{\link{get_veg_from_MT_veg_db}}, \code{\link{get_veg_other_from_MT_veg_db}}
+}
+
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}
diff --git a/misc/man-deprecated/loafercreek.Rd b/misc/man-deprecated/loafercreek.Rd
new file mode 100644
index 00000000..0b3f564f
--- /dev/null
+++ b/misc/man-deprecated/loafercreek.Rd
@@ -0,0 +1,73 @@
+\name{loafercreek}
+\alias{loafercreek}
+\alias{gopheridge}
+\alias{mineralKing}
+
+
+\docType{data}
+\title{Example \code{SoilProfilecollection} Objects Returned by \code{fetchNASIS}.}
+
+\description{Several examples of soil profile collections returned by \code{fetchNASIS(from='pedons')} as \code{SoilProfileCollection} objects.}
+
+\usage{
+data(loafercreek)
+data(gopheridge)
+data(mineralKing)
+}
+
+
+\examples{
+\donttest{
+if(require("aqp")) {
+# load example dataset
+  data("gopheridge")
+  
+  # what kind of object is this?
+  class(gopheridge)
+  
+  # how many profiles?
+  length(gopheridge)
+  
+  # there are 60 profiles, this calls for a split plot
+  par(mar=c(0,0,0,0), mfrow=c(2,1))
+  
+  # plot soil colors
+  plot(gopheridge[1:30, ], name='hzname', color='soil_color')
+  plot(gopheridge[31:60, ], name='hzname', color='soil_color')
+  
+  # need a larger top margin for legend
+  par(mar=c(0,0,4,0), mfrow=c(2,1))
+  # generate colors based on clay content
+  plot(gopheridge[1:30, ], name='hzname', color='clay')
+  plot(gopheridge[31:60, ], name='hzname', color='clay')
+  
+  # single row and no labels
+  par(mar=c(0,0,0,0), mfrow=c(1,1))
+  # plot soils sorted by depth to contact
+  plot(gopheridge, name='', print.id=FALSE, plot.order=order(gopheridge$bedrckdepth))
+  
+  # plot first 10 profiles
+  plot(gopheridge[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+  
+  # add rock fragment data to plot:
+  addVolumeFraction(gopheridge[1:10, ], colname='total_frags_pct')
+  
+  # add diagnostic horizons
+  addDiagnosticBracket(gopheridge[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+  
+  ## loafercreek
+  data("loafercreek")
+  # plot first 10 profiles
+  plot(loafercreek[1:10, ], name='hzname', color='soil_color', label='pedon_id', id.style='side')
+  
+  # add rock fragment data to plot:
+  addVolumeFraction(loafercreek[1:10, ], colname='total_frags_pct')
+  
+  # add diagnostic horizons
+  addDiagnosticBracket(loafercreek[1:10, ], kind='argillic horizon', col='red', offset=-0.4)
+}
+}
+}
+
+
+\keyword{datasets}
diff --git a/misc/man-deprecated/local_NASIS_defined.Rd b/misc/man-deprecated/local_NASIS_defined.Rd
new file mode 100644
index 00000000..7c4de80f
--- /dev/null
+++ b/misc/man-deprecated/local_NASIS_defined.Rd
@@ -0,0 +1,25 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/openNASISchannel.R
+\name{local_NASIS_defined}
+\alias{local_NASIS_defined}
+\title{Check for presence of \code{nasis_local} ODBC data source}
+\usage{
+local_NASIS_defined(static_path = NULL)
+}
+\arguments{
+\item{static_path}{Optional: path to local SQLite database containing NASIS table structure; default: NULL}
+}
+\value{
+logical
+}
+\description{
+Check for presence of \code{nasis_local} ODBC data source
+}
+\examples{
+
+if(local_NASIS_defined()) {
+  # use fetchNASIS or some other lower-level fetch function
+} else {
+  message('could not find `nasis_local` ODBC data source')
+}
+}
diff --git a/misc/man-deprecated/makeChunks.Rd b/misc/man-deprecated/makeChunks.Rd
new file mode 100644
index 00000000..e10918d1
--- /dev/null
+++ b/misc/man-deprecated/makeChunks.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/SDA_query.R
+\name{makeChunks}
+\alias{makeChunks}
+\title{Generate chunk labels for splitting data}
+\usage{
+makeChunks(ids, size = 100)
+}
+\arguments{
+\item{ids}{vector of IDs}
+
+\item{size}{chunk (group) size}
+}
+\value{
+A numeric vector
+}
+\description{
+Generate chunk labels for splitting data
+}
+\examples{
+
+# split the lowercase alphabet into 2 chunks
+
+aggregate(letters, 
+          by = list(makeChunks(letters, size=13)), 
+          FUN = paste0, collapse=",")
+
+}
diff --git a/misc/man-deprecated/mukey.wcs.Rd b/misc/man-deprecated/mukey.wcs.Rd
new file mode 100644
index 00000000..0b42915e
--- /dev/null
+++ b/misc/man-deprecated/mukey.wcs.Rd
@@ -0,0 +1,41 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/mukey-WCS.R
+\name{mukey.wcs}
+\alias{mukey.wcs}
+\title{gNATSGO / gSSURGO Map Unit Key Web Coverage Service (WCS)}
+\usage{
+mukey.wcs(aoi, db = c("gnatsgo", "gssurgo"), res = 30, quiet = FALSE)
+}
+\arguments{
+\item{aoi}{area of interest (AOI) defined using a \code{Spatial*}, a \code{sf}, \code{sfc} or \code{bbox} object or a \code{list}, see details}
+
+\item{db}{name of the gridded map unit key grid to access, should be either 'gnatsgo' or 'gssurgo'}
+
+\item{res}{grid resolution, units of meters. The native resolution of gNATSGO and gSSURGO (this WCS) is 30m.}
+
+\item{quiet}{logical, passed to \code{download.file} to enable / suppress URL and progress bar for download.}
+}
+\value{
+\code{raster} object containing indexed map unit keys and associated raster attribute table
+}
+\description{
+Download chunks of the gNATSGO or gSSURGO map unit key grid via bounding-box from the SoilWeb WCS.
+}
+\details{
+\code{aoi} should be specified as either a \code{Spatial*}, \code{sf}, \code{sfc} or \code{bbox} object or a \code{list} containing:
+
+\describe{
+\item{\code{aoi}}{bounding-box specified as (xmin, ymin, xmax, ymax) e.g. c(-114.16, 47.65, -114.08, 47.68)}
+\item{\code{crs}}{coordinate reference system of BBOX, e.g. '+init=epsg:4326'}
+}
+
+The WCS query is parameterized using \code{raster::extent} derived from the above AOI specification, after conversion to the native CRS (EPSG:6350) of the gNATSGO / gSSURGO grid.
+
+Databases available from this WCS can be queried using \code{WCS_details(wcs = 'mukey')}.
+}
+\note{
+The gNATSGO grid includes raster soil survey map unit keys which are not in SDA.
+}
+\author{
+D.E. Beaudette and A.G. Brown
+}
diff --git a/misc/man-deprecated/parseWebReport.Rd b/misc/man-deprecated/parseWebReport.Rd
new file mode 100644
index 00000000..d25ac569
--- /dev/null
+++ b/misc/man-deprecated/parseWebReport.Rd
@@ -0,0 +1,30 @@
+\name{parseWebReport}
+\alias{parseWebReport}
+
+\title{Parse contents of a web report, based on supplied arguments.}
+\description{Parse contents of a web report, based on supplied arguments.}
+\usage{
+parseWebReport(url, args, index = 1)
+}
+
+\arguments{
+  \item{url}{Base URL to a LIMS/NASIS web report.}
+  \item{args}{List of named arguments to send to report, see details.}
+  \item{index}{Integer index specifying the table to return, or, NULL for a list of tables}
+}
+
+\details{Report argument names can be inferred by inspection of the HTML source associated with any given web report.}
+
+\value{A \code{data.frame} object in the case of a single integer passed to \code{index}, a \code{list} object in the case of an integer vector or NULL passed to \code{index}.}
+
+\author{D.E. Beaudette and S.M. Roecker}
+
+\keyword{ IO }
+
+\note{Most web reports are for internal use only.}
+
+\examples{
+\donttest{
+# pending
+}
+}
diff --git a/misc/man-deprecated/processSDA_WKT.Rd b/misc/man-deprecated/processSDA_WKT.Rd
new file mode 100644
index 00000000..cf554f42
--- /dev/null
+++ b/misc/man-deprecated/processSDA_WKT.Rd
@@ -0,0 +1,30 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/SDA-spatial.R
+\name{processSDA_WKT}
+\alias{processSDA_WKT}
+\title{Post-process WKT returned from SDA.}
+\usage{
+processSDA_WKT(d, g = "geom", p4s = "+proj=longlat +datum=WGS84")
+}
+\arguments{
+\item{d}{\code{data.frame} returned by \code{SDA_query}, containing WKT representation of geometry}
+
+\item{g}{name of column in \code{d} containing WKT geometry}
+
+\item{p4s}{PROJ4 CRS definition, typically GCS WGS84}
+}
+\value{
+A \code{Spatial*} object.
+}
+\description{
+This is a helper function, commonly used with \code{SDA_query} to extract WKT (well-known text) representation of geometry to an sp-class object.
+}
+\details{
+The SDA website can be found at \url{https://sdmdataaccess.nrcs.usda.gov}. See the \href{http://ncss-tech.github.io/AQP/soilDB/SDA-tutorial.html}{SDA Tutorial} for detailed examples.
+}
+\note{
+This function requires the \code{httr}, \code{jsonlite}, \code{XML}, and \code{rgeos} packages.
+}
+\author{
+D.E. Beaudette
+}
diff --git a/misc/man-deprecated/seriesExtent.Rd b/misc/man-deprecated/seriesExtent.Rd
new file mode 100644
index 00000000..2354a373
--- /dev/null
+++ b/misc/man-deprecated/seriesExtent.Rd
@@ -0,0 +1,63 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/seriesExtent.R
+\name{seriesExtent}
+\alias{seriesExtent}
+\title{Retrieve Soil Series Extent Maps from SoilWeb}
+\usage{
+seriesExtent(s, type = c("vector", "raster"), timeout = 60)
+}
+\arguments{
+\item{s}{a soil series name, case-insensitive}
+
+\item{type}{series extent representation, \code{vector} results in a \code{SpatialPolygonsDataFrame} object and \code{raster} results in a \code{raster} object}
+
+\item{timeout}{time that we are willing to wait for a response, in seconds}
+}
+\description{
+This function downloads a generalized representations of a soil series extent from SoilWeb, derived from the current SSURGO snapshot. Data can be returned as vector outlines (\code{SpatialPolygonsDataFrame} object) or gridded representation of area proportion falling within 800m cells (\code{raster} object). Gridded series extent data are only available in CONUS. Vector representations are returned with a GCS/WGS84 coordinate reference system and raster representations are returned with an Albers Equal Area / NAD83 coordinate reference system.
+}
+\note{
+This function requires the \code{rgdal} package. Warning messages about the proj4 CRS specification may be printed depending on your version of \code{rgdal}. This should be resolved soon.
+}
+\examples{
+  
+\donttest{
+if(requireNamespace("curl") &
+   curl::has_internet()) {
+  
+  # required packages
+  library(sp)
+  library(raster)
+  library(rgdal)
+  
+  # specify a soil series name
+  s <- 'magnor'
+  
+  # return as SpatialPolygonsDataFrame
+  x <- seriesExtent(s, type = 'vector')
+  # return as raster
+  y <- seriesExtent(s, type = 'raster')
+  
+  # note that CRS are different
+  proj4string(x)
+  projection(y)
+  
+  # transform vector representation to CRS of raster
+  x <- spTransform(x, CRS(projection(y)))
+  
+  # graphical comparison
+  par(mar = c(1, 1 , 1, 3))
+  plot(y, axes = FALSE)
+  plot(x, add = TRUE)
+  
+  
+}
+}
+
+}
+\references{
+\url{https://casoilresource.lawr.ucdavis.edu/see/}
+}
+\author{
+D.E. Beaudette
+}
diff --git a/misc/man-deprecated/siblings.Rd b/misc/man-deprecated/siblings.Rd
new file mode 100644
index 00000000..47a62b91
--- /dev/null
+++ b/misc/man-deprecated/siblings.Rd
@@ -0,0 +1,60 @@
+\name{siblings}
+\alias{siblings}
+
+\title{Lookup siblings and cousins for a given soil series.}
+\description{Lookup siblings and cousins for a given soil series, from the current fiscal year SSURGO snapshot via SoilWeb.}
+\usage{
+siblings(s, only.major=FALSE, component.data = FALSE, cousins = FALSE)
+}
+
+\arguments{
+  \item{s}{character vector, the name of a single soil series, case-insensitive.}
+  \item{only.major}{logical, should only return siblings that are major components}
+  \item{component.data}{logical, should component data for siblings (and optionally cousins) be returned?}
+  \item{cousins}{logical, should siblings-of-siblings (cousins) be returned?}
+}
+
+\details{The siblings of any given soil series are defined as those soil series (major and minor component) that share a parent map unit with the named series (as a major component). Cousins are siblings of siblings. Data are sourced from SoilWeb which maintains a copy of the current SSURGO snapshot.}
+
+\value{
+\describe{
+	\item{sib}{\code{data.frame} containing siblings, major component flag, and number of co-occurrences}
+	\item{sib.data}{\code{data.frame} containing sibling component data}
+	\item{cousins}{\code{data.frame} containing cousins, major component flag, and number of co-occurrences}
+	\item{cousin.data}{\code{data.frame} containing cousin component data}
+	} 
+}
+
+\references{
+\href{http://ncss-tech.github.io/AQP/soilDB/soil-series-query-functions.html}{soilDB Soil Series Query Functionality}
+
+\href{http://ncss-tech.github.io/AQP/soilDB/siblings.html}{Related tutorial.}
+}
+
+\author{
+D.E. Beaudette
+}
+
+\seealso{
+\link{OSDquery}, \link{siblings}, \link{fetchOSD}
+}
+
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet()) {
+    
+    # basic usage
+    x <- siblings('zook')
+    x$sib
+    
+    # restrict to siblings that are major components
+    # e.g. the most likely siblings
+    x <- siblings('zook', only.major = TRUE)
+    x$sib
+}
+}
+}
+
+\keyword{ manip }
+
diff --git a/man/simplfyFragmentData.Rd b/misc/man-deprecated/simplfyFragmentData.Rd
similarity index 100%
rename from man/simplfyFragmentData.Rd
rename to misc/man-deprecated/simplfyFragmentData.Rd
diff --git a/misc/man-deprecated/simplifyColorData.Rd b/misc/man-deprecated/simplifyColorData.Rd
new file mode 100644
index 00000000..acdd2b6f
--- /dev/null
+++ b/misc/man-deprecated/simplifyColorData.Rd
@@ -0,0 +1,38 @@
+\name{simplifyColorData}
+\alias{simplifyColorData}
+\alias{mix_and_clean_colors}
+
+\title{Simplify Color Data by ID}
+\description{Simplify multiple Munsell color observations associated with each horizon.}
+\usage{
+simplifyColorData(d, id.var = "phiid", wt = "colorpct", bt = FALSE)
+mix_and_clean_colors(x, wt='pct', backTransform = FALSE)
+}
+
+\arguments{
+  \item{d}{a \code{data.frame} object, typically returned from NASIS, see details}
+  \item{id.var}{character vector with the name of the column containing an ID that is unique among all horizons in \code{d}}
+  \item{x}{a \code{data.frame} object containing sRGB coordinates associated with a group of colors to mix}
+  \item{wt}{a character vector with the name of the column containing color weights for mixing}
+  \item{bt}{logical, should the mixed sRGB representation of soil color be transformed to closest Munsell chips? This is performed by \code{aqp::rgb2Munsell}}  
+  \item{backTransform}{logical, should the mixed sRGB representation of soil color be transformed to closest Munsell chips? This is performed by \code{aqp::rgb2Munsell}}
+}
+
+\details{
+This function is mainly intended for the processing of NASIS pedon/horizon data which may or may not contain multiple colors per horizon/moisture status combination. \code{simplifyColorData} will "mix" multiple colors associated with horizons in \code{d}, according to IDs specified by \code{id.var}, using "weights" (area percentages) specified by the \code{wt} argument to \code{mix_and_clean_colors}.
+
+Note that this function doesn't actually simulate the mixture of pigments on a surface, rather, "mixing" is approximated via weighted average in the CIELAB colorspace.
+
+The \code{simplifyColorData} function can be applied to data sources other than NASIS by careful use of the \code{id.var} and \code{wt} arguments. However, \code{d} must contain Munsell colors split into columns named "colorhue", "colorvalue", and "colorchroma". In addition, the moisture state ("Dry" or "Moist") must be specified in a column named "colormoistst".
+
+The \code{mix_and_clean_colors} function can be applied to arbitrary data sources as long as \code{x} contains sRGB coordinates in columns named "r", "g", and "b". This function should be applied to chunks of rows within which color mixtures make sense.
+
+There are examples in \href{http://ncss-tech.github.io/AQP/soilDB/KSSL-demo.html}{the KSSL data tutorial} and \href{http://ncss-tech.github.io/AQP/soilDB/mixing-soil-color-data.html}{the soil color mixing tutorial}.
+}
+
+
+\author{D.E. Beaudette}
+
+
+\keyword{manip}
+
diff --git a/misc/man-deprecated/soilDB-package.Rd b/misc/man-deprecated/soilDB-package.Rd
new file mode 100644
index 00000000..4f2f122d
--- /dev/null
+++ b/misc/man-deprecated/soilDB-package.Rd
@@ -0,0 +1,15 @@
+\name{soilDB-package}
+\alias{soilDB.env}
+\alias{soilDB-package}
+\alias{soilDB}
+\docType{package}
+\title{Soil Database Interface}
+\description{This package provides methods for extracting soils information from local PedonPC and AK Site databases (MS Access format), local NASIS databases (MS SQL Server), and the SDA webservice. Currently USDA-NCSS data sources are supported, however, there are plans to develop interfaces to outside systems such as the Global Soil Mapping project.}
+\details{
+It can be difficult to locate all of the dependencies required for sending/processing SOAP requests, especially on UNIX-like operating systems. Windows binary packages for the dependencies can be found \href{http://www.stats.ox.ac.uk/pub/RWin/bin/windows/contrib/2.15/}{here}. See \code{\link{fetchPedonPC}} for a simple wrapper function that should suffice for typical site/pedon/hz queries. An introduction to the soilDB package can be found \href{https://r-forge.r-project.org/scm/viewvc.php/*checkout*/docs/soilDB/soilDB-Intro.html?root=aqp}{here}.
+}
+\author{J.M. Skovlin and D.E. Beaudette}
+\keyword{package}
+\seealso{\code{\link{fetchPedonPC}, \link{fetchNASIS}, \link{SDA_query}, \link{loafercreek}}}
+
+
diff --git a/misc/man-deprecated/taxaExtent.Rd b/misc/man-deprecated/taxaExtent.Rd
new file mode 100644
index 00000000..b95825cd
--- /dev/null
+++ b/misc/man-deprecated/taxaExtent.Rd
@@ -0,0 +1,82 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/taxaExtent.R
+\name{taxaExtent}
+\alias{taxaExtent}
+\title{Retrieve Soil Taxonomy Membership Grids}
+\usage{
+taxaExtent(
+  x,
+  level = c("order", "suborder", "greatgroup", "subgroup"),
+  timeout = 60
+)
+}
+\arguments{
+\item{x}{single taxa name, case-insensitive}
+
+\item{level}{the taxonomic level within the top 4 tiers of Soil Taxonomy, one of \code{c('order', 'suborder', 'greatgroup', 'subgroup')}}
+
+\item{timeout}{time that we are willing to wait for a response, in seconds}
+}
+\value{
+a \code{raster} object
+}
+\description{
+This function downloads a generalized representation of the geographic extent of any single taxa from the top 4 tiers of Soil Taxonomy. Data are provided by SoilWeb, ultimately sourced from from the current SSURGO snapshot. Data are returned as \code{raster} objects representing area proportion falling within 800m cells. Data are only available in CONUS and returned using an Albers Equal Area / NAD83 coordinate reference system.
+}
+\note{
+This is a work in progress.
+}
+\examples{
+\donttest{
+
+if(requireNamespace("curl") &
+   curl::has_internet()) {
+  
+  library(raster)
+  
+  # try a couple of different examples
+  
+  # soil order
+  taxa <- 'vertisols'
+  x <- taxaExtent(taxa, level = 'order')
+  a <- raster::aggregate(x, fact = 5)
+  
+  # suborder
+  taxa <- 'ustalfs'
+  x <- taxaExtent(taxa, level = 'suborder')
+  a <- raster::aggregate(x, fact = 5)
+  
+  # greatgroup
+  taxa <- 'haplohumults'
+  x <- taxaExtent(taxa, level = 'greatgroup')
+  a <- raster::aggregate(x, fact = 5)
+  
+  # subgroup
+  taxa <- 'Typic Haploxerepts'
+  x <- taxaExtent(taxa, level = 'subgroup')
+  a <- raster::aggregate(x, fact = 5)
+  
+  # quick evaluation of the result
+  if(requireNamespace("rasterVis") & requireNamespace('viridis')) {
+    rasterVis::levelplot(a, 
+      margin = FALSE, scales = list(draw = FALSE), 
+      col.regions = viridis::viridis, 
+      main = names(a)
+    )
+  }
+  
+  # slippy map
+  if(requireNamespace("mapview")) {
+    mapview::mapview(a, col.regions = viridis::viridis, na.color = NA, use.layer.names = TRUE)
+  }
+  
+  
+  
+}
+
+}
+
+}
+\author{
+D.E. Beaudette
+}
diff --git a/misc/man-deprecated/uncode.Rd b/misc/man-deprecated/uncode.Rd
new file mode 100644
index 00000000..43d81272
--- /dev/null
+++ b/misc/man-deprecated/uncode.Rd
@@ -0,0 +1,53 @@
+\name{uncode}
+\alias{metadata}
+\alias{uncode}
+\alias{code}
+
+\title{Convert coded values returned from NASIS and SDA queries to factors}
+\description{These functions convert the coded values returned from NASIS or SDA to factors (e.g. 1 = Alfisols) using the metadata tables from NASIS. For SDA the metadata is pulled from a static snapshot in the soilDB package (/data/metadata.rda).}
+\usage{
+uncode(df, invert = FALSE, db = "NASIS", 
+       droplevels = FALSE,
+       stringsAsFactors = default.stringsAsFactors()
+       )
+code(df, ...)
+}
+%- maybe also 'usage' for other objects documented here.
+\arguments{
+  \item{df}{data.frame}
+  \item{invert}{converts the code labels back to their coded values (FALSE)}
+  \item{db}{label specifying the soil database the data is coming from, which indicates whether or not to query metadata from local NASIS database ("NASIS") or use soilDB-local snapshot ("LIMS" or "SDA")}
+  \item{droplevels}{logical: indicating whether to drop unused levels in classifying factors. This is useful when a class has large number of unused classes, which can waste space in tables and figures.}
+  \item{stringsAsFactors}{logical: should character vectors be converted to factors? The 'factory-fresh' default is TRUE, but this can be changed by setting options(stringsAsFactors = FALSE)}
+  \item{\dots}{arguments passed on to \code{uncode}}
+  }
+  
+\details{These functions convert the coded values returned from NASIS into their plain text representation. It duplicates the functionality of the CODELABEL function found in NASIS. This function is primarily intended to be used internally by other soilDB R functions, in order to minimizes the need to manually convert values. 
+
+The function works by iterating through the column names in a data frame and looking up whether they match any of the ColumnPhysicalNames found in the metadata domain tables. If matches are found then the columns coded values are converted to their corresponding factor levels. Therefore it is not advisable to reuse column names from NASIS unless the contents match the range of values and format found in NASIS. Otherwise uncode() will convert their values to NA.
+
+When data is being imported from NASIS, the metadata tables are sourced directly from NASIS. When data is being imported from SDA or the NASIS Web Reports, the metadata is pulled from a static snapshot in the soilDB package.
+
+Beware the default is to return the values as factors rather than strings. While strings are generally preferable, factors make plotting more convenient. Generally the factor level ordering returned by uncode() follows the naturally ordering of categories that would be expected (e.g. sand, silt, clay).
+}
+
+\value{A data frame with the results.}
+\author{Stephen Roecker}
+\examples{
+\donttest{
+if(requireNamespace("curl") &
+    curl::has_internet() &
+    require(aqp)) {
+  # query component by nationalmusym
+  comp <- fetchSDA(WHERE = "nationalmusym = '2vzcp'")
+  s <- site(comp)
+  
+  # use SDA uncoding domain via db argument
+  s <- uncode(s,  db="SDA")
+  levels(s$taxorder)
+}
+}
+}
+% Add one or more standard keywords, see file 'KEYWORDS' in the
+% R documentation directory.
+\keyword{manip}% use one of  RShowDoc("KEYWORDS")
diff --git a/misc/man-deprecated/us_ss_timeline.Rd b/misc/man-deprecated/us_ss_timeline.Rd
new file mode 100644
index 00000000..f18b8557
--- /dev/null
+++ b/misc/man-deprecated/us_ss_timeline.Rd
@@ -0,0 +1,33 @@
+\name{us_ss_timeline}
+\alias{us_ss_timeline}
+\docType{data}
+
+\title{
+Timeline of US Published Soil Surveys
+}
+
+\description{
+This dataset contains the years of each US Soil Survey was published.
+}
+
+\usage{data("us_ss_timeline")}
+
+\format{
+  A data frame with 5209 observations on the following 5 variables.
+  \describe{
+    \item{\code{ssa}}{Soil Survey name, a character vector}
+    \item{\code{year}}{year of publication, a numeric vector}
+    \item{\code{pdf}}{does a pdf exists, a logical vector}
+    \item{\code{state}}{State abbreviation, a character vector}
+  }
+}
+
+\details{
+This data was web scraped from the NRCS Soils Website. The scraping procedure and a example plot are included in the examples section below.
+}
+
+\source{
+https://www.nrcs.usda.gov/wps/portal/nrcs/soilsurvey/soils/survey/state/
+}
+
+\keyword{datasets}
diff --git a/misc/man-deprecated/waterDayYear.Rd b/misc/man-deprecated/waterDayYear.Rd
new file mode 100644
index 00000000..f28b3fb1
--- /dev/null
+++ b/misc/man-deprecated/waterDayYear.Rd
@@ -0,0 +1,38 @@
+\name{waterDayYear}
+\alias{waterDayYear}
+
+\title{Compute Water Day and Year}
+\description{Compute "water" day and year, based on the end of the typical or legal dry season. This is September 30 in California.}
+
+\usage{
+waterDayYear(d, end = "09-30")
+}
+
+\arguments{
+  \item{d}{anything the can be safely converted to \code{PPOSIXlt}}
+  \item{end}{"MM-DD" notation for end of water year}
+}
+
+\details{This function doesn't know about leap-years. Probably worth checking.}
+
+\value{
+A \code{data.frame} object with the following
+  \item{wy}{the "water year"}
+  \item{wd}{the "water day"}
+}
+
+\references{Ideas borrowed from:
+\url{https://github.com/USGS-R/dataRetrieval/issues/246} and
+\url{https://stackoverflow.com/questions/48123049/create-day-index-based-on-water-year}
+}
+
+\author{D.E. Beaudette}
+
+
+\examples{
+# try it
+waterDayYear('2019-01-01')
+}
+
+\keyword{ manip }% use one of  RShowDoc("KEYWORDS")
+
diff --git a/misc/nasis_pedon_object/check_pedon_table_columns.R b/misc/nasis_pedon_object/check_pedon_table_columns.R
new file mode 100644
index 00000000..b325fa03
--- /dev/null
+++ b/misc/nasis_pedon_object/check_pedon_table_columns.R
@@ -0,0 +1,64 @@
+# first install latest soilDB from nasisDBI branch (unless that is merged when one reads this)
+# remotes::install_github("ncss-tech/soilDB@nasisDBI", dependencies = FALSE)
+library(DBI)
+library(soilDB)
+
+# read file with table,column,fkey as headers
+#                where `column` value is quoted comma delimited string within a CSV
+f <- read.table("misc/nasis_pedon_object/pedon_table_columns.txt",
+                sep = ",", header = TRUE)
+
+# create colname lookup list, named by table
+cols <- strsplit(f$column, ",")
+names(cols) <- f$table
+
+# create foreign key lookup list, named by table
+fkeys <- f$fkey
+names(fkeys) <- f$table
+
+# inspect
+head(cols)
+
+head(fkeys)
+
+# get list of tables from NASIS connection
+nasis <- soilDB::NASIS()
+nasis_tables_all <- DBI::dbListTables(nasis)
+
+# all table names exist in current NASIS?
+all(f$table %in% nasis_tables_all)
+
+# bad tables names?
+badtidx <- which(!f$table %in% nasis_tables_all)
+f$table[badtidx]
+
+# what "veg" tables are in NASIS?
+# nasis_tables_all[grep("veg", nasis_tables_all)]
+
+# this uses ncss-tech/soilDB@nasisDBI to create a list of tables queried by name 
+test <- soilDB::createStaticNASIS(tables = f$table) #, output_file = "test.sqlite")
+
+# check the lookup lists against NASIS (find stuff not in NASIS)
+test.res <- sapply(f$table, function(aTable) {
+  any(!cols[[aTable]] %in% colnames(test[[aTable]]))
+})
+test.res[sapply(test.res, isTRUE)]
+
+# now check NASIS against lookup list (find new stuff in NASIS)
+test.res2 <- sapply(f$table, function(aTable) {
+  # recwlupdated and recuseriidref are calculated internally by NASIS so they are "freebies"
+  testcols <- colnames(test[[aTable]])
+  res <- testcols[!testcols %in% c(cols[[aTable]], c("recwlupdated","recuseriidref"))]
+})
+test.res2[sapply(test.res2, length) > 0]
+
+# check tables
+chktbls <- names(test.res[test.res])
+chkcols <- lapply(chktbls, function(fixtable) {
+  testcols <- cols[[fixtable]]
+  srccols <- colnames(test[[fixtable]])
+  list(pedonpc = testcols[!testcols %in% srccols], 
+       nasis_current = srccols[!srccols %in% testcols])
+})
+names(chkcols) <- chktbls
+chkcols
diff --git a/misc/nasis_pedon_object/pedon_table_columns.txt b/misc/nasis_pedon_object/pedon_table_columns.txt
new file mode 100644
index 00000000..f44178ab
--- /dev/null
+++ b/misc/nasis_pedon_object/pedon_table_columns.txt
@@ -0,0 +1,69 @@
+table, column, fkey
+'site','usiteid,latdegrees,latminutes,latseconds,latdir,longdegrees,longminutes,longseconds,longdir,horizdatnm,locdesc,plsssdetails,plsssection,plsstownship,plssrange,plssmeridian,utmzone,utmnorthing,utmeasting,geocoordsource,elev,geomposhill,geomposmntn,geompostrce,geomposflats,hillslopeprof,geomslopeseg,slope,aspect,slopelenusle,slopelenuptro,shapeacross,shapedown,slopecomplex,locphysnm,siteksatclassupper,siteksatclasslower,drainagecl,runoff,drainagepattern,pmgroupname,pmgroupname_s,climstaid,climstanm,climstatype,ffd,map,reannualprecip,airtempa,soiltempa,airtemps,soiltemps,airtempw,soiltempw,benchmarksoilflag,flodfreqcl,floddurcl,flodmonthbeg,pondfreqcl,ponddurcl,pondmonthbeg,wtabledur,latstddecimaldegrees,longstddecimaldegrees,gpspositionalerror,gpspdop,elevcorrected,sdbiidref,siteiid','siteiid'
+'siteaoverlap','seqnum,areaiidref,siteiidref,sareaoviid','siteiid' 
+'sitebedrock','seqnum,bedrockorder,geogroup,geoform,geomember,bedrckdepth,bedrckkind,bedrckhardness,bedrckfractint,bedrckweather,bedrckstrike,bedrckdip_l,bedrckdip_h,siteiidref,sitebedrockiid','siteiid'
+'siteecositehistory','ecositecorrdate,classifier,ecositeiidref,siteiidref,siteecositehistoryiid','siteiid'
+'sitegeomordesc','seqnum,geomfiidref,geomfmod,geomfeatid,existsonfeat,siteiidref,sitegeomdiid','siteiid'
+'sitepm','seqnum,pmorder,pmdept,pmdepb,pmmodifier,pmgenmod,pmkind,pmorigin,pmweathering,siteiidref,sitepmiid','siteiid'
+'sitetext','seqnum,recdate,recauthor,sitetextkind,textcat,textsubcat,textentry,siteiidref,sitetextiid','siteiid'
+'siteobs','seqnum,obsdate,obsdatekind,datacollector,photoid,swaterkind,swaterdepth,hydrologystatus,geomicrorelief,geommicelev,geommicpat,ecostateid,ecostatename,commphaseid,commphasename,plantassocnm,earthcovkind1,earthcovkind2,resourceretentionclass,bareareamaxwidth,pedodermclass,pedodermcovind,biolcrusttypedom,biolcrusttypesecond,physcrustsubtype,crustdevcl,soilredistributionclass,exposedsoilpct,localdisturbancedistance,localdisturbancedescription,drainedflag,beddingflag,plantationflag,forestrotationstage,yldstudyid,currweathcond,currairtemp,tidalperiod,bottomtype,saswatertempupper,saswatertemplower,saswaterphupper,saswaterphlower,phdetermeth,sasdissolvedoxyupper,sasdissolvedoxylower,saswatersalinityupper,saswatersalinitylower,siteiidref,siteobsiid','siteiid'
+'siteerosionacc','seqnum,erokind,siteobsiidref,siteeroacciid','siteobsiid'
+'siteexistveg','seqnum,lplantiidref,vegetationstratalevel,orderofdominance,siteobsiidref,siteexistvegiid','siteobsiid'
+'siteobstext','seqnum,recdate,recauthor,siteobstextkind,textcat,textsubcat,textentry,siteobsiidref,siteobstextiid','siteobsiid'
+'sitesoilmoist','seqnum,soimoistdept,soimoistdepb,soilmoistsensordepth,soilmoistsensorkind,obssoimoiststat,obssoimoist,obsgrsoimoist,soimoistten,siteobsiidref,sitesmiid','siteobsiid'
+'sitesoiltemp','seqnum,soitempdep,soiltempsensorkind,soitemp,siteobsiidref,sitestiid','siteobsiid'
+'sitesurffrags','seqnum,sfragcov,distrocks,sfragkind,sfragsize_l,sfragsize_r,sfragsize_h,sfragshp,sfraground,sfraghard,siteobsiidref,sitesurffragsiid','siteobsiid'
+'transect','utransectid,tsectauth,tsectkind,tsectselmeth,tsectdelinsize,tsectdir,tsectcertstatus,tsectdbiidref,tsectiid','tsectiid'
+'transectestcomposition','seqnum,compname,localphase,comppct,slope_l,slope_h,tsectiidref,tsectestcompiid','tsectiid'
+'transecttext','seqnum,recdate,recauthor,transecttextkind,textcat,textsubcat,textentry,tsectiidref,transecttextiid','tsectiid'
+'siteassoc','usiteassocid,sadbiidref,siteassociid','siteassociid'
+'siteassocsite','seqnum,siteiidref,siteassociidref,siteasiteiid','siteiid'
+'siteassocsoi','seqnum,assocsoi,siteiidref,siteassocsoiiid','siteiid'
+'siteassoctext','seqnum,recdate,recauthor,siteassoctextkind,textcat,textsubcat,textentry,siteassociidref,siteatextiid','siteassociid'
+'pedon','upedonid,pedrecorigin,descname,tsectstopnum,tsectinterval,taxonkind,taxonname,taxclname,pedontype,pedonpurpose,pedonunit,relexpsize,relexpuom,earthcovkind1,earthcovkind2,erocl,labsourceid,pedlabsampnum,crustdevcl,physcrustsubtype,labdatadescflag,sascorelength,sascoresettlement,sascorestoragesite,sasexposurebegin,sasexposureend,saspipelengthext,saspipelengthtot,saspipelengthunfilled,pedodermclass,pedodermcovind,biolcrusttypedom,biolcrusttypesecond,pedonhydricrating,pedbiidref,siteobsiidref,tsectiidref,peiid','peiid'
+'pediagfeatures','seqnum,featkind,featdept,featdepb,featthick_l,featthick_r,featthick_h,peiidref,pediagfeatiid','peiid'
+'pefmp','seqnum,fmpname,fmpvalue,fmpunits,peiidref,pefmpiid','peiid'
+'pehydricfieldindicator','seqnum,hydricsoilfieldindicator,peiidref,pehydricfieldindiid','peiid'
+'pepenetrationresistance','seqnum,penetrometerdepth,obssoimoiststat,penetrometertiptype,penetrometerspringtype,penetorient,penetrometerreading1,penetrometerreading2,penetrometerreading3,penetrometerreading4,datacollector,peiidref,pepenetrometeriid','peiid'
+'perestrictions','seqnum,reskind,reshard,resdept,resdepb,resthk_l,resthk_r,resthk_h,peiidref,perestrictiid','peiid'
+'pesoilstability','seqnum,soilstabilitydepth,samplecollector,datacollector,testdate,soilstabilityclass1,soilstabilityclass2,soilstabilityclass3,soilstabilityclasspredom,soilhydrophobicind1,soilhydrophobicind2,soilhydrophobicind3,peiidref,pesoilstabilityiid','peiid'
+'petaxhistory','seqnum,classdate,classtype,classifier,taxonname,taxonkind,seriesstatus,taxclname,taxclname_s,taxorder,taxsuborder,taxgrtgroup,taxsubgrp,taxpartsize,taxpartsizemod,taxceactcl,taxreaction,taxtempcl,taxmoistscl,taxtempregime,soiltaxedition,osdtypelocflag,psctopdepth,pscbotdepth,peiidref,petaxhistoryiid','peiid'
+'petext','seqnum,recdate,recauthor,pedontextkind,textcat,textsubcat,textentry,peiidref,petextiid','peiid'
+'peinfiltrationsummary','seqnum,testdate,datacollector,infiltrationmean,infiltrationstddev,infiltrationtestmethod,peiidref,pedoninfilsumiid','peiid'
+'petaxhistfmmin','seqnum,taxminalogy,minorder,pedtaxhistoryiidref,petaxfmminiid','pedtaxhistoryiid'
+'petxhistfmother','seqnum,taxfamother,pedtaxhistoryiidref,petaxfoiid','pedtaxhistoryiid'
+'petaxhistmoistcl','seqnum,taxmoistcl,pedtaxhistoryiidref,petaxmciid','pedtaxhistoryiid'
+'peinfiltrationch','testdate,repnum,infiltrationmeasured,infiltrationringconfig,ringinsertiondepth,ringradius,waterponddepth,mariottebottleradius,notes,peinfilsumiidref,peinfilconstheadiid','peinfilsumiid'
+'peinfiltrationfh','testdate,repnum,infiltrationmeasured,infiltrationringconfig,ringinsertiondepth,ringradius,notes,peinfilsumiidref,peinfilfallheadiid','peinfilsumiid'
+'peinfiltrationchdata','infiltrationrunnum,waterdrop,deltatime,infiltrationmeasured,steadystateflag,peinfilconstheadiidref,peinfilchdataiid','peinfilconstheadiid'
+'peinfiltrationfhdata','infiltrationrunnum,deltatime,infiltrationwatervolume,peinfilfallheadiidref,pedoninfilfhdataiid','peinfilfallheadiid'
+'phorizon','seqnum,obsmethod,hzname,hzname_s,desgndisc,desgnmaster,desgnmasterprime,desgnvert,hzdept,hzdepb,hzthk_l,hzthk_r,hzthk_h,texture,texture_s,stratextsflag,claytotest,claycarbest,silttotest,sandtotest,carbdevstagefe,carbdevstagecf,fragvoltot,horcolorvflag,fiberrubbedpct,fiberunrubbedpct,obssoimoiststat,rupresblkmst,rupresblkdry,rupresblkcem,rupresplate,mannerfailure,stickiness,plasticity,toughclass,penetrres,penetorient,ksatpedon,ksatstddev,ksatrepnum,horzpermclass,obsinfiltrationrate,phfield,phdetermeth,phnaf,effclass,efflocation,effagent,mneffclass,mneffagent,reactadipyridyl,dipyridylpct,dipyridylloc,ecmeasured,ecdeterminemeth,ec15,excavdifcl,soilodor,soilodorintensity,rmonosulfidep,bounddistinct,boundtopo,horzvoltotpct_l,horzvoltotpct_r,horzvoltotpct_h,horzlatareapct_l,horzlatareapct_r,horzlatareapct_h,peiidref,phiid','peiid'
+'phcemagent','seqnum,ruprescem,phiidref,phcemagentiid','phiid'
+'phcolor','seqnum,colorpct,colorphysst,colorhue,colorvalue,colorchroma,colormoistst,phiidref,phcoloriid','phiid'
+'phconcs','seqnum,concpct,concsize,conccntrst,conchardness,concshape,conckind,conclocation,concboundary,phiidref,phconceniid','phiid'
+'phcracks','seqnum,crackfreq,crackkind,crackdepth,crackwidth,crackextabove,crackextbelow,phiidref,phcracksiid','phiid'
+'phdesgnsuffix','seqnum,desgnsuffix,phiidref,phdesgnsfxiid','phiid'
+'phfeatures','seqnum,horfeatkind,horfeatvtpct_l,horfeatvtpct_r,horfeatvtpct_h,horfeatlapct_l,horfeatlapct_r,horfeatlapct_h,phiidref,phfeatsiid','phiid'
+'phfmp','seqnum,fmpname,fmpvalue,fmpunits,phiidref,phfmpiid','phiid'
+'phfrags','seqnum,fragvol,fragweight,fragkind,fragsize_l,fragsize_r,fragsize_h,fragshp,fraground,fraghard,fragestmethod,phiidref,phfragsiid','phiid'
+'phhuarts','seqnum,huartvol,huartsize_l,huartsize_r,huartsize_h,huartkind,huartco,huartshp,huartrnd,huartpen,huartsafety,huartper,phiidref,phhuartiid','phiid'
+'phmottles','seqnum,mottlepct,mottlesize,mottlecntrst,colorhue,colorvalue,colorchroma,mottleshape,colormoistst,mottleloc,phiidref,phmottlesiid','phiid'
+'phpvsf','seqnum,pvsfpct,pvsfdistinct,pvsfcont,pvsfkind,pvsflocation,phiidref,phpvsfiid','phiid'
+'phpores','seqnum,poreqty,poreqtyclass,poresize,porecont,poreshp,phiidref,phporesiid','phiid'
+'phrdxfeatures','seqnum,rdxfeatpct,rdxfeatsize,rdxfeatcntrst,rdxfeathardness,rdxfeatshape,rdxfeatkind,rdxfeatlocation,rdxfeatboundary,phiidref,phrdxfiid','phiid'
+'phroots','seqnum,rootsquantity,rootsquantityclass,rootssize,rootslocation,phiidref,phrootsiid','phiid'
+'phsample','seqnum,labsampnum,fldsampid,phiidref,phlabsampiid','phiid'
+'phstructure','seqnum,structgrade,structsize,structtype,structid,structpartsto,phiidref,phstructureiid','phiid'
+'phtext','seqnum,recdate,recauthor,phorizontextkind,textcat,textsubcat,textentry,phiidref,phtextiid','phiid'
+'phtexture','seqnum,texcl,lieutex,phiidref,phtiid','phiid'
+'phdb','seqnum,bddepthtop,bddepthbottom,bdmethod,datacollector,samplevolfieldmoist,totalsamplewtfm,totalsamplewtairdry,coarsefragwtfm,coarsefragwtairdry,coarsefragdensity,coarsefragvolmeasured,subsamplewtairdry,subsamplewtod,obsgrsoimoist,obsgrsoimoist_s,obsgravsoilmoistfe,obsgravsoilmoistfe_s,bdovendrywhole,bdovendrywhole_s,bdovendryfineearth,bdovendryfineearth_s,bdsatiated,phiidref,phbulkdensityiid','phiid'
+'phdbcompliantcavity','bulkdensitycavitydiameter,bulkdensitycavitylength,bulkdensitycavitywidth,bulkdensitycavityinitvol,bulkdensitycavityinitvol_s,bulkdensitycavityfinalvol,bulkdensitycavityfinalvol_s,cavityavedepthpredig,cavityavedepthpostdig,bulkdensitycavitysampvol,bulkdensitycavitysampvol_s,phbulkdensityiidref,phbulkdencavityiid','phbulkdensityiid'
+'phdbcore','sampletubelength,unfilledtubelength,unfilledtubelength_s,coresamplelength,coresamplelength_s,sampletubediameter,coresamplevolume,coresamplevolume_s,phbulkdensityiidref,phbulkdencoreiid','phbulkdensityiid'
+'phdbscoop','scooplength,scoopwidth,scoopdepthtotal,scoopvolume,scoopvolume_s,scoopdepthunfilled,scoopdepthunfilled_s,scoopsamplevolume,scoopsamplevolume_s,phbulkdensityiidref,phbulkdenscoopiid','phbulkdensityiid'
+'phdbcorereading','bulkdensitytubeheadspace,phbulkdencoreiidref,phbdcorereadingiid','phbulkdencoreiid'
+'phdbscoopreading','scoopheadspace,phbulkdenscoopiidref,phbulkdenscoopreadiid','phbulkdenscoopiid'
+'phconccolor','seqnum,colorpct,colorhue,colorvalue,colorchroma,colormoistst,phconceniidref,phconcencoloriid','phconceniid'
+'phfeatcolor','seqnum,colorpct,colorhue,colorvalue,colorchroma,colormoistst,phfeatsiidref,phfeatcoloriid','phfeatsiid'
+'phpvsfcolor','seqnum,colorpct,colorhue,colorvalue,colorchroma,colormoistst,phpvsfiidref,phpvsfcoloriid','phpvsfiid'
+'phredoxfcolor','seqnum,colorpct,colorhue,colorvalue,colorchroma,colormoistst,phrdxfiidref,phrdxfcoloriid','phrdxfiid'
+'phtexturemod','seqnum,texmod,phtiidref,phtexmodiid','phtiid'
\ No newline at end of file
diff --git a/misc/run-all-NASIS-get-methods.R b/misc/run-all-NASIS-get-methods.R
new file mode 100644
index 00000000..9ae44a9e
--- /dev/null
+++ b/misc/run-all-NASIS-get-methods.R
@@ -0,0 +1,120 @@
+## validation of NASIS-functionality for nasisDBI PR
+## git shell command to list files changed in the PR
+
+# git diff --name-only master...nasisDBI | grep -E get\\|NASIS\\|fetch
+
+f <- read.table(text = "R/fetchNASIS.R
+                        R/fetchNASIS_pedons.R
+                        R/fetchNASIS_components.R
+                        R/fetchVegdata.R
+                        R/getHzErrorsNASIS.R
+                        R/get_RMF_from_NASIS_db.R
+                        R/get_colors_from_NASIS_db.R
+                        R/get_component_data_from_NASIS_db.R
+                        R/get_concentrations_from_NASIS_db.R
+                        R/get_cosoilmoist_from_NASIS.R
+                        R/get_extended_data_from_NASIS_db.R
+                        R/get_hz_data_from_NASIS_db.R
+                        R/get_lablayer_data_from_NASIS_db.R
+                        R/get_labpedon_data_from_NASIS_db.R
+                        R/get_phfmp_from_NASIS_db.R
+                        R/get_phlabresults_data_from_NASIS_db.R
+                        R/get_projectmapunit_from_NASIS.R
+                        R/get_site_data_from_NASIS_db.R
+                        R/get_text_notes_from_NASIS_db.R
+                        R/get_veg_data_from_NASIS_db.R
+                        R/get_vegplot_data_from_NASIS_db.R
+                        R/openNASISchannel.R")$V1
+                        # R/dbQueryNASIS.R
+                        # R/get_soilseries_from_NASIS.R
+                        # man/dbConnectNASIS.Rd
+                        # man/dbQueryNASIS.Rd
+                        # man/fetchNASIS.Rd
+                        # man/getHzErrorsNASIS.Rd
+                        # misc/man-deprecated/fetchNASIS.Rd
+                        #
+# # you want the version of soilDB you are testing to be installed
+# devtools::install()
+
+library(soilDB)
+
+# path to data source (NULL = use ODBC to local nasis,
+#                     otherwise path to SQLite)
+dsn <- NULL # "~/workspace/NASISlite/nasis_local.db" # "misc/testStatic.sqlite"
+
+# Function to load all function names in package, run them using SS and dsn as specified
+test_local_NASIS <- function(SS = FALSE, dsn = NULL) {
+
+  # get package function names
+  fnames <- sapply(f, function(x) {
+    names(as.list(evalSource(x, package = "soilDB")))
+  })
+
+  # iterate over functions by name
+  test <- lapply(fnames, function(fname) {
+
+      lapply(fname, function(FUN) {
+
+        message("\n")
+        message(sprintf("Testing: %s", FUN))
+        message("\n")
+
+        # get function out of (installed) soilDB package environment
+        TESTFUN <- get(FUN, envir = as.environment("package:soilDB"))
+
+        # handle special cases -- all functions tested take an SS argument except local_NASIS_defined
+        switch (FUN,
+                "local_NASIS_defined" = try(TESTFUN(dsn = dsn)),
+                try(TESTFUN(SS = SS, dsn = dsn)) )
+      })
+    })
+
+  # which functions error? that is the function result -- in addition to whatever messages/out generated
+  unlist(lapply(names(test), function(x) lapply(seq_along(test[[x]]), function(y) {
+        res <- inherits(test[[x]][[y]], 'try-error')
+        names(res) <- fnames[[x]][y]
+        res
+      })))
+}
+
+# test with selected set
+nasis_ss <- test_local_NASIS(SS = TRUE, dsn = NULL)
+
+# list names of failed functions; if length 0 all good
+nasis_ss[which(nasis_ss)]
+
+# test against whole local database
+nasis_all <- test_local_NASIS(SS = FALSE, dsn = NULL)
+
+nasis_all[which(nasis_all)]
+
+# RUN IF NEEDED:
+dsn <- "misc/testStatic.sqlite"
+createStaticNASIS(dsn = NULL, SS = TRUE, output_path = dsn)
+
+# test with selected set in SQLite instance
+nasis_static_ss <- test_local_NASIS(SS = TRUE, dsn = dsn)
+
+nasis_static_ss[which(nasis_static_ss)]
+
+# test against whole local database in SQLite instance
+nasis_static_all <- test_local_NASIS(SS = FALSE, dsn = dsn)
+
+nasis_static_all[which(nasis_static_all)]
+
+save(nasis_ss, nasis_all, nasis_static_ss, nasis_static_all, 
+     file = "NASIS-table-results.rda")
+
+### prior fixes:
+
+# Fixed: Text fields must come at end of query per MSSQL specs
+# get_text_notes_from_NASIS_db()
+
+# Fixed: same as above
+# get_vegplot_location_from_NASIS_db()
+
+# Fixed:
+# get_vegplot_textnote_from_NASIS_db()
+
+# Relatively rare data  update soon with input from Jay
+# get_vegplot_transpecies_from_NASIS_db()
diff --git a/tests/testthat/test-DBI.R b/tests/testthat/test-DBI.R
new file mode 100644
index 00000000..8359097d
--- /dev/null
+++ b/tests/testthat/test-DBI.R
@@ -0,0 +1,42 @@
+context("test DBI conversion")
+
+test_that('tests for NA values (DBI/odbc replacement of RODBC)', {
+  
+  # test for conditions permitting this test to run
+  if (!local_NASIS_defined(dsn = NULL)) {
+    skip("local NASIS database not available")
+  }
+  
+  # hypothesis: this DBI NA mangling is related to ODBC 13 * nanodbc interaction
+  #             note: exercising this test requires site records without aspect populated in local db
+  if (sum(is.na(dbQueryNASIS(NASIS(), "SELECT aspect FROM site")$aspect)) > 0) {
+    # NA filled with (usually large) integer values--much greater than 360 implied by domain
+    expect_equal(sum(is.na(
+      dbQueryNASIS(NASIS(), "SELECT * FROM site")$aspect
+    )), 0)
+  } else {
+    skip("no missing aspect values in site table")
+  }
+  
+  rodbccontest <- RODBC::odbcDriverConnect(connection = getOption('soilDB.NASIS.credentials'))
+  
+  # this works as expected
+  # expect_equal(
+  #   RODBC::sqlQuery(
+  #     rodbccontest,
+  #     "SELECT aspect FROM site_View_1"
+  #   )$aspect,
+  #   RODBC::sqlQuery(
+  #     rodbccontest,
+  #     "SELECT * FROM site_View_1"
+  #   )$aspect
+  # )
+  
+  # built in "whole table" query is equivalent to RODBC
+  expect_equal(
+    soilDB:::.dump_NASIS_table("site_View_1")$aspect,
+    RODBC::sqlQuery(rodbccontest, "SELECT * FROM site_View_1")$aspect
+  )
+  
+  RODBC::odbcClose(rodbccontest)
+})
diff --git a/tests/testthat/test-dbQueryNASIS.R b/tests/testthat/test-dbQueryNASIS.R
new file mode 100644
index 00000000..f803ac4b
--- /dev/null
+++ b/tests/testthat/test-dbQueryNASIS.R
@@ -0,0 +1,32 @@
+dsn <- NULL
+
+test_that("dbQueryNASIS works", {
+  
+  if (!local_NASIS_defined(dsn = dsn)) {
+    skip("local NASIS database not available")
+  }
+  
+  # making a DBIConnection works
+  conn <- dbConnectNASIS()
+  expect_true(inherits(conn, 'DBIConnection'))
+  
+  # a single query works
+  res <- dbQueryNASIS(conn, "SELECT geomftname, geomftiid FROM geomorfeattype", close = FALSE)
+  
+  expect_true(inherits(res, 'data.frame') && 
+                all(c("geomftname", "geomftiid") %in% colnames(res)) &&
+                res$geomftname[1] == "Landform")
+  
+  # a double query works (returns a named list)
+  res2 <- dbQueryNASIS(conn, q = c(geomorfeat = "SELECT geomfname, geomfiid, geomftiidref FROM geomorfeat",
+                                   geomorfeattype = "SELECT geomftname, geomftiid FROM geomorfeattype"))
+  
+  expect_true(is.list(res2) && 
+                all(c("geomorfeat", "geomorfeattype") %in% names(res2)))
+  
+  # results for geomorfeattype should be identical
+  expect_equal(res, res2[["geomorfeattype"]])
+  
+  # connection is closed from close=TRUE from end of res2 call
+  expect_error(dbQueryNASIS(conn, "SELECT geomftname, geomftiid FROM geomorfeattype"))
+})
diff --git a/tests/testthat/test-fetchNASIS.R b/tests/testthat/test-fetchNASIS.R
index 8c0dd21b..9d443163 100644
--- a/tests/testthat/test-fetchNASIS.R
+++ b/tests/testthat/test-fetchNASIS.R
@@ -1,5 +1,8 @@
 context("fetchNASIS() -- requires local NASIS and ODBC connection")
 
+# TODO: develop minimal test set for NASIS data, stored as static SQLite DB
+dsn <- NULL
+
 ## helper functions used to skip tests that rely on special conditions
 # http://r-pkgs.had.co.nz/tests.html
 #
@@ -7,36 +10,36 @@ context("fetchNASIS() -- requires local NASIS and ODBC connection")
 # * pedons / component missing from local database
 
 
-check_local_NASIS_pedons_available <- function() {
+check_local_NASIS_pedons_available <- function(dsn = NULL) {
 
   # attempt to load pedons
   # these functions will return empty data.frame objects when there are no data in the SS
-  res1 <- try(suppressWarnings(get_site_data_from_NASIS_db()), silent = TRUE)
-  res2 <- try(suppressWarnings(get_hz_data_from_NASIS_db()), silent = TRUE)
+  res1 <- try(suppressWarnings(get_site_data_from_NASIS_db(dsn = dsn)), silent = TRUE)
+  res2 <- try(suppressWarnings(get_hz_data_from_NASIS_db(dsn = dsn)), silent = TRUE)
 
-  if(nrow(res1) == 0) {
+  if (nrow(res1) == 0) {
     skip("no Site/Pedon records in local NASIS database")
   }
-  if(nrow(res2) == 0) {
+  if (nrow(res2) == 0) {
     skip("no Pedon Horizon records in local NASIS database")
   }
 }
 
-check_local_NASIS_components_available <- function() {
+check_local_NASIS_components_available <- function(dsn = NULL) {
 
   # attempt to load components
   # these functions will return empty data.frame objects when there are no data in the SS
-  res1 <- try(suppressWarnings(get_component_data_from_NASIS_db()), silent = TRUE)
-  res2 <- try(suppressWarnings(get_component_horizon_data_from_NASIS_db()), silent = TRUE)
+  res1 <- try(suppressWarnings(get_component_data_from_NASIS_db(dsn = dsn)), silent = TRUE)
+  res2 <- try(suppressWarnings(get_component_horizon_data_from_NASIS_db(dsn = dsn)), silent = TRUE)
 
   # res <- try(suppressWarnings(fetchNASIS(from='pedons')), silent = TRUE)
   # note: this was too broad of a test -- any error in fetchNASIS will result in skipping the test!
   #if(class(res) == 'try-error'){
-  if(nrow(res1) == 0) {
+  if (nrow(res1) == 0) {
     skip("no Component records in local NASIS database")
   }
 
-  if(nrow(res2) == 0) {
+  if (nrow(res2) == 0) {
     skip("no Component Horizon records in local NASIS database")
   }
 }
@@ -47,17 +50,17 @@ check_local_NASIS_components_available <- function() {
 test_that("fetchNASIS(from='pedons') returns reasonable data", {
 
   # test for conditions permitting this test to run
-  if(!local_NASIS_defined()) {
+  if (!local_NASIS_defined(dsn = dsn)) {
     skip("local NASIS database not available")
   }
 
   # pedons must be present for tests
-  check_local_NASIS_pedons_available()
+  check_local_NASIS_pedons_available(dsn = dsn)
 
   # get data
   # ignore warnings for now
-  x <- suppressWarnings(fetchNASIS(from='pedons'))
-
+  x <- suppressWarnings(fetchNASIS(from = 'pedons'))
+  
   # expected outcomes
   expect_true(inherits(x, 'SoilProfileCollection'))
   expect_equal(nrow(site(x)) > 0, TRUE)
@@ -75,21 +78,21 @@ test_that("fetchNASIS(from='pedons') returns reasonable data", {
 test_that("fetchNASIS(from='pedons') nullFragsAreZero works as expected", {
 
   # test for conditions permitting this test to run
-  if(!local_NASIS_defined()) {
+  if (!local_NASIS_defined(dsn = dsn)) {
     skip("local NASIS database not available")
   }
 
   # components must be present for tests
-  check_local_NASIS_pedons_available()
+  check_local_NASIS_pedons_available(dsn = dsn)
 
   # get data
   # ignore warnings for now
-  x <- suppressWarnings(fetchNASIS(from='pedons'))
-  y <- suppressWarnings(fetchNASIS(from='pedons', nullFragsAreZero=FALSE))
+  x <- suppressWarnings(fetchNASIS(from = 'pedons'))
+  y <- suppressWarnings(fetchNASIS(from = 'pedons', nullFragsAreZero = FALSE))
 
   # no NA in total fragments using default arguments
-  expect_true(all(horizons(x)[is.na(y$total_frags_pct),'total_frags_pct'] ==0))
-  expect_true(all(horizons(x)[is.na(y$total_art_pct),'total_art_pct'] ==0))
+  expect_true(all(horizons(x)[is.na(y$total_frags_pct),'total_frags_pct'] == 0))
+  expect_true(all(horizons(x)[is.na(y$total_art_pct),'total_art_pct'] == 0))
 })
 
 test_that("fetchNASIS(from='components') returns reasonable data", {
@@ -101,12 +104,12 @@ test_that("fetchNASIS(from='components') returns reasonable data", {
   }
 
   # must have components to complete test
-  check_local_NASIS_components_available()
+  check_local_NASIS_components_available(dsn = dsn)
 
   # get data
   # ignore warnings for now
-  x <- suppressWarnings(fetchNASIS(from='components'))
-
+  x <- suppressWarnings(fetchNASIS(from = 'components'))
+ 
   # expected outcomes
   expect_true(inherits(x, 'SoilProfileCollection'))
   expect_equal(nrow(site(x)) > 0, TRUE)
@@ -116,4 +119,20 @@ test_that("fetchNASIS(from='components') returns reasonable data", {
 
 })
 
+test_that("get_text_notes_from_NASIS_db works", {
+  if (!local_NASIS_defined(dsn = dsn)) {
+    skip("local NASIS database not available")
+  }
+  expect_silent({get_text_notes_from_NASIS_db()})
+})
+
+test_that("getHzErrorsNASIS works", { 
+  if (!local_NASIS_defined(dsn = dsn)) {
+    skip("local NASIS database not available")
+  }
+  expect_silent({suppressMessages(getHzErrorsNASIS(static_path = static_path))})
+})
+
+
+