Cycles-utils 4.0.0__tar.gz → 4.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/Cycles_utils.egg-info/PKG-INFO +2 -1
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/PKG-INFO +2 -1
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/README.md +1 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles.py +2 -3
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/_base_file.py +1 -1
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/operation_file.py +15 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/plot_tools.py +10 -6
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/ssurgo/ssurgo.py +57 -61
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/setup.py +1 -1
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/Cycles_utils.egg-info/SOURCES.txt +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/Cycles_utils.egg-info/dependency_links.txt +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/Cycles_utils.egg-info/requires.txt +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/Cycles_utils.egg-info/top_level.txt +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/LICENSE +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/MANIFEST.in +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_runner.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/control_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/nudge_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/output_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/reinit_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/soil_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/cycles_tools/weather_file.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/GLDASp5_elevation_025d.nc4 +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/GLDASp5_landmask_025d.nc4 +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/NLDAS_elevation.nc4 +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/NLDAS_masks-veg-soil.nc4 +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/fips_gid_conversion.csv +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/gridMET_elevation_mask.nc +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/metdata_elevationdata.nc +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/data/us_states.csv +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/gadm/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/gadm/gadm.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/rotation_builder.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/soilgrids/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/soilgrids/soilgrids.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/ssurgo/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/weather/__init__.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/cycles/weather/weather.py +0 -0
- {cycles_utils-4.0.0 → cycles_utils-4.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Cycles-utils
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.2
|
|
4
4
|
Summary: Python scripts to build Cycles input files and post-process Cycles output files
|
|
5
5
|
Home-page: https://github.com/PSUmodeling/Cycles-utils
|
|
6
6
|
Author: Yuning Shi
|
|
@@ -42,6 +42,7 @@ Dynamic: summary
|
|
|
42
42
|
This package provides a number of tools for users to prepare Cycles simulation input files, run Cycles simulations, and post-process Cycles simulation results.
|
|
43
43
|
|
|
44
44
|
For usage examples, please refer to this [Jupyter notebook](https://github.com/PSUmodeling/Cycles/blob/master/cycles-utils.ipynb).
|
|
45
|
+
For more complex usage examples, please refer to this [Colab notebook](https://colab.research.google.com/drive/1d8TFfZR_t6eo-Lgh8ZdR382RUcAXlawf?usp=sharing).
|
|
45
46
|
|
|
46
47
|
# Installation
|
|
47
48
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Cycles-utils
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.2
|
|
4
4
|
Summary: Python scripts to build Cycles input files and post-process Cycles output files
|
|
5
5
|
Home-page: https://github.com/PSUmodeling/Cycles-utils
|
|
6
6
|
Author: Yuning Shi
|
|
@@ -42,6 +42,7 @@ Dynamic: summary
|
|
|
42
42
|
This package provides a number of tools for users to prepare Cycles simulation input files, run Cycles simulations, and post-process Cycles simulation results.
|
|
43
43
|
|
|
44
44
|
For usage examples, please refer to this [Jupyter notebook](https://github.com/PSUmodeling/Cycles/blob/master/cycles-utils.ipynb).
|
|
45
|
+
For more complex usage examples, please refer to this [Colab notebook](https://colab.research.google.com/drive/1d8TFfZR_t6eo-Lgh8ZdR382RUcAXlawf?usp=sharing).
|
|
45
46
|
|
|
46
47
|
# Installation
|
|
47
48
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
This package provides a number of tools for users to prepare Cycles simulation input files, run Cycles simulations, and post-process Cycles simulation results.
|
|
5
5
|
|
|
6
6
|
For usage examples, please refer to this [Jupyter notebook](https://github.com/PSUmodeling/Cycles/blob/master/cycles-utils.ipynb).
|
|
7
|
+
For more complex usage examples, please refer to this [Colab notebook](https://colab.research.google.com/drive/1d8TFfZR_t6eo-Lgh8ZdR382RUcAXlawf?usp=sharing).
|
|
7
8
|
|
|
8
9
|
# Installation
|
|
9
10
|
|
|
@@ -150,7 +150,7 @@ class Cycles:
|
|
|
150
150
|
return _plot_yield(self.output['harvest'].data, ax=ax, fontsize=fontsize)
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def plot_operations(self,
|
|
153
|
+
def plot_operations(self, *, axs: Axes | np.ndarray | None=None, fontsize: int | None=None):
|
|
154
154
|
"""Plot operation timelines grouped by rotation year.
|
|
155
155
|
|
|
156
156
|
Args:
|
|
@@ -167,7 +167,6 @@ class Cycles:
|
|
|
167
167
|
assert self.control is not None
|
|
168
168
|
assert self.operations is not None
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
rotation_size = self.control.simulation_years.rotation_size
|
|
170
|
+
rotation_size = self.control.simulation_years.rotation_size
|
|
172
171
|
|
|
173
172
|
return _plot_operations(self.operations, rotation_size, axs=axs, fontsize=fontsize)
|
|
@@ -42,6 +42,6 @@ def parse_value(raw: str, name: str, hint: type) -> int | float | str:
|
|
|
42
42
|
|
|
43
43
|
def unwrap_optional(t) -> type:
|
|
44
44
|
origin = getattr(t, '__origin__', None)
|
|
45
|
-
if origin is Union or origin is types.UnionType:
|
|
45
|
+
if origin is Union or origin is types.UnionType or isinstance(t, types.UnionType):
|
|
46
46
|
return next(arg for arg in t.__args__ if arg is not type(None))
|
|
47
47
|
return t
|
|
@@ -8,6 +8,7 @@ class Operation(Protocol):
|
|
|
8
8
|
year: int | None
|
|
9
9
|
doy: int
|
|
10
10
|
relative_doy: bool
|
|
11
|
+
resolved_doy: int | None
|
|
11
12
|
|
|
12
13
|
@dataclass(kw_only=True)
|
|
13
14
|
class Planting(Operation):
|
|
@@ -33,6 +34,7 @@ class Planting(Operation):
|
|
|
33
34
|
harvest_timing: float = -999
|
|
34
35
|
kill_after_harvest: int = 1
|
|
35
36
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
37
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
36
38
|
|
|
37
39
|
@dataclass(kw_only=True)
|
|
38
40
|
class Tillage(Operation):
|
|
@@ -43,6 +45,7 @@ class Tillage(Operation):
|
|
|
43
45
|
frac_thermal_time: float = 0.0
|
|
44
46
|
kill_efficiency: float = 0.0
|
|
45
47
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
48
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
46
49
|
|
|
47
50
|
@dataclass(kw_only=True)
|
|
48
51
|
class Harvest(Operation):
|
|
@@ -53,6 +56,7 @@ class Harvest(Operation):
|
|
|
53
56
|
frac_thermal_time: float = 0.0
|
|
54
57
|
kill_efficiency: float = 0.0
|
|
55
58
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
59
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
56
60
|
|
|
57
61
|
@dataclass(kw_only=True)
|
|
58
62
|
class Kill(Operation):
|
|
@@ -63,6 +67,7 @@ class Kill(Operation):
|
|
|
63
67
|
frac_thermal_time: float = 0.0
|
|
64
68
|
kill_efficiency: float = 0.0
|
|
65
69
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
70
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
66
71
|
|
|
67
72
|
@dataclass(kw_only=True)
|
|
68
73
|
class FixedFertilization(Operation):
|
|
@@ -74,6 +79,7 @@ class FixedFertilization(Operation):
|
|
|
74
79
|
method: str = 'Broadcast'
|
|
75
80
|
depth: float = 0.0
|
|
76
81
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
82
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
77
83
|
|
|
78
84
|
@dataclass(kw_only=True)
|
|
79
85
|
class FixedIrrigation(Operation):
|
|
@@ -81,6 +87,7 @@ class FixedIrrigation(Operation):
|
|
|
81
87
|
doy: int
|
|
82
88
|
volume: float = 0.0
|
|
83
89
|
relative_doy: bool = field(default=False, metadata={'readable': False})
|
|
90
|
+
resolved_doy: int | None = field(default=None, metadata={'readable': False})
|
|
84
91
|
|
|
85
92
|
@dataclass(kw_only=True)
|
|
86
93
|
class AutoIrrigation:
|
|
@@ -99,6 +106,13 @@ OPERATION_PARAMETERS = {
|
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
|
|
109
|
+
def _resolve_reference_doy(operations: list[Operation], relative_doy: int) -> int:
|
|
110
|
+
planting = next((op for op in reversed(operations) if isinstance(op, Planting)), None)
|
|
111
|
+
if planting is None:
|
|
112
|
+
raise ValueError("Relative DOY specified but no planting operation found.")
|
|
113
|
+
return planting.doy + relative_doy
|
|
114
|
+
|
|
115
|
+
|
|
102
116
|
def read_operation_file(operation: str | Path) -> list:
|
|
103
117
|
"""Parse a Cycles operation file into operation objects.
|
|
104
118
|
|
|
@@ -137,6 +151,7 @@ def read_operation_file(operation: str | Path) -> list:
|
|
|
137
151
|
if isinstance(raw_doy, str) and raw_doy.split()[1].strip().startswith('+'):
|
|
138
152
|
operation_dict['doy'] = int(raw_doy.split()[1].strip()[1:])
|
|
139
153
|
operation_dict['relative_doy'] = True
|
|
154
|
+
operation_dict['resolved_doy'] = _resolve_reference_doy(operations, operation_dict['doy'])
|
|
140
155
|
else:
|
|
141
156
|
operation_dict['doy'] = parse_value(raw_doy, 'doy', hints['doy'])
|
|
142
157
|
operation_dict['relative_doy'] = False
|
|
@@ -150,30 +150,34 @@ def plot_operations(operations: list, rotation_size: int, *, axs: Axes | np.ndar
|
|
|
150
150
|
for key, value in OPERATION_TYPES.items():
|
|
151
151
|
sub_list = [op for op in operations if type(op).__name__.lower() == key and op.year == y + 1]
|
|
152
152
|
|
|
153
|
-
if
|
|
153
|
+
if not sub_list: continue
|
|
154
154
|
|
|
155
155
|
axs[y].plot(
|
|
156
|
-
[op.doy for op in sub_list], [value.yloc] * len(sub_list),
|
|
156
|
+
[op.doy if not op.relative_doy else op.resolved_doy for op in sub_list], [value.yloc] * len(sub_list),
|
|
157
157
|
'o',
|
|
158
|
-
label=value.title + ':\n' + '\n'.join(f'{op.doy}: {getattr(op, value.label)}' if value.label is not None else f'{op.doy}' for op in sub_list),
|
|
158
|
+
label=value.title + ':\n' + '\n'.join(f'{op.doy if not op.relative_doy else op.resolved_doy}: {getattr(op, value.label)}' if value.label is not None else f'{op.doy}' for op in sub_list),
|
|
159
159
|
color=value.color,
|
|
160
160
|
ms=10,
|
|
161
161
|
)
|
|
162
162
|
|
|
163
|
+
if key == 'planting':
|
|
164
|
+
for op in sub_list:
|
|
165
|
+
if op.end_doy == -999: continue
|
|
166
|
+
axs[y].fill_betweenx([value.yloc - 0.5, value.yloc + 0.5], op.doy, op.end_doy, alpha=0.25, color=value.color)
|
|
167
|
+
|
|
163
168
|
axs[y].set_xlim(-1, 370)
|
|
164
169
|
axs[y].grid(False)
|
|
165
|
-
axs[y].spines['right'].set_color('none')
|
|
166
|
-
axs[y].spines['left'].set_color('none')
|
|
167
170
|
axs[y].yaxis.set_ticks_position('none')
|
|
168
171
|
axs[y].yaxis.set_tick_params(left=False, right=False, which='both', labelleft=False)
|
|
169
172
|
axs[y].set_ylim(-3, 7)
|
|
170
173
|
axs[y].text(184, 5, f'Year {y + 1}', ha='center')
|
|
174
|
+
for spine in ('right', 'left', 'top'):
|
|
175
|
+
axs[y].spines[spine].set_color('none')
|
|
171
176
|
|
|
172
177
|
# set the y-spine
|
|
173
178
|
axs[y].spines['bottom'].set_position('zero')
|
|
174
179
|
|
|
175
180
|
# turn off the top spine/ticks
|
|
176
|
-
axs[y].spines['top'].set_color('none')
|
|
177
181
|
axs[y].xaxis.tick_bottom()
|
|
178
182
|
axs[y].set_xticks(MDOYS)
|
|
179
183
|
axs[y].set_xticklabels(MONTHS)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import geopandas as gpd
|
|
3
|
+
import matplotlib.colors as colors
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import numpy as np
|
|
3
6
|
import pandas as pd
|
|
4
7
|
import shapely
|
|
5
8
|
from dataclasses import dataclass
|
|
9
|
+
from matplotlib.axes import Axes
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
from shapely.geometry import Point
|
|
8
12
|
from cycles.cycles_tools import generate_soil_file as _generate_soil_file
|
|
@@ -65,11 +69,29 @@ SSURGO_PARAMETERS: dict[str, SsurgoParameter] = {
|
|
|
65
69
|
|
|
66
70
|
LatLon = tuple[float, float]
|
|
67
71
|
|
|
72
|
+
|
|
73
|
+
class MapUnitGeoDataFrame(gpd.GeoDataFrame):
|
|
74
|
+
def plot(self, **kwargs) -> Axes:
|
|
75
|
+
n = len(self)
|
|
76
|
+
kwargs.setdefault('column', 'musym')
|
|
77
|
+
kwargs.setdefault('legend', True)
|
|
78
|
+
kwargs.setdefault(
|
|
79
|
+
'cmap',
|
|
80
|
+
'tab20' if n > 20 else _truncate_colormap(plt.get_cmap('tab20'), 0, n / 20),
|
|
81
|
+
)
|
|
82
|
+
ax = super().plot(**kwargs)
|
|
83
|
+
ax.axis('off')
|
|
84
|
+
ax.set_aspect('equal')
|
|
85
|
+
return ax
|
|
86
|
+
|
|
87
|
+
|
|
68
88
|
class Ssurgo:
|
|
69
89
|
"""Load SSURGO lookup tables and extract soil profiles for locations."""
|
|
70
90
|
|
|
71
91
|
def __init__(self, path: str | Path, state: str, *, lat_lon: LatLon | None=None, boundary: gpd.GeoDataFrame | None=None) -> None:
|
|
72
92
|
"""Initialize SSURGO lookup tables and optional spatial subset.
|
|
93
|
+
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;
|
|
94
|
+
the map-units are also grouped by name and symbol, and the major map unit is selected for profile extraction.
|
|
73
95
|
|
|
74
96
|
Args:
|
|
75
97
|
path: Directory containing SSURGO geodatabase and lookup CSV files.
|
|
@@ -86,10 +108,10 @@ class Ssurgo:
|
|
|
86
108
|
_validate_geographic_input(lat_lon, boundary)
|
|
87
109
|
|
|
88
110
|
self.state: str = state
|
|
89
|
-
self._mapunits: gpd.GeoDataFrame | pd.DataFrame
|
|
90
|
-
self.
|
|
91
|
-
self.
|
|
92
|
-
self.
|
|
111
|
+
self._mapunits: gpd.GeoDataFrame | pd.DataFrame
|
|
112
|
+
self.components: pd.DataFrame
|
|
113
|
+
self.horizons: pd.DataFrame
|
|
114
|
+
self.grouped_mapunits: MapUnitGeoDataFrame | None = None
|
|
93
115
|
self.mukey: int | None = None
|
|
94
116
|
self.slope: float | None = None
|
|
95
117
|
self.hsg: str = ''
|
|
@@ -111,21 +133,25 @@ class Ssurgo:
|
|
|
111
133
|
)
|
|
112
134
|
gdf = _read_mupolygon(path, state, boundary)
|
|
113
135
|
self._mapunits = gdf.merge(luts['mapunit'], on='mukey', how='left')
|
|
114
|
-
|
|
115
136
|
self.components = self.components[self.components['mukey'].isin(self._mapunits['mukey'].unique())]
|
|
116
137
|
self.horizons = self.horizons[self.horizons['cokey'].isin(self.components['cokey'].unique())]
|
|
117
138
|
|
|
139
|
+
self._group_map_units()
|
|
140
|
+
self._select_major_mapunit()
|
|
118
141
|
self._average_slope_hsg()
|
|
119
142
|
|
|
120
143
|
|
|
121
144
|
@property
|
|
122
|
-
def mapunits(self) ->
|
|
145
|
+
def mapunits(self) -> MapUnitGeoDataFrame | pd.DataFrame:
|
|
123
146
|
"""Return loaded map-unit table.
|
|
124
147
|
|
|
125
148
|
Returns:
|
|
126
149
|
Map-unit table as GeoDataFrame/DataFrame, or None if not loaded.
|
|
127
150
|
"""
|
|
128
|
-
|
|
151
|
+
if isinstance(self._mapunits, gpd.GeoDataFrame):
|
|
152
|
+
return MapUnitGeoDataFrame(self._mapunits, geometry=self._mapunits.geometry.name, crs=self._mapunits.crs)
|
|
153
|
+
else:
|
|
154
|
+
return self._mapunits
|
|
129
155
|
|
|
130
156
|
|
|
131
157
|
@property
|
|
@@ -152,39 +178,22 @@ class Ssurgo:
|
|
|
152
178
|
return self._mapunits[self._mapunits['mukey'] == self.mukey]['musym'].iloc[0]
|
|
153
179
|
|
|
154
180
|
|
|
155
|
-
def
|
|
156
|
-
"""Group map units by base soil-series name.
|
|
157
|
-
|
|
158
|
-
Many map units share a base soil texture but differ in slope class or
|
|
159
|
-
minor qualifiers. This method groups by the base ``muname`` text before
|
|
160
|
-
the first comma and optionally dissolves geometries.
|
|
161
|
-
|
|
162
|
-
Args:
|
|
163
|
-
geometry: If True, dissolve polygons by grouped map-unit name.
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
None.
|
|
167
|
-
"""
|
|
168
|
-
if self.grouped_mapunits is not None:
|
|
169
|
-
return
|
|
170
|
-
assert self._mapunits is not None
|
|
171
|
-
|
|
181
|
+
def _group_map_units(self) -> None:
|
|
172
182
|
gmu = self._mapunits.copy()
|
|
173
183
|
gmu['muname'] = gmu['muname'].map(lambda name: name.split(',')[0]) # type: ignore
|
|
174
|
-
gmu['musym']
|
|
184
|
+
gmu['musym'] = gmu['musym'].map(_strip_slope_suffix)
|
|
175
185
|
|
|
176
186
|
mask = self.non_soil_mask(gmu)
|
|
177
187
|
gmu.loc[mask, 'muname'] = 'Water, urban, etc.'
|
|
178
188
|
gmu.loc[mask, 'mukey'] = -999
|
|
179
189
|
gmu.loc[mask, 'musym'] = 'N/A'
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
).reset_index() # type: ignore
|
|
191
|
+
gmu = gmu.dissolve(
|
|
192
|
+
by='muname',
|
|
193
|
+
aggfunc={'mukey': 'first', 'musym': 'first', 'shape_area': 'sum'},
|
|
194
|
+
).reset_index() # type: ignore
|
|
186
195
|
|
|
187
|
-
self.grouped_mapunits = gmu
|
|
196
|
+
self.grouped_mapunits = MapUnitGeoDataFrame(gmu, geometry=gmu.geometry.name, crs=gmu.crs)
|
|
188
197
|
|
|
189
198
|
|
|
190
199
|
def non_soil_mask(self, mapunits: pd.DataFrame | gpd.GeoDataFrame) -> pd.Series:
|
|
@@ -203,18 +212,8 @@ class Ssurgo:
|
|
|
203
212
|
)
|
|
204
213
|
|
|
205
214
|
|
|
206
|
-
def
|
|
207
|
-
"""Select the dominant map unit by area.
|
|
208
|
-
|
|
209
|
-
Returns:
|
|
210
|
-
None.
|
|
211
|
-
"""
|
|
212
|
-
if self.mukey is not None:
|
|
213
|
-
return
|
|
214
|
-
if self.grouped_mapunits is None:
|
|
215
|
-
self.group_map_units(geometry=True)
|
|
215
|
+
def _select_major_mapunit(self) -> None:
|
|
216
216
|
assert self.grouped_mapunits is not None
|
|
217
|
-
|
|
218
217
|
gdf = self.grouped_mapunits[~self.non_soil_mask(self.grouped_mapunits)].copy()
|
|
219
218
|
gdf['area'] = gdf.area
|
|
220
219
|
self.mukey = int(gdf.loc[gdf['area'].idxmax(), 'mukey']) # type: ignore
|
|
@@ -231,7 +230,6 @@ class Ssurgo:
|
|
|
231
230
|
Soil profile as a list of ``SoilLayer`` records.
|
|
232
231
|
"""
|
|
233
232
|
mukey = mukey or self._ensure_mukey()
|
|
234
|
-
assert self.components is not None and self.horizons is not None
|
|
235
233
|
|
|
236
234
|
df = self.components[self.components['mukey'] == int(mukey)].copy()
|
|
237
235
|
if major_only:
|
|
@@ -261,19 +259,15 @@ class Ssurgo:
|
|
|
261
259
|
Returns:
|
|
262
260
|
None.
|
|
263
261
|
"""
|
|
264
|
-
if mukey is None:
|
|
265
|
-
self.
|
|
266
|
-
self.
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
262
|
+
if mukey is not None:
|
|
263
|
+
hsg = self._mapunits[self._mapunits['mukey'] == mukey]['hydgrpdcd'].iloc[0] if hsg is None else hsg
|
|
264
|
+
slope = self._mapunits[self._mapunits['mukey'] == mukey]['slopegradwta'].iloc[0] if slope is None else slope
|
|
265
|
+
else:
|
|
266
|
+
mukey = self._ensure_mukey()
|
|
267
|
+
hsg = self.hsg if hsg is None else hsg
|
|
268
|
+
slope = self.slope if slope is None else slope
|
|
271
269
|
|
|
272
|
-
|
|
273
|
-
hsg = self._mapunits[self._mapunits['mukey'] == mukey]['hydgrpdcd'].iloc[0] if not self.hsg else self.hsg
|
|
274
|
-
assert hsg is not None
|
|
275
|
-
if slope is None:
|
|
276
|
-
slope = self._mapunits[self._mapunits['mukey'] == mukey]['slopegradwta'].iloc[0] if not self.slope else self.slope
|
|
270
|
+
assert mukey is not None and hsg is not None and slope is not None
|
|
277
271
|
|
|
278
272
|
profile = self.get_soil_profile(mukey=mukey)
|
|
279
273
|
desc = desc if desc is not None else _build_desc(self._get_muname(mukey), mukey, hsg)
|
|
@@ -281,20 +275,15 @@ class Ssurgo:
|
|
|
281
275
|
|
|
282
276
|
|
|
283
277
|
def _ensure_mukey(self) -> int:
|
|
284
|
-
|
|
285
|
-
self.select_major_mapunit()
|
|
286
|
-
assert self.mukey is not None
|
|
278
|
+
assert self.mukey is not None, "A major soil map unit has not been selected, because a location or boundary has not been provided."
|
|
287
279
|
return self.mukey
|
|
288
280
|
|
|
289
281
|
|
|
290
282
|
def _get_muname(self, mukey: int) -> str:
|
|
291
|
-
assert self._mapunits is not None
|
|
292
283
|
return self._mapunits[self._mapunits['mukey'] == mukey]['muname'].iloc[0]
|
|
293
284
|
|
|
294
285
|
|
|
295
286
|
def _average_slope_hsg(self) -> None:
|
|
296
|
-
assert self._mapunits is not None
|
|
297
|
-
|
|
298
287
|
gdf = self._mapunits[~self.non_soil_mask(self._mapunits)].copy()
|
|
299
288
|
gdf['area'] = gdf.area
|
|
300
289
|
|
|
@@ -302,6 +291,13 @@ class Ssurgo:
|
|
|
302
291
|
self.hsg = _dominant_hsg(gdf) # type: ignore
|
|
303
292
|
|
|
304
293
|
|
|
294
|
+
def _truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
|
|
295
|
+
return colors.LinearSegmentedColormap.from_list(
|
|
296
|
+
'trunc({n},{a:.2f},{b:.2f})'.format(n=cmap.name, a=minval, b=maxval),
|
|
297
|
+
cmap(np.linspace(minval, maxval, n))
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
305
301
|
def _validate_geographic_input(lat_lon: LatLon | None, boundary: gpd.GeoDataFrame | None) -> None:
|
|
306
302
|
if lat_lon is not None and boundary is not None:
|
|
307
303
|
raise ValueError("lat_lon and boundary are mutually exclusive — provide only one.")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|