Metadata-Version: 2.4
Name: soil-aggregation-tool
Version: 0.1.0
Summary: Aggregate SWAT usersoil classes and reclassify a matching soil raster.
Author: Yashas Kumar
Maintainer-email: Chandan Kumar <chandankr014@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/chandankr014/swatplus-soil-aggregation-tool
Project-URL: Issues, https://github.com/chandankr014/swatplus-soil-aggregation-tool/issues
Keywords: SWAT,SWAT+,soil,hydrology,raster,clustering
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Scientific/Engineering :: Hydrology
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: rasterio>=1.3
Requires-Dist: scikit-learn>=1.3
Dynamic: license-file

# Soil Aggregation Tool

`Soil_aggregation_tool` is an installable Python package for aggregating SWAT
usersoil classes and reclassifying a matching soil raster. It groups comparable
soils, selects an appropriate number of k-means clusters, and produces a smaller
usersoil table, a matching raster, and a two-column lookup table.

Developed by Yashas Kumar. Packaged and maintained by
[chandankr014](https://github.com/chandankr014).

## Outputs

Each run creates exactly these three files:

| Output file | Contents |
| --- | --- |
| `Soil_usersoil.csv` | Aggregated usersoil table with the same columns as the input usersoil CSV. |
| `Soil_lookup.csv` | Lookup containing only `MUID` and `SNAM`, copied from `Soil_usersoil.csv`. |
| `Soil.tif` | Input soil raster reclassified to the new aggregated MUID values. |

Generated soil names are `Soil_1`, `Soil_2`, `Soil_3`, and so on. Therefore,
the `MUID` and `SNAM` pairs in `Soil_lookup.csv` always match those in
`Soil_usersoil.csv`.

The program does not delete unrelated files already present in the output
folder. For a folder containing only these three files, use a new or empty
output folder.

## How the tool works

1. It reads the unique MUID values and cell areas from the input soil raster.
2. It selects usersoil rows whose `MUID` values occur in that raster.
3. It divides the selected soils into comparable strata using `HYDGRP`,
   `TEXTURE`, `SOL_ZMX`, and `NLAYERS`.
4. Within each stratum, it clusters soils using `SOL_K1`, `SOL_AWC1`,
   `SOL_BD1`, and `USLE_K1`.
5. It compares candidate cluster counts using the silhouette score.
6. It aggregates each selected cluster into one usersoil row and assigns new
   sequential MUIDs beginning with `1`.
7. It reclassifies the raster from the original MUID values to the new MUIDs.
8. It copies `MUID` and `SNAM` from the output usersoil into the lookup file.

## Input requirements

### Usersoil CSV

The input CSV must contain these columns:

| Column | Use |
| --- | --- |
| `MUID` | Soil identifier corresponding to raster cell values. |
| `SNAM` | Soil name. Output names are replaced with `Soil_1`, `Soil_2`, etc. |
| `HYDGRP` | Hydrologic soil group used to define strata. |
| `TEXTURE` | Soil texture used to define strata. |
| `SOL_ZMX` | Maximum soil depth used to define strata. |
| `NLAYERS` | Number of soil layers used to define strata. |
| `SOL_K1` | Layer-one saturated hydraulic conductivity used for clustering. |
| `SOL_AWC1` | Layer-one available water capacity used for clustering. |
| `SOL_BD1` | Layer-one bulk density used for clustering. |
| `USLE_K1` | Layer-one soil erodibility factor used for clustering. |

All other input usersoil columns are retained in `Soil_usersoil.csv`. If the
input includes every column from `SOL_Z1` through `SOL_Z10`, the tool corrects
an inconsistent `NLAYERS` value from the number of positive layer depths.

### Soil raster

The raster must:

- be a single-band GeoTIFF;
- contain cell values corresponding to `MUID` values in the usersoil CSV; and
- contain valid raster dimensions, transform, and coordinate-system metadata.

Values `0` and `65535` are treated as background or no-data by default. Raster
MUIDs that do not occur in the usersoil CSV are written as `0` in `Soil.tif`
and reported in the terminal.

## Software requirements

- Python 3.10 or newer
- NumPy
- pandas
- Rasterio
- scikit-learn

Python installs the package dependencies from `pyproject.toml`.

## Download and install from GitHub

First, install Git and Python 3.10 or newer. Then follow these steps.

### 1. Download the repository

Open PowerShell or a terminal and run:

```powershell
git clone https://github.com/chandankr014/swatplus-soil-aggregation-tool.git
```

### 2. Enter the downloaded folder

```powershell
cd swatplus-soil-aggregation-tool
```

### 3. Create a virtual environment

```powershell
python -m venv .venv
```

Activate it on Windows PowerShell:

```powershell
.\.venv\Scripts\Activate.ps1
```

On macOS or Linux, activate it with:

```bash
source .venv/bin/activate
```

### 4. Install the package

```powershell
python -m pip install .
```

The required Python packages are installed automatically. Confirm that the
installation succeeded:

```powershell
soil-aggregation-tool --help
```

### Install directly without cloning

The package can also be installed directly from GitHub:

```powershell
python -m pip install "git+https://github.com/chandankr014/swatplus-soil-aggregation-tool.git"
```

### Install in Jupyter Notebook

Run this command in a notebook cell to install the package into the environment
used by the current notebook kernel:

```python
%pip install "git+https://github.com/chandankr014/swatplus-soil-aggregation-tool.git"
```

Restart the notebook kernel after installation. The Python API can then be used
directly in another cell:

```python
from soil_aggregation_tool import aggregate_soils

result = aggregate_soils(
    usersoil_path="usersoil.csv",
    raster_path="soil_input.tif",
    output_dir="soil_output",
)

print(result.usersoil_path)
print(result.lookup_path)
print(result.raster_path)
```

## Running with interactive input

Run the command without file arguments:

```powershell
soil-aggregation-tool
```

The program asks for the inputs as follows:

```text
Usersoil CSV path:
Soil raster path:
Output folder path:
```

Enter a complete or relative path after each prompt. The prompts do not use
default filenames or square brackets.

Example:

```text
Usersoil CSV path: D:\data\usersoil.csv
Soil raster path: D:\data\soil_input.tif
Output folder path: D:\data\soil_output
```

## Running with command-line arguments

File paths can be supplied directly to avoid interactive prompts:

```powershell
soil-aggregation-tool `
  --usersoil "D:\data\usersoil.csv" `
  --raster "D:\data\soil_input.tif" `
  --output-dir "D:\data\soil_output"
```

The Python module form is equivalent:

```powershell
python -m soil_aggregation_tool `
  --usersoil "D:\data\usersoil.csv" `
  --raster "D:\data\soil_input.tif" `
  --output-dir "D:\data\soil_output"
```

Paths are quoted so paths containing spaces work correctly. The output folder
is created automatically when it does not exist.

### Optional settings

| Argument | Default | Description |
| --- | ---: | --- |
| `--max-optimal-clusters` | `15` | Maximum number of clusters evaluated within each soil stratum. |
| `--seed` | `123` | Random seed that makes k-means results reproducible. |
| `--ignore-raster-values` | `0 65535` | Space-separated raster values treated as background. |

Example using `0` and `-9999` as ignored values:

```powershell
soil-aggregation-tool `
  --usersoil "D:\data\usersoil.csv" `
  --raster "D:\data\soil_input.tif" `
  --output-dir "D:\data\soil_output" `
  --ignore-raster-values 0 -9999
```

## Using the package from Python

```python
from soil_aggregation_tool import aggregate_soils

result = aggregate_soils(
    usersoil_path="usersoil.csv",
    raster_path="soil_input.tif",
    output_dir="soil_output",
)

print(result.usersoil_path)
print(result.lookup_path)
print(result.raster_path)
print(result.aggregated_soil_count)
```

## Running the tests

Run the automated test from the repository folder:

```powershell
python -m unittest discover -s tests -v
```

The tests create a small usersoil CSV and GeoTIFF, run the complete workflow,
and check that:

- only the three documented output files are created;
- `Soil_lookup.csv` has only the `MUID` and `SNAM` columns;
- lookup values exactly match `Soil_usersoil.csv`;
- the new MUID values are written into `Soil.tif`;
- raster MUIDs missing from the usersoil CSV are written as `0`; and
- a missing required column raises a clear error.

The same tests run in GitHub Actions before each release is published.

## Publishing a release to PyPI

Pushing a tag that starts with `v` runs `.github/workflows/publish.yml`, which
tests, builds, and uploads the package to PyPI. The package version is taken
from the tag, so there is no version number to edit by hand.

One-time setup: create a PyPI API token and add it on GitHub under
**Settings → Secrets and variables → Actions** as `PYPI_API_TOKEN`.

To release a new version:

```powershell
git add .
git commit -m "Release v0.1.0"
git push origin main
git tag v0.1.0
git push origin v0.1.0
```

Check progress in the repository's **Actions** tab. After it finishes, install
with:

```powershell
pip install --no-cache-dir soil-aggregation-tool
```

## License

Released under the MIT License. See [LICENSE](LICENSE).

## Repository structure

```text
Soil_aggregation_tool/
|-- .github/
|   `-- workflows/
|       `-- publish.yml
|-- .gitignore
|-- LICENSE
|-- README.md
|-- pyproject.toml
|-- src/
|   `-- soil_aggregation_tool/
|       |-- __init__.py
|       |-- __main__.py
|       |-- cli.py
|       `-- core.py
`-- tests/
    `-- test_aggregation.py
```

Large CSV and TIFF datasets are excluded by `.gitignore`. Keep input data
outside the repository, or check that it is not staged before committing.

## Common errors

- **Usersoil CSV not found:** check the path entered at `Usersoil CSV path` or
  supplied to `--usersoil`.
- **Soil raster not found:** check the path entered at `Soil raster path` or
  supplied to `--raster`.
- **Missing required columns:** add or rename the columns listed in the usersoil
  input section.
- **No raster MUID values matched:** confirm that raster cell values and the
  usersoil `MUID` column use the same identifiers.
- **Output file is open:** close the file in other software and run the tool
  again. When an output is locked, the tool warns and uses a timestamped
  filename rather than overwriting the open file.
- **Arguments are required when input is not interactive:** when running from
  a script or scheduled job, pass `--usersoil`, `--raster`, and `--output-dir`.
