Load SSURGO lookup tables and extract soil profiles for locations.
Initialize SSURGO lookup tables and optional spatial subset.
When a location (lat_lon) or boundary polygon is provided, the map-unit table is filtered to include only
those map units that intersect the location or polygon; the map-units are also grouped by name and symbol, and
the major map unit is selected for profile extraction.
| Parameters: |
-
path
(str | Path)
–
Directory containing SSURGO geodatabase and lookup CSV files.
-
state
(str)
–
State identifier used in SSURGO file naming.
-
lat_lon
(LatLon | None, default:
None
)
–
Optional latitude/longitude for point-based spatial filtering.
-
boundary
(GeoDataFrame | None, default:
None
)
–
Optional boundary GeoDataFrame for polygon-based filtering.
|
| Raises: |
-
ValueError
–
If both lat_lon and boundary are provided.
|
Source code in cycles/ssurgo/ssurgo.py
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143 | def __init__(self, path: str | Path, state: str, *, lat_lon: LatLon | None=None, boundary: gpd.GeoDataFrame | None=None) -> None:
"""Initialize SSURGO lookup tables and optional spatial subset.
When a location (`lat_lon`) or boundary polygon is provided, the map-unit table is filtered to include only
those map units that intersect the location or polygon; the map-units are also grouped by name and symbol, and
the major map unit is selected for profile extraction.
Args:
path: Directory containing SSURGO geodatabase and lookup CSV files.
state: State identifier used in SSURGO file naming.
lat_lon: Optional latitude/longitude for point-based spatial filtering.
boundary: Optional boundary GeoDataFrame for polygon-based filtering.
Returns:
None.
Raises:
ValueError: If both ``lat_lon`` and ``boundary`` are provided.
"""
_validate_geographic_input(lat_lon, boundary)
self.state: str = state
self._mapunits: gpd.GeoDataFrame | pd.DataFrame
self.components: pd.DataFrame
self.horizons: pd.DataFrame
self.grouped_mapunits: MapUnitGeoDataFrame | None = None
self.mukey: int | None = None
self.slope: float | None = None
self.hsg: str = ''
path = Path(path)
luts = _read_all_luts(path, state)
self.components = luts['component']
self.horizons = luts['horizon']
if lat_lon is None and boundary is None:
self._mapunits = luts['mapunit']
return
if lat_lon is not None:
boundary = gpd.GeoDataFrame(
{'name': ['point']},
geometry=[Point(lat_lon[1], lat_lon[0])],
crs='epsg:4326',
)
gdf = _read_mupolygon(path, state, boundary)
self._mapunits = gdf.merge(luts['mapunit'], on='mukey', how='left')
self.components = self.components[self.components['mukey'].isin(self._mapunits['mukey'].unique())]
self.horizons = self.horizons[self.horizons['cokey'].isin(self.components['cokey'].unique())]
self._group_map_units()
self._select_major_mapunit()
self._average_slope_hsg()
|
mapunits
property
Return loaded map-unit table.
| Returns: |
-
MapUnitGeoDataFrame | DataFrame
–
Map-unit table as GeoDataFrame/DataFrame, or None if not loaded.
|
muname
property
Return map-unit name for the currently selected MUKEY.
musym
property
Return map-unit symbol for the currently selected MUKEY.
non_soil_mask(mapunits)
Build a mask for non-soil or urban map units.
| Parameters: |
-
mapunits
(DataFrame | GeoDataFrame)
–
Map-unit table to evaluate.
|
| Returns: |
-
Series
–
Boolean Series where True indicates non-soil or urban classes.
|
Source code in cycles/ssurgo/ssurgo.py
201
202
203
204
205
206
207
208
209
210
211
212
213
214 | def non_soil_mask(self, mapunits: pd.DataFrame | gpd.GeoDataFrame) -> pd.Series:
"""Build a mask for non-soil or urban map units.
Args:
mapunits: Map-unit table to evaluate.
Returns:
Boolean Series where True indicates non-soil or urban classes.
"""
return (
mapunits['mukey'].isna() |
mapunits['muname'].isin(SSURGO_NON_SOIL_TYPES) |
mapunits['muname'].str.contains('|'.join(SSURGO_URBAN_TYPES), na=False)
)
|
get_soil_profile(*, mukey=None, major_only=True)
Build a soil profile from SSURGO components and horizons.
| Parameters: |
-
mukey
(int | None, default:
None
)
–
Optional map-unit key. If omitted, the selected major MUKEY is used.
-
major_only
(bool, default:
True
)
–
If True, include only components marked as major.
|
| Returns: |
-
list[SoilLayer]
–
Soil profile as a list of SoilLayer records.
|
Source code in cycles/ssurgo/ssurgo.py
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 | def get_soil_profile(self, *, mukey: int | None=None, major_only: bool=True) -> list[SoilLayer]:
"""Build a soil profile from SSURGO components and horizons.
Args:
mukey: Optional map-unit key. If omitted, the selected major MUKEY is used.
major_only: If True, include only components marked as major.
Returns:
Soil profile as a list of ``SoilLayer`` records.
"""
mukey = mukey or self._ensure_mukey()
df = self.components[self.components['mukey'] == int(mukey)].copy()
if major_only:
df = df[df['majcompflag'] == 'Yes']
df = pd.merge(df, self.horizons, on='cokey').query("hzname != 'R'").sort_values(by=['cokey', 'top'], ignore_index=True)
return [SoilLayer(
top = row['top'],
bottom = row['bottom'],
**{p: None if pd.isna(row[p]) else row[p] for p in MAPPABLE_PARAMETERS}, # type: ignore
) for _, row in df.iterrows()]
|
generate_soil_file(fn, *, mukey=None, desc=None, hsg=None, slope=None, soil_depth=None)
Generate a Cycles soil file from SSURGO profile data.
| Parameters: |
-
fn
(Path | str)
–
-
mukey
(int | None, default:
None
)
–
Optional map-unit key. If omitted, dominant MUKEY is used.
-
desc
(str | None, default:
None
)
–
Optional custom header text for the output file.
-
hsg
(str | None, default:
None
)
–
Optional hydrologic soil group; inferred from map unit if omitted.
-
slope
(float | None, default:
None
)
–
Optional slope value; inferred from map unit if omitted.
-
soil_depth
(float | None, default:
None
)
–
Optional maximum depth (m) used during profile mapping.
|
Source code in cycles/ssurgo/ssurgo.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 | def generate_soil_file(self, fn: Path | str, *,
mukey: int | None=None, desc: str | None=None, hsg: str | None=None, slope: float | None=None, soil_depth: float | None=None) -> None:
"""Generate a Cycles soil file from SSURGO profile data.
Args:
fn: Output soil file path.
mukey: Optional map-unit key. If omitted, dominant MUKEY is used.
desc: Optional custom header text for the output file.
hsg: Optional hydrologic soil group; inferred from map unit if omitted.
slope: Optional slope value; inferred from map unit if omitted.
soil_depth: Optional maximum depth (m) used during profile mapping.
Returns:
None.
"""
if mukey is not None:
hsg = self._mapunits[self._mapunits['mukey'] == mukey]['hydgrpdcd'].iloc[0] if hsg is None else hsg
slope = self._mapunits[self._mapunits['mukey'] == mukey]['slopegradwta'].iloc[0] if slope is None else slope
else:
mukey = self._ensure_mukey()
hsg = self.hsg if hsg is None else hsg
slope = self.slope if slope is None else slope
assert mukey is not None and hsg is not None and slope is not None
profile = self.get_soil_profile(mukey=mukey)
desc = desc if desc is not None else _build_desc(self._get_muname(mukey), mukey, hsg)
_generate_soil_file(fn, profile, desc=desc, hsg=hsg, slope=slope, soil_depth=soil_depth)
|