Skip to content

gSSURGO: Fix spatial sampling and improve data aggregation accuracy#3643

Open
divine7022 wants to merge 29 commits into
PecanProject:developfrom
divine7022:fix/gssurgo-improve
Open

gSSURGO: Fix spatial sampling and improve data aggregation accuracy#3643
divine7022 wants to merge 29 commits into
PecanProject:developfrom
divine7022:fix/gssurgo-improve

Conversation

@divine7022

Copy link
Copy Markdown
Member

Description

Refactored extract_soil_gssurgo() to replace point-based WFS queries with raster-based Web Coverage Service (WCS) approach, enabling accurate area-weighted sampling and eliminating spatial coverage gaps.

  • Implemented bounding box raster query using soilDB::mukey.wcs() to extract map unit keys with complete spatial coverage at 30m resolution. This provides accurate area weighting through pixel counts and eliminates spatial coverage gaps.
  • Utilized soilDB::get_SDA_property() with "Weighted Average" aggregation method. This retrieves soil properties (sand, silt, clay, organic matter, bulk density) integrated across specified depth ranges with component weighting in a single batch query.
  • Integrated soilDB::fetchSDA() to obtain complete rock fragment data (fragvol_r) representing total volume across all size classes: 2-75mm (pebbles), 75-250mm (cobbles), 250-600mm (stones), and >600mm (boulders). Applied proper depth and component weighting for fragments:

Motivation and Context

Fixes #3609

Review Time Estimate

  • Immediately
  • Within one week
  • When possible

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • My name is in the list of CITATION.cff
  • I agree that PEcAn Project may distribute my contribution under any or all of
    • the same license as the existing code,
    • and/or the BSD 3-clause license.
  • I have updated the CHANGELOG.md.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@infotroph infotroph changed the title Fix spatial sampling and improve data aggregation accuracy gSSURGO: Fix spatial sampling and improve data aggregation accuracy Oct 10, 2025
Comment thread CHANGELOG.md Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated

@dlebauer dlebauer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR implements the features specified in #3609. Please check comments and suggestions, then will be ready to merge, thanks!

Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R
Comment thread modules/data.land/R/extract_soil_nc.R

@dlebauer dlebauer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Thanks for all of your work on this!

@infotroph it looks like @divine7022 has addressed your suggestions and answered your questions. This seems to be a substantial improvement. Are there any errors that would prevent this from being merged?

@infotroph infotroph left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments here. I haven't finished my re-review yet, but I can already say that given the magnitude of the changes it would be very helpful to include some test cases so we can verify the function is behaving as intended.

Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
@github-actions github-actions Bot added the tests label Nov 29, 2025
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/tests/testthat/test-extract_soil_nc.R Outdated
Comment thread modules/data.land/tests/testthat/test-extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R Outdated
Comment thread modules/data.land/R/extract_soil_nc.R
Comment thread modules/data.land/R/extract_soil_nc.R
@dlebauer dlebauer added the ccmmf issues and pre related to the ccmmf project label Jan 20, 2026
Comment on lines +176 to +181
# Validate depths parameter
if (depths[1] != 0) {
PEcAn.logger::logger.severe(
"First depth must be 0. Use depths = c(0, 0.15, 0.30, ...) like hist() breaks."
)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per conversation (much) earlier in this PR, this is clarifying and simplifying behavior that already existed. 👍 to that.

But! This function is the only PEcAn soil tool I can find that expects top-of-layer depths; the others either don't say or expect bottom-of-layer depth:

See also #3775

@infotroph infotroph mentioned this pull request Feb 4, 2026
Comment on lines +205 to +237
test_that("extract_soil_gssurgo requires depths to start with 0", {
skip_on_cran()
skip_on_ci()
tmp_outdir <- withr::local_tempdir("gssurgo_test_")

# Disable debugging during error testing
withr::local_options(error = NULL)

# Should error when depths doesn't start with 0
expect_error(
extract_soil_gssurgo(
outdir = tmp_outdir,
lat = 40.1164,
lon = -88.2434,
size = 1,
radius = 500,
depths = c(0.15, 0.30) # Missing 0 at start
),
regexp = "First depth must be 0"
)

# Should work when depths starts with 0
res <- extract_soil_gssurgo(
outdir = tmp_outdir,
lat = 40.1164,
lon = -88.2434,
size = 1,
radius = 500,
depths = c(0, 0.15, 0.30) # Correct format
)

expect_false(is.null(res))
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depth validation happens before any API calls, so it's safe to remove the skips from this. Avoiding boilerplate also makes it much easier to see what's actually happening in this test:

Suggested change
test_that("extract_soil_gssurgo requires depths to start with 0", {
skip_on_cran()
skip_on_ci()
tmp_outdir <- withr::local_tempdir("gssurgo_test_")
# Disable debugging during error testing
withr::local_options(error = NULL)
# Should error when depths doesn't start with 0
expect_error(
extract_soil_gssurgo(
outdir = tmp_outdir,
lat = 40.1164,
lon = -88.2434,
size = 1,
radius = 500,
depths = c(0.15, 0.30) # Missing 0 at start
),
regexp = "First depth must be 0"
)
# Should work when depths starts with 0
res <- extract_soil_gssurgo(
outdir = tmp_outdir,
lat = 40.1164,
lon = -88.2434,
size = 1,
radius = 500,
depths = c(0, 0.15, 0.30) # Correct format
)
expect_false(is.null(res))
})
test_that("extract_soil_gssurgo requires depths to start with 0", {
expect_error(
extract_soil_gssurgo(
outdir = withr::local_tempdir(),
lat = 40,
lon = -88,
depths = c(0.15, 0.30) # Missing 0 at start
),
regexp = "First depth must be 0"
)
})

(Personally I'd also move this to the tests for gssurgo_fetch_area and keep this file focused on testing the ensemble fitting portions, possibly using mocked results from gssurgo_fetch_area to do it)

Comment on lines +163 to +164
expect_type(res_small, "list")
expect_type(res_large, "list")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this doesn't actually check that the change in buffer had any effect! Am I right we should expect re_larges$mukey_counts to sum to more pixels than res_small$mukey_counts?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I'm thinking of gssurgo_fetch_area.... But this test would give about the same information if it called gssurgo_test_area directly.

Comment on lines +188 to +191
if (requireNamespace("ncdf4", quietly = TRUE) && length(res) >= 3) {
# Compare two different ensemble members (skip first - it's unsampled)
nc1 <- ncdf4::nc_open(unlist(res)[2])
nc2 <- ncdf4::nc_open(unlist(res)[3])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible you could reduce boilerplate by using soil_ic_netcdf2list() as a helper to read the nc files

Comment on lines +130 to +131
expect_false(is.null(res))
expect_type(res, "list")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly sure testing if the result is a list is also an effective check that it's not null. Not a big deal in one place, but across many test blocks this adds up to a lot of extra lines to scroll through without much added information about the behavior of the function.

Suggested change
expect_false(is.null(res))
expect_type(res, "list")
expect_type(res, "list")

"mukey", "cokey") %in% names(result$soilprop)))

# Values should be in original units (percentages, not fractions)
expect_true(all(result$soilprop$sandtotal_r <= 100, na.rm = TRUE))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this test won't fail if they are fractions! Would checking that sandtotal+silttotal+claytotal == 100 be too strict?

Comment thread CHANGELOG.md
Comment on lines +67 to +71
- `extract_soil_gssurgo()` (#3643)
- Replaced point-based WFS queries with raster-based WCS approach using `soilDB::mukey.wcs()`.
- Replaced `grid_size`/`grid_spacing` parameters with `radius` (meters) for simpler buffer-based AOI creation.
- Switched to single `soilDB::fetchSDA()` call for component-level soil data retrieval, enabling better ensemble uncertainty quantification through within-map-unit variability. Added support for custom AOI polygons.
- This eliminates spatial coverage gaps and reduces network requests while maintaining backward compatibility.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move up to line 25-ish

Comment thread modules/data.land/tests/testthat/test-extract_soil_nc.R Outdated
Comment thread modules/data.land/tests/testthat/test-extract_soil_nc.R Outdated
Comment thread modules/data.land/R/gSSURGO_Query.R Outdated
@dlebauer

Copy link
Copy Markdown
Member

What is the status of this PR?

Also, Does this pr #3455 overlap or augment other work that has been/will be done on the gssurgo handling?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ccmmf issues and pre related to the ccmmf project dockerfile modules tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore Area-Weighted Sampling for SSURGO

4 participants