Soil Taxonomy is a theoretical framework for organizing soil information, typically at scales coarser than most land management decisions. Soil series are a practical (but limited in geographic scope) framework for organizing soil/landscape combinations close to the scale of land management decisions.
So, what is the practical manifestation of a single subgroup from Soil Taxonomy (abruptic durixeralfs), in terms of soil series concepts?
The following code, links, and notes may serve as a starting point for those interested in learning more about Soil Taxonomy. This is a work in progress.
# AQP suite, be sure to use the development versions from GitHub
library(aqp)
library(soilDB)
library(sharpshootR)
# used for visualization
library(latticeExtra)
library(tactile)
library(cluster)
library(ape)
library(FactoMineR)
# mapping a subgroup
library(sf)
library(terra)
library(spData)
library(rasterVis)
library(viridisLite)
# subgroup "explainer"
library(SoilTaxonomy)
library(data.tree)
Get a snapshot of the Soil Classification database. This contains the current classification for all soil series.
# latest version, synced quarterly
u <- 'https://github.com/ncss-tech/SoilWeb-data/raw/main/files/SC-database.csv.gz'
tf <- tempfile()
download.file(u, destfile = tf)
SC <- read.csv(tf, stringsAsFactors = FALSE)
Identify all of the soil series associated with a specific subgroup
taxa: abruptic durixeralfs and download basic morphology and
climate summaries via fetchOSD
.
# select series names for a single subgroup
SC <- SC[which(SC$taxsubgrp == 'abruptic durixeralfs'), ]
s <- SC$soilseriesname
# get OSD morphology and extended summaries
osd <- fetchOSD(soils = s, extended = TRUE)
Explain subgroup taxa via SoilTaxonomy package.
cat(explainST('abruptic durixeralfs'))
abruptic durixeralfs | | | | abrupt textural change | | | presence of a duripan | | xeric soil moisture regime | soils with an argillic, kandic, or natric horizon
Automatic to the subgroup, via SoilTaxonomy package.
taxonTree(c('durixeralfs', 'rhodoxeralfs'), special.chars = c("\u251c","\u2502", "\u2514", "\u2500 "))
Soil Taxonomy └─ alfisols └─ xeralfs ├─ durixeralfs │ ├─ natric durixeralfs │ ├─ vertic durixeralfs │ ├─ aquic durixeralfs │ ├─ abruptic haplic durixeralfs │ ├─ abruptic durixeralfs │ ├─ haplic durixeralfs │ └─ typic durixeralfs └─ rhodoxeralfs ├─ lithic rhodoxeralfs ├─ vertic rhodoxeralfs ├─ petrocalcic rhodoxeralfs ├─ calcic rhodoxeralfs ├─ inceptic rhodoxeralfs └─ typic rhodoxeralfs
We have to do this manually.
# subset SC database to required columns
SC <- SC[, c('soilseriesname', 'taxorder', 'taxsuborder', 'taxgrtgroup', 'taxsubgrp', 'taxclname')]
# normalization via lower case
SC$taxclname <- tolower(SC$taxclname)
SC$soilseriesname <- tolower(SC$soilseriesname)
# remove subgroup component of family spec
SC$f <- NA_character_
for(i in 1:nrow(SC)) {
SC$f[i] <- gsub(pattern = SC$taxsubgrp[i], replacement = '', SC$taxclname[i], fixed = TRUE)
}
# remove white space
SC$f <- trimws(SC$f, which = 'both')
# required columns only, smaller data.tree
v <- c('taxorder', 'taxsuborder', 'taxgrtgroup', 'taxsubgrp', 'f', 'soilseriesname', 'path')
# init data.tree object
SC$path <- sprintf("ST/%s/%s/%s/%s/%s/%s", SC$taxorder, SC$taxsuborder, SC$taxgrtgroup, SC$taxsubgrp, SC$f, SC$soilseriesname)
n <- as.Node(SC[, v], pathName = 'path')
## missing family / series result in an ugly tree, prune accordingly
# prune missing family / series
pf <- function(i) {
# NA due to left join
# note odd approach required, matching to 'NA' vs. is.na()
if(GetAttribute(i, 'name') == 'NA') {
return(FALSE)
} else {
return(TRUE)
}
}
## print
# print(n, limit = NULL, pruneFun = pf)
levelName 1 ST 2 °--alfisols 3 °--xeralfs 4 °--durixeralfs 5 °--abruptic durixeralfs 6 ¦--fine-loamy, mixed, superactive, thermic 7 ¦ °--bellota 8 ¦--ashy, glassy, frigid 9 ¦ °--borealis 10 ¦--fine, kaolinitic, thermic 11 ¦ °--chesterton 12 ¦--clayey-skeletal, kaolinitic, thermic 13 ¦ °--clough 14 ¦--very-fine, smectitic, frigid 15 ¦ °--deunah 16 ¦--fine, mixed, active, thermic 17 ¦ ¦--eastbiggs 18 ¦ ¦--redding 19 ¦ ¦--san joaquin 20 ¦ °--yuvas 21 ¦--fine, smectitic, mesic 22 ¦ °--fairylawn 23 ¦--clayey, smectitic, frigid, shallow 24 ¦ ¦--furshur 25 ¦ ¦--nicholflat 26 ¦ °--ponina 27 ¦--fine, illitic, thermic 28 ¦ °--gloria 29 ¦--clayey, mixed, active, thermic, shallow 30 ¦ ¦--keyes 31 ¦ °--vistarobles 32 ¦--fine, smectitic, thermic 33 ¦ °--madera 34 ¦--fine, vermiculitic, thermic 35 ¦ °--moda 36 ¦--fine, smectitic, frigid 37 ¦ °--thacker 38 °--fine-loamy, mixed, active, thermic 39 °--thermalito
Link to SoilWeb subgroup taxa tree for abruptic durixeralfs.
# get 800m extent map
e <- taxaExtent('abruptic durixeralfs', level = 'subgroup')
# aggregate via focal mean using 5x5 moving window
# visualization purposes only
a <- terra::aggregate(e, fact = 5, fun = mean, na.rm = TRUE)
data("us_states")
us_states <- st_transform(us_states, 5070)
us_states <- st_crop(us_states, e)
# simple figure
plot(a, axes = FALSE, col = mako(50), mar = c(3, 1, 3, 3))
plot(st_geometry(us_states), add = TRUE)
title('Abruptic Durixeralfs')
mtext('pixel values represent percent of area\nwithin 800m grid cells', side = 1, line = 1.5)
Cluster series based on annual climate summaries but don’t plot it yet.
# control centers symbol and size here
res <- vizAnnualClimate(osd$climate.annual, s = 'SAN JOAQUIN', IQR.cex = 1.1, cex=1.1, pch=18)
Series associated with abruptic durixeralfs subgroup, arranged according to annual climate summaries.
par(mar = c(0,0,1,2))
plotProfileDendrogram(osd$SPC, clust = res$clust, scaling.factor = 0.075, width = 0.25, y.offset = 0.5, name.style = 'center-center', shrink = TRUE, max.depth = 150, depth.axis = list(cex = 0.8))
mtext('Abruptic Durixeralfs', side = 1, at = 0.5, adj = 0, line = -1.5, font=4)
mtext('sorted by annual climate summaries', side = 3, at = 0.5, adj = 0, line = -1.5, font = 1, cex = 1.1)
# display annual climate summary
trellis.par.set(plot.line=list(col='RoyalBlue'))
print(update(res$fig, layout=c(4,2), sub='Abruptic Durixeralfs'))
hs <- vizHillslopePosition(osd$hillpos)
print(hs$fig)
gc <- vizGeomorphicComponent(osd$geomcomp)
print(gc$fig)
tr <- vizTerracePosition(osd$terrace)
print(tr$fig)
hs.tab <- (osd$hillpos[, 2:6])
row.names(hs.tab) <- osd$hillpos$series
gc.tab <- (osd$geomcomp[, 2:7])
row.names(gc.tab) <- osd$geomcomp$series
hs.ca <- CA(hs.tab, graph = FALSE)
gc.ca <- CA(gc.tab, graph = FALSE)
plot(hs.ca, autoLab='yes', title='Hillslope Position', cex=0.75, col.col='firebrick', col.row='royalblue')
plot(gc.ca, autoLab='yes', title='Geomorphic Component', cex=0.75, col.col='firebrick', col.row='royalblue')
# latest LDM snapshot, no soil morphologic data
lab <- fetchLDM(
WHERE = "CASE WHEN corr_taxsubgrp IS NOT NULL THEN LOWER(corr_taxsubgrp) ELSE LOWER(samp_taxsubgrp) END = 'abruptic durixeralfs' "
)
# remove any profiles with horizon depth logic
lab <- HzDepthLogicSubset(lab)
length(lab)
# truncate profiles at 200cm
lab <- trunc(lab, 0, 200)
# # sanity check
# par(mar = c(0, 0, 3, 2))
# plotSPC(lab[1:15, ], print.id = FALSE, name = NA, width = 0.33, color = 'clay_total')
# weighted-mean particle diameter
lab$wmpd <- with(
horizons(lab), ((sand_very_coarse * 1.5) + (sand_coarse * 0.75) + (sand_medium * 0.375) + (sand_fine * 0.175) + (sand_very_fine *0.075) + (silt_total * 0.026) + (clay_total * 0.0015)) / (sand_very_coarse + sand_coarse + sand_medium + sand_fine + sand_very_fine + silt_total + clay_total))
# estimate soil depth based on horizon designations
sdc <- getSoilDepthClass(lab)
# splice-into site data
site(lab) <- sdc
site(lab)$.grp <- factor('Abruptic Durixeralfs')
a <- slab(lab, .grp ~ clay_total + wmpd + cole_whole_soil + ph_h2o + base_sat_sum_of_cations_ph_8_2)
# re-name soil properties for clarity
a$variable <- factor(
a$variable,
labels = c('Total Clay (%)', 'WMPD', 'COLE WS (%)', 'pH 1:1 H2O', 'Base Saturation pH 8.2 (%)')
)
# define plotting style
tps <- tactile.theme(superpose.line=list(col=c('RoyalBlue', 'DarkRed', 'DarkGreen'), lwd=2))
# plot grouped, aggregate data
xyplot(top ~ p.q50 | variable,
groups = .grp,
data = a,
ylab='Depth',
xlab='median bounded by 5th and 95th percentiles',
main = 'Abruptic Durixeralfs',
lower=a$p.q5, upper=a$p.q95, ylim=c(155,-5),
panel=panel.depth_function, alpha=0.25, sync.colors=TRUE,
prepanel=prepanel.depth_function,
cf=a$contributing_fraction,
par.strip.text=list(cex=0.8),
strip=strip.custom(bg=grey(0.85)),
layout=c(5,1),
scales=list(x=list(alternating=1, relation='free'), y=list(alternating=3)),
par.settings=tps
)
by generalized horizon labels, depth intervals, ???.
hasDarkColors(lab.spc, dhuenm = 'd_hue', dvalnm = 'd_value', dchrnm = 'd_chroma', mhuenm = 'm_hue', mvalnm = 'm_value', mchrnm = 'm_chroma')
profileApply(lab.spc, getArgillicBounds, clay.attr = 'clay', texcl.attr = 'lab_texture_class', simplify = FALSE)
kable_styling(kable(osd$soilweb.metadata, format = 'html', caption = 'SoilWeb Snapshot Metadata'), full_width = FALSE, font_size = 10)
product | last_update |
---|---|
block diagram archive | 2019-12-17 |
cached sketches | 2023-10-12 |
component pedons | 2023-10-09 |
ESID-series cross-tabulation | 2023-11-03 |
ESID/series cross-tabulation | 2023-11-06 |
KSSL snapshot | 2020-03-18 |
MLRA membership | 2023-10-06 |
national musym LUT | 2023-10-04 |
OSD fulltext | 2023-10-06 |
OSD morphology | 2023-10-06 |
SC database | 2023-10-02 |
SDE cached figures | 2023-10-12 |
series climate summary | 2023-10-09 |
series-ESID cross-tabulation | 2023-11-03 |
series/ESID cross-tabulation | 2023-11-06 |
series extent | 2023-10-04 |
series monthly water balance | 2023-10-12 |
SeriesTree | 2023-10-06 |
SSA vintage | 2023-10-04 |
SSURGO geomorphology | 2023-10-04 |
SSURGO NCCPI Stats | 2022-10-11 |
SSURGO parent material | 2023-10-04 |
SSURGO SOC Stocks | 2023-10-04 |
taxa extent | 2022-10-20 |
This document is based on aqp
version 2.0.3 and
soilDB
version 2.8.1.