A web coverage service (WCS) is provided for the gSSURGO and gNATGSO map unit key grids. These grids represent a rasterizaion of the gSSURGO and gNATSGO map unit keys for the conterminous United States at a resolution of 30m, referenced to an Albers Equal Area Conic (NAD83) coordinate reference system (EPSG:5070). Grids are LZW compressed and internally tiled for efficient random access. Cell values are map unit keys, encoded as unsigned 32-bit integers. The grid topology and cell values are identical to the rasters contained within the gSSURGO and gNATSGO file geo-databases (FGDB). The GeoTiff format is used to ensure maximum compatibility. Files are re-created as part of the annual SSURGO refresh cycle.
An experimental WCS is provided for raster soil survey (RSS) products at 10m resolution.
An experimental WCS is provided for STATSGO2 (2016) at 300m resolution.
An experimental WCS is provided for SSURGO in HI (EPSG:6628) and PR (EPSG:32161), at 30m resolution.
A similar WCS is provided for the ISSR-800 soil property grids.
See the mukey.wcs
and ISSR800.wcs
manual pages for details.
Basic usage.
# select gSSURGO grid, 30m resolution
x <- mukey.wcs(aoi = aoi, db = 'gssurgo', ...)
# select gNATSGO grid, 30m resolution
x <- mukey.wcs(aoi = aoi, db = 'gnatsgo', ...)
# select RSS grid, 10m resolution
x <- mukey.wcs(aoi = aoi, db = 'RSS', ...)
# select STATSGO2 grid, 300m resolution
x <- mukey.wcs(aoi = aoi, db = 'statsgo', ...)
# select various ISSR-800 grids, details below
x <- ISSR800.wcs(aoi = aoi, var = 'paws')
Excerpt from the gSSURGO documentation.
The gSSURGO Database is derived from the official Soil Survey Geographic (SSURGO) Database. SSURGO generally has the most detailed level of soil geographic data developed by the National Cooperative Soil Survey (NCSS) in accordance with NCSS mapping standards. The tabular data represent the soil attributes and are derived from properties and characteristics stored in the National Soil Information System (NASIS). The gSSURGO data were prepared by merging the traditional vector-based SSURGO digital map data and tabular data into statewide extents, adding a statewide gridded map layer derived from the vector layer, and adding a new value-added look up table (Valu1) containing “ready to map” attributes. The gridded map layer is a file geodatabase raster in an ArcGIS file geodatabase. The raster and vector map data have a statewide extent. The raster map data have a 10-meter cell size that approximates the vector polygons in an Albers Equal Area projection. Each cell (and polygon) is linked to a map unit identifier called the map unit key. A unique map unit key is used to link the raster cells and polygons to attribute tables. Due to file size, the raster layer for the conterminous United States is only available in a 30-meter resolution.
Excerpt from the gNATSGO documentation.
The gNATSGO databases contain a raster of the soil map units and 70 related tables of soil properties and interpretations. They are designed to work with the SPSD gSSURGO ArcTools. Users can create full coverage thematic maps and grids of soil properties and interpretations for large geographic areas, such as the extent of a State or the conterminous United States. Please note that the State-wide geodatabases contain a 10 meter raster and the CONUS database contains a 30 meter raster.
The gNATSGO database is composed primarily of SSURGO data, but STATSGO2 data was used to fill in the gaps. The RSSs are newer product with relatively limited spatial extent. These RSSs were merged into the gNATSGO after combining the SSURGO and STATSGO2 data. The extent of RSS is expected to increase in the coming years.
Excerpt from the RSS documentation.
Raster Soil Survey is a reference to the products of soil survey work completed using digital soil mapping methodologies. Digital soil mapping is the production of georeferenced soil databases based on the quantitative relationships between soil measurements made in the field or laboratory and environmental data and may be represented as either discrete classes or continuous soil properties. Both digital and traditional soil mapping use a conceptual soil-landscape model as a means for organizing environmental information into discrete divisions. The primary difference between these two approaches is that digital methods exploit quantitative relationships of the environmental information, while traditional methods utilize a more subjective approach and the approximate relationships of the environmental information to spatially represent where the divisions are represented.
An experimental, 300m gridded representation of STATSGO 2 is provided by the SoilWeb web coverage service. This is not an official USDA-NRCS product.
Excerpt from STATSGO2 Documentation.
The Digital General Soil Map of the United States or STATSGO2 is a broad-based inventory of soils and non-soil areas that occur in a repeatable pattern on the landscape and that can be cartographically shown at the scale mapped of 1:250,000 in the continental U.S., Hawaii, Puerto Rico, and the Virgin Islands and 1:1,000,000 in Alaska. The level of mapping is designed for broad planning and management uses covering state, regional, and multi-state areas. The U.S. General Soil Map is comprised of general soil association units and is maintained and distributed as a spatial and tabular dataset.
Thematic mapping or analysis of soil information requires connecting
the grids to our tabular data sources, either using local files or Soil
Data Access (SDA) web-service. The soilDB
package provides
many convenient interfaces to SDA. Note that SDA does not yet contain
tabular data for the raster soil surveys.
soilDB
,
which has recently
switched over to sf
and terra
packages.The WCS interface functions mukey.wcs
and
ISSR800.wcs
can automatically generate a bounding-box
(BBOX) from most spatial data formats. However, sometimes it is more
convenient to manually specify a BBOX created from a website or single
point specified in WGS84 coordinates. SoilWeb
provides two convenient keyboard shortcuts, available after clicking on
the map interface:
‘b’ copy bounding-box, returned as: -118.6609 36.4820,-118.6609 36.5972,-118.3979 36.5972,-118.3979 36.4820,-118.6609 36.4820
‘p’ copy link to center coordinate, returned as:
https://casoilresource.lawr.ucdavis.edu/gmap/?loc=36.53964,-118.52943,z13
Right-clicking anywhere in the map interface will also generate a link to those coordinates and zoom-level.
A SoilWeb-style BBOX can be directly converted into an
sf
object:
library(sf)
library(soilDB)
library(terra)
# paste your BBOX text here
bb <- '-118.6609 36.4820,-118.6609 36.5972,-118.3979 36.5972,-118.3979 36.4820,-118.6609 36.4820'
# convert text -> WKT -> sf
wkt <- sprintf('POLYGON((%s))', bb)
x <- st_as_sfc(wkt)
# set coordinate reference system as GCS/WGS84
st_crs(x) <- 4326
# query WCS
mu <- mukey.wcs(x, db = 'gSSURGO')
# looks OK
plot(mu, legend = FALSE, axes = FALSE, main = metags(mu, name = 'description'))
# add original BBOX, after transforming to mukey grid CRS
plot(st_transform(x, 5070), add = TRUE)
terra::metags()
function.
Currently, a short description and “vintage” are included. The gNATSGO,
gSSURGO, and derivatives are typically created 2-3 months after the
annual SSURGO refresh on October 1st of each year (beginning of US
federal fiscal year).
description | vintage |
---|---|
gSSURGO map unit keys | FY2024 |
A buffer applied to a single WGS84 coordinate can create a BBOX:
library(sf)
library(soilDB)
# example point, WGS84 coordinates
p <- st_as_sf(
data.frame(
x = -118.55639,
y = 36.52578
),
coords = c('x', 'y'),
crs = 4326
)
# 1000m buffer applied to WGS84 coordinate
# radius defined in meters
b <- st_buffer(p, dist = units::set_units(1000, m))
b <- st_as_sf(b)
# query WCS
# result is in EPSG:5070
mu <- mukey.wcs(b, db = 'gSSURGO')
# looks OK
plot(mu, legend = FALSE, axes = FALSE, main = metags(mu, name = 'description'))
# add buffer, after transforming to mukey grid CRS
plot(st_transform(b, 5070), add = TRUE)
# add original point, after transforming to mukey grid CRS
plot(st_transform(p, 5070), add = TRUE, pch = 16)
Need the latest version of soilDB
and
terra
, along with all packages specified below (CRAN
versions fine).
install.packages('soilDB')
remotes::install_github("ncss-tech/soilDB", dependencies = FALSE, upgrade = FALSE, build = FALSE)
# terra
install.packages('terra', repos = 'https://rspatial.r-universe.dev')
Load required packages.
# latest from GitHub
# remotes::install_github("ncss-tech/soilDB", dependencies = FALSE)
library(soilDB)
# wrangling polygons and CRS transformations
library(sf)
# raster data / analysis
# need latest: install.packages('terra', repos='https://rspatial.r-universe.dev')
library(terra)
# raster visualization
library(rasterVis)
# figures
library(lattice)
library(tactile)
# soil classification
library(aqp)
# color palettes and manipulation
library(RColorBrewer)
library(colorspace)
Use the mukey.wcs()
function to access chunks of the
CONUS map unit key grids, based on some representation of an area of
interest (AOI). The AOI can be defined manually, as below, or
automatically extracted from sf
, sfc
,
bbox
, SpatRaster
, or SpatVector
objects. Backwards-compatibility with Spatial*
and
RasterLayer
objects is also provided. The resulting grid of
integers isn’t all that useful by itself; join data from Soil Data
Access (SDA) or local files (by map unit key) to create thematic maps.
Note: the gSSURGO and gNATSGO grids are updated annually, and typically
in-sync with the live version of the data hosted by SDA (updated Oct
1st) by early November. Map unit keys can change over time, especially
in soil survey areas that were updated during the last fiscal year.
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a <- st_bbox(
c(xmin = -114.16, xmax = -114.08, ymin = 47.65, ymax = 47.68),
crs = st_crs(4326)
)
# fetch gSSURGO map unit keys at native resolution (30m)
mu <- mukey.wcs(aoi = a, db = 'gssurgo')
# check:
print(mu)
## class : SpatRaster
## dimensions : 147, 219, 1 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : -1365495, -1358925, 2869245, 2873655 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b28190611e1
## categories : mukey
## name : mukey
## min value : 144983
## max value : 1716001
# looks good
plot(
mu,
main = 'gSSURGO map unit keys',
sub = 'Albers Equal Area Projection',
axes = FALSE,
legend = FALSE,
col = hcl.colors(100)
)
It is possible to retrieve small areas of vector geometry (SSURGO
polygons) from SDA with the SDA_spatialQuery()
function.
These data are stored and delivered in a geographic coordinate system
(WGS84). Overlaying the SSURGO polygons and map unit key grids will
require a simple transformation.
# get intersecting SSURGO polygons from SDA
# result is a SpatVector object
# GCS WGS84
p <- SDA_spatialQuery(mu, what = 'mupolygon', geomIntersection = TRUE)
# transform to AEA coordinate reference system used by gSSURGO / gNATSGO
# this is EPSG:5070
p <- project(p, crs(mu))
par(mar = c(1, 0, 2, 0))
plot(p, main = 'SSURGO Polygons (SDA)', axes = FALSE)
mtext('Albers Equal Area Projection', side = 1, line = -0.5)
Overlay SSURGO polygons and 30m map unit key grid.
par(mar = c(1, 0, 2, 0))
plot(mu, main = 'gSSURGO Grid (WCS)\nSSURGO Polygons (SDA)', col = hcl.colors(50), axes = FALSE, legend = FALSE)
plot(p, add = TRUE, border = 'white')
mtext('CONUS Albers Equal Area Projection (EPSG:5070)', side = 1, line = 1)
Requesting map unit key grids at a resolution other than 30m is possible, but only suitable for a quick “preview” of the data. For example, it is possible to get a much larger chunk of data by requesting grids at 800m. However, pixels are selected by nearest-neighbor and not generalized to the coarser scale. In short: don’t use the resulting data for anything other than a simple preview.
# part of CA
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a.CA <- st_bbox(
c(xmin = -121, xmax = -120, ymin = 37, ymax = 38),
crs = st_crs(4326)
)
# fetch gSSURGO map unit keys at ~~ 800m
# nearest-neighbor resampling = this is a "preview"
# result is a SpatRaster object
x.800 <- mukey.wcs(aoi = a.CA, db = 'gssurgo', res = 800)
# OK
plot(
x.800,
main = 'A Preview of gSSURGO Map Unit Keys',
sub = 'Albers Equal Area Projection (800m)\nnearest-neighbor resampling',
axes = FALSE,
legend = FALSE,
col = hcl.colors(100),
)
Note: tabular data for RSS are not yet available via SDA.
# slightly larger than Coweeta RSS BBOX
# specified in EPSG:5070
a <- st_bbox(
c(xmin = 1129000, xmax = 1135000, ymin = 1403000, ymax = 1411000),
crs = st_crs(5070)
)
# convert to sf polygon for later use in figures
a <- st_as_sfc(a)
# gSSURGO grid: 30m resolution
(x <- mukey.wcs(a, db = 'gSSURGO', res = 30))
## class : SpatRaster
## dimensions : 267, 200, 1 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 1129005, 1135005, 1402995, 1411005 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b2855f94464
## categories : mukey
## name : mukey
## min value : 545800
## max value : 545887
# gNATSGO grid: 30m resolution
(y <- mukey.wcs(a, db = 'gNATSGO', res = 30))
## class : SpatRaster
## dimensions : 267, 200, 1 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 1129005, 1135005, 1402995, 1411005 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b286d6c75a2
## categories : mukey
## name : mukey
## min value : 545800
## max value : 3244759
# RSS grid: 10m resolution
(z <- mukey.wcs(a, db = 'RSS', res = 10))
## class : SpatRaster
## dimensions : 800, 600, 1 (nrow, ncol, nlyr)
## resolution : 10, 10.0125 (x, y)
## extent : 1129005, 1135005, 1402995, 1411005 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b285cf22aa0
## categories : mukey
## name : mukey
## min value : 3244721
## max value : 3244759
# graphical comparison
par(mfcol = c(1, 3))
# gSSURGO
plot(x, axes = FALSE, legend = FALSE, main = metags(x, name = 'description'))
# original BBOX
plot(a, add = TRUE)
# gNATSGO
plot(y, axes = FALSE, legend = FALSE, main = metags(y, name = 'description'))
# original BBOX
plot(a, add = TRUE)
# RSS
plot(z, axes = FALSE, legend = FALSE, main = metags(z, name = 'description'), ext = x)
# original BBOX
plot(a, add = TRUE)
Continuing from the example above.
(statsgo <- mukey.wcs(a, db = 'statsgo', res = 300))
## class : SpatRaster
## dimensions : 27, 20, 1 (nrow, ncol, nlyr)
## resolution : 300, 300 (x, y)
## extent : 1129005, 1135005, 1402995, 1411095 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b283903451
## categories : mukey
## name : mukey
## min value : 659074
## max value : 664845
# graphical comparison
par(mfcol = c(1, 2))
# gSSURGO
plot(x, axes = FALSE, legend = FALSE, main = metags(x, name = 'description'))
# STATSGO
plot(statsgo, axes = FALSE, legend = FALSE, main = metags(statsgo, name = 'description'))
An experimental, 30m SSURGO mukey WCS based on the EPSG:6628 coordinate reference system. The example bounding-box is centered on the southern coast of Kauai.
# paste your BBOX text here
bb <- '-159.7426 21.9059,-159.7426 22.0457,-159.4913 22.0457,-159.4913 21.9059,-159.7426 21.9059'
# convert text -> WKT -> sf
wkt <- sprintf('POLYGON((%s))', bb)
x <- st_as_sfc(wkt)
# set coordinate reference system as GCS/WGS84
st_crs(x) <- 4326
# query WCS
mu <- mukey.wcs(x, db = 'hi_ssurgo')
# make NA (the ocean) blue
plot(mu, legend = FALSE, axes = FALSE, main = metags(mu, name = 'description'), colNA = 'royalblue')
# # check mu names
# .is <- format_SQL_in_statement(cats(mu)[[1]]$mukey)
# .sql <- sprintf("SELECT mukey, muname FROM mapunit WHERE mukey IN %s", .is)
# knitr::kable(SDA_query(.sql))
An experimental, 30m SSURGO mukey WCS based on the EPSG:32161 coordinate reference system. The example bounding-box is centered on the eastern coast of Puerto Rico.
# paste your BBOX text here
bb <- '-65.7741 18.1711,-65.7741 18.3143,-65.5228 18.3143,-65.5228 18.1711,-65.7741 18.1711'
# convert text -> WKT -> sf
wkt <- sprintf('POLYGON((%s))', bb)
x <- st_as_sfc(wkt)
# set coordinate reference system as GCS/WGS84
st_crs(x) <- 4326
# query WCS
mu <- mukey.wcs(x, db = 'pr_ssurgo')
# make NA (the ocean) blue
plot(mu, legend = FALSE, axes = FALSE, main = metags(mu, name = 'description'), colNA = 'royalblue')
# # check mu names
# .is <- format_SQL_in_statement(cats(mu)[[1]]$mukey)
# .sql <- sprintf("SELECT mukey, muname FROM mapunit WHERE mukey IN %s", .is)
# knitr::kable(SDA_query(.sql))
The following example BBOX + resulting gSSURGO mukey grid will be used for the following examples
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a <- st_bbox(
c(xmin = -114.16, xmax = -114.08, ymin = 47.65, ymax = 47.68),
crs = st_crs(4326)
)
# convert bbox to sf geometry
a <- st_as_sfc(a)
# fetch gSSURGO map unit keys at native resolution (~30m)
mu <- mukey.wcs(aoi = a, db = 'gssurgo')
The Mapunit Aggregated Attribute table records a variety of soil attributes and interpretations that have been aggregated from the component level to a single value at the map unit level. They have been aggregated by one or more appropriate means in order to express a consolidated value or interpretation for the map unit as a whole.
Use the get_SDA_muaggatt()
convenience function, or
write a simple query in SQL and submit via SDA_query()
.
# copy example grid
mu2 <- mu
# extract RAT for thematic mapping
rat <- cats(mu2)[[1]]
# optionally use convenience function:
# * returns all fields from muagatt table
# * along with map unit name
# tab <- get_SDA_muaggatt(mukeys = as.numeric(rat$mukey), query_string = TRUE)
# the SQL is simple, only need a few columns from a single table
# safely create IN statement from vector of mukey
.sql <- sprintf(
"SELECT mukey, aws050wta, aws0100wta FROM muaggatt WHERE mukey IN %s",
format_SQL_in_statement(as.numeric(rat$mukey))
)
# run query, result is a data.frame
tab <- SDA_query(.sql)
# check
head(tab)
## mukey aws050wta aws0100wta
## 1 144983 7.26 14.02
## 2 144984 7.31 14.14
## 3 144985 7.36 14.27
## 4 144986 7.06 13.19
## 5 145005 5.21 9.34
## 6 145009 5.49 9.16
# merge aggregate soil data into RAT
rat <- merge(rat, tab, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu2) <- rat
# convert grid + RAT -> stack of propery grids
# note explicit subset of returned grids, we don't need the mukey grid
aws <- catalyze(mu2)[[c('aws050wta', 'aws0100wta')]]
# simple plot, note scales are different
plot(aws, axes = FALSE, col = hcl.colors(10, 'mako'), main = c('Plant Available Water Storage (cm)\nWeighted Mean over Components, 0-50cm', 'Plant Available Water Storage (cm)\nWeighted Mean over Components, 0-100cm'))
Use the get_SDA_interpretation()
convenience
function.
# copy example grid
mu2 <- mu
# extract RAT for thematic mapping
rat <- cats(mu2)[[1]]
rules <- c('ENG - Construction Materials; Roadfill', 'AWM - Irrigation Disposal of Wastewater')
tab <- get_SDA_interpretation(
rulename = rules,
method = "Weighted Average",
mukeys = as.numeric(rat$mukey)
)
# check
head(tab)
## areasymbol musym muname mukey
## 1 MT629 101 McCollum fine sandy loam, 0 to 2 percent slopes 144983
## 2 MT629 102 McCollum fine sandy loam, 2 to 4 percent slopes 144984
## 3 MT629 103 McCollum fine sandy loam, 4 to 8 percent slopes 144985
## 4 MT629 104 McCollum fine sandy loam, gravelly substratum, 0 to 2 percent slopes 144986
## 5 MT629 120 Niarada gravelly loam, 0 to 4 percent slopes 145005
## 6 MT629 123 Niarada gravelly loam, cool, 15 to 30 percent slopes 145009
## rating_ENGConstructionMaterialsRoadfill rating_AWMIrrigationDisposalofWastewater
## 1 1.00 0.00
## 2 0.98 0.05
## 3 0.98 0.68
## 4 1.00 0.15
## 5 0.98 0.19
## 6 0.12 1.00
## class_ENGConstructionMaterialsRoadfill class_AWMIrrigationDisposalofWastewater
## 1 Well suited Not limited
## 2 Moderately well suited Slightly limited
## 3 Moderately well suited Moderately limited
## 4 Well suited Slightly limited
## 5 Moderately well suited Slightly limited
## 6 Poorly suited Very limited
## reason_ENGConstructionMaterialsRoadfill
## 1 <NA>
## 2 Low strength (0.778); Dusty (0.785)
## 3 Low strength (0.778); Dusty (0.785)
## 4 <NA>
## 5 Dusty (0.981); Dusty (0.981); Dusty (0.902)
## 6 Slope (0.08); Dusty (0.975); Slope (0.08); Dusty (0.981); Depth to bedrock (0); Slope (0)
## reason_AWMIrrigationDisposalofWastewater
## 1 <NA>
## 2 Filtering capacity (1); Too steep for surface application (0.08)
## 3 Too steep for surface application (0.68); Too steep for surface application (0.68)
## 4 Filtering capacity (1); Filtering capacity (1); Droughty (0.918)
## 5 Droughty (0.133); Large stones on the surface (0.141); Droughty (0.117); Filtering capacity (1); Droughty (0.918); Slow water movement (0.372)
## 6 Too steep for surface application (1); Too steep for sprinkler application (1); Droughty (0.061); Too steep for surface application (1); Too steep for sprinkler application (1); Large stones on the surface (0.141); Droughty (0.117); Droughty (1); Too steep for surface application (1); Too steep for sprinkler application (1); Depth to bedrock (1); Large stones on the surface (0.684); Filtering capacity (1); Too steep for surface application (1); Droughty (0.918); Too steep for sprinkler application (0.395)
# set factor levels
tab$class_ENGConstructionMaterialsRoadfill <- factor(
tab$class_ENGConstructionMaterialsRoadfill,
levels = c('Not suited', 'Poorly suited', 'Moderately suited', 'Moderately well suited', 'Well suited', 'Not Rated'),
ordered = TRUE
)
bwplot(
class_ENGConstructionMaterialsRoadfill ~ rating_ENGConstructionMaterialsRoadfill,
data = tab,
xlab = 'Fuzzy Rating',
main = 'ENG - Construction Materials; Roadfill',
par.settings = tactile.theme()
)
# merge aggregate soil data into RAT
rat <- merge(rat, tab, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu2) <- rat
# convert grid + RAT -> stack of property grids
# note explicit subset of returned grids, we don't need the mukey grid
vars <- c('rating_ENGConstructionMaterialsRoadfill', 'rating_AWMIrrigationDisposalofWastewater')
rating <- catalyze(mu2)[[vars]]
# simple plot, note scales are different
plot(rating, axes = FALSE, col = hcl.colors(10, 'mako'), main = c('Construction Materials; Roadfill\nWeighted Mean over Components', 'Irrigation Disposal of Wastewater\nWeighted Mean over Components'))
Steel corrosion potential.
# copy example grid
mu2 <- mu
# extract RAT for thematic mapping
rat <- cats(mu2)[[1]]
tab <- get_SDA_property(property = 'Corrosion of Steel',
method = 'DOMINANT CONDITION',
mukeys = as.integer(rat$mukey)
)
# convert to factor and set levels in logical order
tab$corsteel <- factor(tab$corsteel, levels = c('Low', 'Moderate', 'High'))
# merge aggregate soil data into RAT
rat <- merge(rat, tab, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu2) <- rat
# set active category to corsteel
activeCat(mu2) <- 'corsteel'
# plot
plot(mu2, axes = FALSE, col = hcl.colors(3), mar = c(0, 0, 0, 6))
Simplified component parent material group.
# copy example grid
mu2 <- mu
# extract RAT for thematic mapping
rat <- cats(mu2)[[1]]
# simplified parent material group name
tab <- get_SDA_pmgroupname(mukeys = as.integer(rat$mukey))
# merge aggregate soil data into RAT
rat <- merge(rat, tab, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu2) <- rat
# set active category to corsteel
activeCat(mu2) <- 'pmgroupname'
plot(mu2, axes = FALSE, col = hcl.colors(10), mar = c(0, 0, 0, 12))
Hydric Rating.
# copy example grid
mu2 <- mu
# extract RAT for thematic mapping
rat <- cats(mu2)[[1]]
# simplified parent material group name
tab <- get_SDA_hydric(mukeys = as.integer(rat$mukey))
# merge aggregate soil data into RAT
rat <- merge(rat, tab, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu2) <- rat
# set active category to corsteel
activeCat(mu2) <- 'HYDRIC_RATING'
plot(mu2, axes = FALSE, col = hcl.colors(10), mar = c(0, 0, 0, 6))
The get_SDA_property()
function from soilDB
is convenient interface to aggregated
SSURGO/STATSGO tabular data via SDA.
Setup a new AOI for the following examples.
# https://casoilresource.lawr.ucdavis.edu/gmap/?loc=36.57666,-96.70175,z14
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a <- st_bbox(
c(xmin = -96.7696, xmax = -96.6477, ymin = 36.5477, ymax = 36.6139),
crs = st_crs(4326)
)
# fetch gSSURGO map unit keys at native resolution (~30m)
mu <- mukey.wcs(aoi = a, db = 'gssurgo')
# check: OK
plot(
mu,
legend = FALSE,
axes = FALSE,
col = hcl.colors(100),
main = 'gSSURGO Map Unit Key Grid'
)
Derive aggregate soil properties, merge with raster attribute table (RAT).
# extract RAT for thematic mapping
rat <- cats(mu)[[1]]
# variables of interest
vars <- c("dbthirdbar_r", "awc_r", "ph1to1h2o_r")
# get / aggregate specific horizon-level properties from SDA
# be sure to see the manual page for this function
p <- get_SDA_property(property = vars,
method = "Dominant Component (Numeric)",
mukeys = as.integer(rat$mukey),
top_depth = 0,
bottom_depth = 25)
# check: OK
head(p)
## mukey areasymbol musym muname
## 1 623396 OK113 1 Apperson silty clay loam, 1 to 3 percent slopes
## 2 623399 OK113 4 Coyle loam, 1 to 3 percent slopes
## 3 623402 OK113 7 Keokuk very fine sandy loam, 0 to 1 percent slopes, occasionally flooded
## 4 623405 OK113 10 Bethany silt loam, 1 to 3 percent slopes
## 5 623406 OK113 11 Bethany silt loam, 3 to 5 percent slopes
## 6 623407 OK113 12 Bethany-Pawhuska complex, 1 to 5 percent slopes
## dbthirdbar_r awc_r ph1to1h2o_r
## 1 1.45 0.18 6.15
## 2 1.40 0.18 5.70
## 3 1.43 0.17 7.30
## 4 1.35 0.20 6.20
## 5 1.30 0.20 6.30
## 6 1.34 0.20 6.30
# convert areasymbol into a factor easy plotting later
p$areasymbol <- factor(p$areasymbol)
# merge aggregate soil data into RAT
rat <- merge(rat, p, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu) <- rat
# result is a grid of map unit keys + RAT
Plant available water 0-25cm.
# list variables in the RAT
names(cats(mu)[[1]])
## [1] "mukey" "ID" "areasymbol" "musym" "muname" "dbthirdbar_r"
## [7] "awc_r" "ph1to1h2o_r"
activeCat(mu) <- 'awc_r'
plot(mu)
Plot aggregated soil properties.
# reset the active category to 1
# this will ensure that all variables in the RAT will be converted by catalyze(mu)
activeCat(mu) <- 1
# convert mukey grid + RAT -> stack of numerical grids
mu.stack <- catalyze(mu)
# keep only properties / remove IDs
mu.stack <- mu.stack[[vars]]
# note implicit simplification via maxpixels
levelplot(
mu.stack[['dbthirdbar_r']],
main = '1/3 Bar Bulk Density (g/cm^3)\nDominant Component\n0-25cm',
margin = FALSE,
scales = list(draw = FALSE),
col.regions = hcl.colors,
maxpixels = 1e5
)
levelplot(
mu.stack[['awc_r']],
main = 'AWC (cm/cm)\nDominant Component\n0-25cm',
margin = FALSE,
scales = list(draw = FALSE),
col.regions = hcl.colors,
maxpixels = 1e5
)
levelplot(
mu.stack[['ph1to1h2o_r']],
main = 'pH 1:1 H2O\nDominant Component\n0-25cm',
margin = FALSE,
scales = list(draw = FALSE),
col.regions = hcl.colors,
maxpixels = 1e5
)
A not so great SSA join (1979 vs 2004).
Setup BBOX and query mukey WCS.
# extract a BBOX like this from SoilWeb by pressing "b"
bb <- '-91.6853 36.4617,-91.6853 36.5281,-91.5475 36.5281,-91.5475 36.4617,-91.6853 36.4617'
wkt <- sprintf('POLYGON((%s))', bb)
# init sf object from WKT
x <- st_as_sfc(wkt)
# assign GCS WGS84 coordinate reference system
st_crs(x) <- 4326
# get gSSURGO grid here
mu <- mukey.wcs(aoi = x, db = 'gssurgo')
# note SSA boundary
plot(mu, legend = FALSE, axes = FALSE, col = hcl.colors(100))
Derive aggregate sand, silt, clay (RV) values from the largest component, taking the weighted mean over 25-50cm depth interval.
# extract RAT for thematic mapping
rat <- cats(mu)[[1]]
# variables of interest
vars <- c("sandtotal_r","silttotal_r","claytotal_r")
# get thematic data from SDA
# dominant component
# depth-weighted average
# sand, silt, clay (RV)
p <- get_SDA_property(property = vars,
method = "Dominant Component (Numeric)",
mukeys = as.integer(rat$mukey),
top_depth = 25,
bottom_depth = 50)
# check
head(p)
## mukey areasymbol musym muname
## 1 691980 MO091 73306 Gressy-Gatewood complex, 3 to 8 percent slopes, rocky
## 2 2502332 MO149 73321 Alred-Gatewood complex, 1 to 8 percent slopes
## 3 2502334 MO149 73322 Alred-Gatewood complex, 8 to 15 percent slopes
## 4 2503322 MO149 76002 Batcave-Farewell complex, 1 to 3 percent slopes, frequently flooded
## 5 2503473 MO149 76046 Secesh silt loam, 1 to 3 percent slopes, rarely flooded
## 6 2503476 MO149 76047 Secesh-Tilk complex, 1 to 3 percent slopes, occasionally flooded
## sandtotal_r silttotal_r claytotal_r
## 1 23.00 59.00 18.00
## 2 28.69 51.11 20.20
## 3 28.69 51.11 20.20
## 4 40.00 40.00 20.00
## 5 25.65 49.00 25.35
## 6 26.91 48.08 25.02
# merge aggregate soil data into RAT
rat <- merge(rat, p, by.x = 'mukey', by.y = 'mukey', sort = FALSE, all.x = TRUE)
# requires that grid cell ID (mukey) be numeric
rat$mukey <- as.integer(rat$mukey)
levels(mu) <- rat
# convert mukey grid + RAT -> stack of numerical grids
# retaining only sand, silt, clay via [[vars]]
ssc <- catalyze(mu)[[vars]]
# graphical check
# note implicit simplification via maxpixels
levelplot(
ssc,
main = 'Sand, Silt, Clay (RV)\nDominant Component\n25-50cm',
margin = FALSE,
scales = list(draw = FALSE),
col.regions = hcl.colors,
maxpixels = 1e4,
layout = c(3, 1)
)
Convert to soil texture class of <2mm fraction.
# create a new grid by copy
# we will place soil texture classes into this grid
texture.class <- ssc[[1]]
names(texture.class) <- 'soil.texture'
# assign soil texture classes for the fine earth fraction
# using sand and clay percentages
values(texture.class) <- ssc_to_texcl(
sand = values(ssc[['sandtotal_r']]),
clay = values(ssc[['claytotal_r']]),
droplevels = FALSE
)
plot(texture.class, col = hcl.colors(50), axes = FALSE, main = 'Soil Texture Class <2mm Fraction\nDominant Component, 25-50cm (RV)')
Available data sets. (TODO: there are more more to add, including NCCPI)
WCS_details(wcs = 'ISSR800')
var | description |
---|---|
caco3_kg_sq_m | Total CaCO3 (kg/m^2) |
cec_025cm | CEC at pH 7 0-25cm depth (cmol[+]/kg) |
cec_050cm | CEC at pH 7 0-50cm depth (cmol[+]/kg) |
cec_05cm | CEC at pH 7 0-5cm depth (cmol[+]/kg) |
clay_025cm | clay percent 0-25cm depth |
clay_05cm | clay percent 0-5cm depth |
clay_2550cm | clay percent 25-50cm depth |
clay_3060cm | clay percent 30-60cm depth |
drainage_class | Soil Drainage Class |
ec_025cm | EC 0-25cm depth (dS/m) |
ec_05cm | EC 0-5cm depth (dS/m) |
greatgroup | Soil Taxonomy: Greatgroup |
hydgrp | Hydrologic Soil Group |
lcc_irrigated | Land Capability Class, irrigated |
lcc_nonirrigated | Land Capability Class, non-irrigated |
om_kg_sq_m | Total Soil Organic Matter (kg/m^2) |
paws | total plant available water storage (cm water) |
paws_025cm | plant available water storage 0-25cm depth (cm water) |
paws_050cm | plant available water storage 0-50cm depth (cm water) |
ph_025cm | pH 1:1 H2O 0-25cm depth |
ph_05cm | pH 1:1 H2O 0-5cm depth |
ph_2550cm | pH 1:1 H2O 25-50cm depth |
ph_3060cm | pH 1:1 H2O 30-60cm depth |
sand_025cm | sand percent 0-25cm depth |
sand_05cm | sand percent 0-5cm depth |
sand_2550cm | sand percent 25-50cm depth |
sand_3060cm | sand percent 30-60cm depth |
sar | SAR, entire profile |
series_name | Soil Series Name |
silt_025cm | silt percent 0-25cm depth |
silt_05cm | silt percent 0-5cm depth |
silt_2550cm | silt percent 25-50cm depth |
silt_3060cm | silt percent 30-60cm depth |
soilorder | Soil Taxonomy: Soil Order |
ssurgo_pct | SSURGO data available, fraction of 800x800m grid cell |
statsgo_pct | STATSGO data available, fraction of 800x800m grid cell |
str | Soil Temperature Regime |
suborder | Soil Taxonomy: Suborder |
texture_025cm | Soil Texture Class, 0-25cm |
texture_05cm | Soil Texture Class, 0-5cm |
texture_2550cm | Soil Texture Class, 25-50cm |
weg | Wind Erodibility Group |
wei | Wind Erodibility Index |
Examples for a broad slice of California
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a.CA <- st_bbox(
c(xmin = -121, xmax = -120, ymin = 37, ymax = 38),
crs = st_crs(4326)
)
# convert bbox to sf geometry
a.CA <- st_as_sfc(a.CA)
# floating point grids
paws_total <- ISSR800.wcs(aoi = a.CA, var = 'paws')
paws_025cm <- ISSR800.wcs(aoi = a.CA, var = 'paws_025cm')
paws_050cm <- ISSR800.wcs(aoi = a.CA, var = 'paws_050cm')
pH_05cm <- ISSR800.wcs(aoi = a.CA, var = 'ph_05cm')
pH_3060cm <- ISSR800.wcs(aoi = a.CA, var = 'ph_3060cm')
clay_05cm <- ISSR800.wcs(aoi = a.CA, var = 'clay_05cm')
clay_3060cm <- ISSR800.wcs(aoi = a.CA, var = 'clay_3060cm')
silt_3060cm <- ISSR800.wcs(aoi = a.CA, var = 'silt_3060cm')
sand_3060cm <- ISSR800.wcs(aoi = a.CA, var = 'sand_3060cm')
## NA encoding issues
# 0 is a real value
sar <- ISSR800.wcs(aoi = a.CA, var = 'sar')
# 16bit integer grids
wei <- ISSR800.wcs(aoi = a.CA, var = 'wei')
# 8bit unsigned (BYTE) grids with RAT
drainage_class <- ISSR800.wcs(aoi = a.CA, var = 'drainage_class')
weg <- ISSR800.wcs(aoi = a.CA, var = 'weg')
str <- ISSR800.wcs(aoi = a.CA, var = 'str')
# metadata
metags(sar)
## description vintage
## "SAR, entire profile" "FY2024"
Check to see if aggregated sand + silt + clay is roughly = 100%. Almost. This isn’t surprising since each component of soil texture is aggregated separately.
z <- sand_3060cm + silt_3060cm + clay_3060cm
plot(z - 100)
Soil texture class 25-50cm.
texture_2550cm <- ISSR800.wcs(aoi = a.CA, var = 'texture_2550cm')
# plot using SoilWeb soil texture class colors
# preset by ISSR800.wcs()
plot(texture_2550cm, axes = FALSE)
Other soil properties of interest.
plot(sar, axes = FALSE, col = hcl.colors(50), main = 'SAR')
plot(wei, axes = FALSE, col = hcl.colors(50), main = 'Wind Erodibility Index')
plot(drainage_class, axes = FALSE, col = hcl.colors(50), main = 'Drainage Class')
plot(weg, axes = FALSE, col = hcl.colors(50), main = 'Wind Erodibility Group')
plot(str, axes = FALSE, col = hcl.colors(50), main = 'Soil Temperature Regime')
Stacking soil property grids for multi-panel figures that share a common color ramp / legend.
# combine into multi-layer SpatRaster
# like raster::stack()
s <- c(pH_05cm, pH_3060cm)
# plot multiple layers via SpatRaster plot method
# common color map / legend
plot(s, col = hcl.colors(50), axes = FALSE, mar = c(2, 2, 2, 4), range = c(4.5, 9))
I like this approach.
# or, plot using rasterVis::levelplot
# note laundering of layer names, required when original layer names contain spaces, punctuation, etc.
names(s) <- make.names(names(s))
# manually specify panel names
# in original ordering specified to c()
# formatting ideas: https://stackoverflow.com/questions/34652580/change-raster-panel-titles-using-levelplot
levelplot(s,
scales = list(draw = FALSE),
col.regions = hcl.colors,
names.attr = c('pH 1:1 H20, 0-5cm', 'pH 1:1 H20, 30-60cm')
)
Another example, with 3 panels.
s <- c(sand_3060cm, silt_3060cm, clay_3060cm)
names(s) <- make.names(names(s))
levelplot(s,
scales = list(draw = FALSE),
col.regions = hcl.colors,
names.attr = c('Percent Sand, 30-60cm', 'Percent Silt, 30-60cm', 'Percent Clay, 30-60cm')
)
# use terra methods to define a BBOX, from SoilWeb 'b' keypress
# https://casoilresource.lawr.ucdavis.edu/gmap/?loc=47.24008,-99.73114,z11
# generate WKT description of viewport BBOX
bb <- '-100.2976 47.0407,-100.2976 47.4717,-99.3013 47.4717,-99.3013 47.0407,-100.2976 47.0407'
wkt <- sprintf('POLYGON((%s))', bb)
# init spatVector
a <- vect(wkt, crs = 'epsg:4326')
# results are grids + RAT
greatgroup <- ISSR800.wcs(aoi = a, var = 'greatgroup')
# create a large color palette for greatgroup classes
cols <- brewer.pal(12, 'Set3')
cols <- darken(cols, amount = 0.25)
cr <- colorRampPalette(cols, space = 'Lab', interpolate = 'spline')
plot(greatgroup, axes = FALSE, col = cr(50), mar = c(1, 1, 1, 6))
# both LCC data may or may not available in some places
lcc_nonirrigated <- ISSR800.wcs(aoi = a, var = 'lcc_nonirrigated')
lcc_irrigated <- ISSR800.wcs(aoi = a, var = 'lcc_irrigated')
# check NA fraction
global(lcc_nonirrigated, 'isNA') / ncell(lcc_nonirrigated)
## isNA
## label 0.000490918
global(lcc_irrigated, 'isNA') / ncell(lcc_irrigated)
## isNA
## Land Capability Class, irrigated 1
# colors for irrigated/non-irrigated LCC
cols <- hcl.colors(50, 'Spectra', rev = TRUE)
cols <- colorspace::darken(cols, amount = 0.1)
cr <- colorRampPalette(cols, space = 'Lab', interpolate = 'spline')
plot(lcc_nonirrigated, axes = FALSE, col = cr(50), mar = c(1, 1, 1, 6), main = 'Non-Irrigated LCC')
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a <- st_bbox(
c(xmin = -121, xmax = -120, ymin = 37, ymax = 38),
crs = st_crs(4326)
)
# convert bbox to sf geometry
a <- st_as_sfc(a)
# moist soil color at 25cm, low-res version
s <- soilColor.wcs(aoi = a, var = 'sc025cm', res = 270)
# metadata
metags(s)
## description vintage
## "Moist soil color, 25cm (270m)" "FY2023"
# color table is pre-set by soilColor.wcs()
plot(s, legend = FALSE, axes = FALSE, main = metags(s, name = 'description'))
Soil color distribution.
par(mar = c(4.5, 5, 1, 1))
# specific to this AOI
rat <- cats(s)[[1]]
barplot(s, col = rat$col, names.arg = rat$munsell, las = 1, cex.names = 0.75, horiz = TRUE, xlab = 'Cell Count')
# these colors, but frequencies tabulated across all of CONUS
# barplot(rat$freq, col = rat$col, las = 3, names.arg = rat$munsell, cex.names = 0.6)
Detailed soil color mapping.
# from SoilWeb
bb <- '-90.1576 41.8579,-90.1576 41.9193,-90.0100 41.9193,-90.0100 41.8579,-90.1576 41.8579'
# use terra methods
# convert text -> WKT -> spatVect
wkt <- sprintf('POLYGON((%s))', bb)
a <- vect(wkt, crs = 'epsg:4326')
# moist soil color at 25cm, low-res version
s <- soilColor.wcs(aoi = a, var = 'sc025cm_hr', res = 30)
# color table is pre-set by soilColor.wcs()
plot(s, legend = FALSE, axes = FALSE, main = metags(s, name = 'description'))
# part of CA
# make a bounding box and assign a CRS (4326: GCS, WGS84)
a <- st_bbox(
c(xmin = -121, xmax = -120, ymin = 37, ymax = 38),
crs = st_crs(4326)
)
# convert bbox to sf geometry
a <- st_as_sfc(a)
# EC 0-25cm
ec <- ISSR800.wcs(aoi = a, var = 'ec_025cm')
# pH 0-25cm
pH <- ISSR800.wcs(aoi = a, var = 'ph_025cm')
# no ESP grid, use a constant value of 1%
esp <- ec
values(esp) <- 0
names(esp) <- 'ESP'
# copy EC grid, replace values with classification in next step
ss <- ec
names(ss) <- 'FAO Salt Severity'
# resulting raster contains factor levels (integers) and labels
values(ss) <- allocate(EC = values(ec), pH = values(pH), ESP = values(esp), to = 'FAO Salt Severity', droplevels = TRUE)
# note factor details stored in raster attribute table (RAT)
print(ss)
## class : SpatRaster
## dimensions : 163, 142, 1 (nrow, ncol, nlyr)
## resolution : 800, 800 (x, y)
## extent : -2178400, -2064800, 1816800, 1947200 (xmin, xmax, ymin, ymax)
## coord. ref. : NAD83 / Conus Albers (EPSG:5070)
## source(s) : memory
## varname : file3b28556b7014
## categories : label
## name : FAO Salt Severity
## min value : extremely saline
## max value : none
# plot
plot(ss, axes = FALSE, main = 'FAO Salt Severity', col = hcl.colors(10))
FAO Salt Severity Classification
## TODO: NODATA not correctly handled, possibly need to mask
# combine individual SpatRaster objects into a multi-band SpatRaster
s <- c(pH, ec, esp)
# classification wrapper
.fun <- function(x) {
.pH <- x[1]
.ec <- x[2]
.esp <- x[3]
.res <- allocate(EC = .ec, pH = .pH, ESP = .esp, to = 'FAO Salt Severity', droplevels = FALSE)
return(.res)
}
# apply classification pixel-wise to stack
ss <- app(s, fun = .fun)
## TODO: can this be done within app()?
# manually specified levels
ll <- c("nonsaline", "slightly saline", "moderately saline",
"strongly saline", "very strongly saline", "extremely saline",
"saline-sodic", "slightly sodic", "moderately sodic", "strongly sodic",
"very strongly sodic")
# convert to grid + RAT
levels(ss) <- data.frame(ID = 1:length(ll), class = ll)
plot(ss, axes = FALSE, main = 'FAO Salt Severity', col = hcl.colors(10))
This document is based on soilDB
version 2.8.3 and
terra
version 1.7-75.