def create_parser():
"""Create the argument parser with all subcommands."""
parser = argparse.ArgumentParser(
prog='pycropwat',
description='pyCropWat - Calculate effective precipitation from GEE climate data',
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
'--version',
action='version',
version='%(prog)s 1.2'
)
parser.add_argument(
'--list-methods',
action='store_true',
help='List available effective precipitation methods and exit'
)
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# =========================================================================
# PROCESS subcommand
# =========================================================================
process_parser = subparsers.add_parser(
'process',
help='Calculate effective precipitation from GEE climate data',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
pycropwat process --asset ECMWF/ERA5_LAND/MONTHLY_AGGR \\
--band total_precipitation_sum \\
--gee-geometry projects/my-project/assets/roi \\
--start-year 2020 --end-year 2023 \\
--scale-factor 1000 --output ./output
pycropwat process --asset IDAHO_EPSCOR/TERRACLIMATE --band pr \\
--geometry roi.geojson \\
--start-year 2015 --end-year 2020 \\
--method fao_aglw --output ./output
# PCML method (Western U.S. only - no asset/band/geometry required)
pycropwat process --method pcml --start-year 2020 --end-year 2024 \\
--output ./WesternUS_PCML --workers 8
"""
)
process_parser.add_argument('--asset', '-a', help='GEE ImageCollection asset ID (not required for PCML method)')
process_parser.add_argument('--band', '-b', help='Precipitation band name (not required for PCML method)')
process_parser.add_argument('--geometry', '-g', type=str, help='Path to shapefile or GeoJSON')
process_parser.add_argument('--gee-geometry', '-G', type=str, help='GEE FeatureCollection asset ID')
process_parser.add_argument('--start-year', '-s', required=True, type=int, help='Start year')
process_parser.add_argument('--end-year', '-e', required=True, type=int, help='End year')
process_parser.add_argument('--output', '-o', required=True, type=Path, help='Output directory')
process_parser.add_argument('--scale-factor', '-f', type=float, default=1.0, help='Precipitation scale factor')
process_parser.add_argument('--scale', '-r', type=float, help='Output resolution in meters')
process_parser.add_argument('--workers', '-w', type=int, default=4, help='Number of parallel workers')
process_parser.add_argument('--months', '-m', type=int, nargs='+', help='Specific months to process (1-12)')
process_parser.add_argument('--project', '-p', type=str, help='GEE project ID')
process_parser.add_argument('--method', type=str, default='ensemble',
choices=['cropwat', 'fao_aglw', 'fixed_percentage', 'dependable_rainfall', 'farmwest', 'usda_scs', 'suet', 'pcml', 'ensemble'],
help='Effective precipitation method (pcml uses default PCML asset for Western U.S.)')
process_parser.add_argument('--percentage', type=float, default=0.7, help='Percentage for fixed_percentage method')
process_parser.add_argument('--probability', type=float, default=0.75, help='Probability for dependable_rainfall method')
# USDA-SCS method parameters
process_parser.add_argument('--awc-asset', type=str,
help='GEE AWC asset for usda_scs/ensemble method. U.S.: projects/openet/soil/ssurgo_AWC_WTA_0to152cm_composite, Global: projects/sat-io/open-datasets/FAO/HWSD_V2_SMU')
process_parser.add_argument('--awc-band', type=str, default=None,
help='AWC band name. Omit for SSURGO (single-band), use "AWC" for HWSD')
process_parser.add_argument('--eto-asset', type=str,
help='GEE ETo asset for usda_scs/suet methods. U.S.: projects/openet/assets/reference_et/conus/gridmet/monthly/v1, Global: projects/climate-engine-pro/assets/ce-ag-era5-v2/daily')
process_parser.add_argument('--eto-band', type=str, default='eto',
help='ETo band name. GridMET: "eto", ERA5: "ReferenceET_PenmanMonteith_FAO56"')
process_parser.add_argument('--eto-is-daily', action='store_true',
help='Set if ETo asset is daily (will aggregate to monthly)')
process_parser.add_argument('--rooting-depth', type=float, default=1.0,
help='Crop rooting depth in meters for usda_scs method (default: 1.0)')
process_parser.add_argument('--mad-factor', type=float, default=0.5,
help='Management Allowed Depletion factor (0-1) for usda_scs method (default: 0.5)')
process_parser.add_argument('--sequential', action='store_true', help='Process sequentially')
add_common_args(process_parser)
process_parser.set_defaults(func=cmd_process)
# =========================================================================
# AGGREGATE subcommand
# =========================================================================
agg_parser = subparsers.add_parser(
'aggregate',
help='Temporal aggregation of effective precipitation rasters',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Annual total
pycropwat aggregate --input ./output --type annual --year 2020 --output ./annual_2020.tif
# Seasonal (summer)
pycropwat aggregate --input ./output --type seasonal --year 2020 --season JJA --output ./summer_2020.tif
# Growing season (April-October)
pycropwat aggregate --input ./output --type growing-season --year 2020 \\
--start-month 4 --end-month 10 --output ./growing_2020.tif
# Multi-year climatology
pycropwat aggregate --input ./output --type climatology \\
--start-year 2000 --end-year 2020 --output ./climatology/
"""
)
agg_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory with monthly rasters')
agg_parser.add_argument('--type', '-t', required=True,
choices=['annual', 'seasonal', 'growing-season', 'custom', 'climatology'],
help='Aggregation type')
agg_parser.add_argument('--year', '-y', type=int, help='Year to aggregate')
agg_parser.add_argument('--start-year', type=int, help='Start year (for climatology)')
agg_parser.add_argument('--end-year', type=int, help='End year (for climatology)')
agg_parser.add_argument('--season', choices=['DJF', 'MAM', 'JJA', 'SON'], help='Season code')
agg_parser.add_argument('--start-month', type=int, default=4, help='Growing season start month')
agg_parser.add_argument('--end-month', type=int, default=10, help='Growing season end month')
agg_parser.add_argument('--months', '-m', type=int, nargs='+', help='Specific months for custom aggregation')
agg_parser.add_argument('--method', default='sum', choices=['sum', 'mean', 'min', 'max', 'std'],
help='Aggregation method')
agg_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
agg_parser.add_argument('--output', '-o', required=True, help='Output file or directory')
add_common_args(agg_parser)
agg_parser.set_defaults(func=cmd_aggregate)
# =========================================================================
# ANALYZE subcommand
# =========================================================================
analyze_parser = subparsers.add_parser(
'analyze',
help='Statistical analysis of effective precipitation data',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Calculate anomaly
pycropwat analyze anomaly --input ./output --year 2020 --month 6 \\
--clim-start 1990 --clim-end 2020 --output ./anomaly_2020_06.tif
# Calculate trend
pycropwat analyze trend --input ./output --start-year 2000 --end-year 2020 \\
--method sen --output ./trend/
# Zonal statistics
pycropwat analyze zonal --input ./output --zones ./regions.shp \\
--start-year 2000 --end-year 2020 --output ./zonal_stats.csv
"""
)
analyze_subparsers = analyze_parser.add_subparsers(dest='analysis_type', help='Analysis type')
# Anomaly
anomaly_parser = analyze_subparsers.add_parser('anomaly', help='Calculate anomaly relative to climatology')
anomaly_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
anomaly_parser.add_argument('--year', '-y', required=True, type=int, help='Target year')
anomaly_parser.add_argument('--month', '-m', required=True, type=int, help='Target month (1-12)')
anomaly_parser.add_argument('--clim-start', required=True, type=int, help='Climatology start year')
anomaly_parser.add_argument('--clim-end', required=True, type=int, help='Climatology end year')
anomaly_parser.add_argument('--anomaly-type', default='absolute',
choices=['absolute', 'percent', 'standardized'],
help='Type of anomaly')
anomaly_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
anomaly_parser.add_argument('--output', '-o', required=True, help='Output file')
add_common_args(anomaly_parser)
anomaly_parser.set_defaults(func=cmd_analyze)
# Trend
trend_parser = analyze_subparsers.add_parser('trend', help='Calculate temporal trend')
trend_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
trend_parser.add_argument('--start-year', required=True, type=int, help='Start year')
trend_parser.add_argument('--end-year', required=True, type=int, help='End year')
trend_parser.add_argument('--month', '-m', type=int, help='Specific month (or annual if omitted)')
trend_parser.add_argument('--trend-method', default='linear', choices=['linear', 'sen'],
help='Trend method (linear or Theil-Sen)')
trend_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
trend_parser.add_argument('--output', '-o', required=True, help='Output directory')
add_common_args(trend_parser)
trend_parser.set_defaults(func=cmd_analyze)
# Zonal
zonal_parser = analyze_subparsers.add_parser('zonal', help='Calculate zonal statistics')
zonal_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
zonal_parser.add_argument('--zones', '-z', required=True, help='Path to zones shapefile/GeoJSON')
zonal_parser.add_argument('--start-year', required=True, type=int, help='Start year')
zonal_parser.add_argument('--end-year', required=True, type=int, help='End year')
zonal_parser.add_argument('--months', '-m', type=int, nargs='+', help='Specific months')
zonal_parser.add_argument('--stats', default='mean,sum,min,max,std',
help='Statistics to compute (comma-separated)')
zonal_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
zonal_parser.add_argument('--output', '-o', required=True, help='Output CSV file')
add_common_args(zonal_parser)
zonal_parser.set_defaults(func=cmd_analyze)
# =========================================================================
# EXPORT subcommand
# =========================================================================
export_parser = subparsers.add_parser(
'export',
help='Export data to different formats',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Export to NetCDF
pycropwat export netcdf --input ./output --output ./data.nc
# Convert to Cloud-Optimized GeoTIFF
pycropwat export cog --input ./output/effective_precip_2020_06.tif --output ./cog_2020_06.tif
"""
)
export_subparsers = export_parser.add_subparsers(dest='format', help='Export format')
# NetCDF
netcdf_parser = export_subparsers.add_parser('netcdf', help='Export to NetCDF')
netcdf_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
netcdf_parser.add_argument('--output', '-o', required=True, help='Output NetCDF file')
netcdf_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
netcdf_parser.add_argument('--variable', help='Variable name in NetCDF')
netcdf_parser.add_argument('--no-compression', action='store_true', help='Disable compression')
add_common_args(netcdf_parser)
netcdf_parser.set_defaults(func=cmd_export)
# COG
cog_parser = export_subparsers.add_parser('cog', help='Convert to Cloud-Optimized GeoTIFF')
cog_parser.add_argument('--input', '-i', required=True, help='Input GeoTIFF file')
cog_parser.add_argument('--output', '-o', required=True, help='Output COG file')
add_common_args(cog_parser)
cog_parser.set_defaults(func=cmd_export)
# =========================================================================
# PLOT subcommand
# =========================================================================
plot_parser = subparsers.add_parser(
'plot',
help='Create visualizations',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Time series plot
pycropwat plot timeseries --input ./output --start-year 2000 --end-year 2020 \\
--output ./timeseries.png
# Monthly climatology bar chart
pycropwat plot climatology --input ./output --start-year 2000 --end-year 2020 \\
--output ./climatology.png
# Single month map
pycropwat plot map --input ./output --year 2020 --month 6 --output ./map_2020_06.png
# Interactive map (requires leafmap or folium)
pycropwat plot interactive --input ./output --year 2020 --month 6 --output ./map.html
# Compare two datasets side-by-side
pycropwat plot compare --input ./era5_output --other-input ./terraclimate_output \\
--year 2020 --month 6 --label1 ERA5 --label2 TerraClimate \\
--output ./comparison.png
# Scatter plot comparison
pycropwat plot scatter --input ./era5_output --other-input ./terraclimate_output \\
--start-year 2000 --end-year 2020 --output ./scatter.png
# Annual comparison bar chart
pycropwat plot annual-compare --input ./era5_output --other-input ./terraclimate_output \\
--start-year 2000 --end-year 2020 --output ./annual.png
"""
)
plot_subparsers = plot_parser.add_subparsers(dest='plot_type', help='Plot type')
# Time series
ts_parser = plot_subparsers.add_parser('timeseries', help='Time series plot')
ts_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
ts_parser.add_argument('--start-year', required=True, type=int, help='Start year')
ts_parser.add_argument('--end-year', required=True, type=int, help='End year')
ts_parser.add_argument('--months', '-m', type=int, nargs='+', help='Specific months')
ts_parser.add_argument('--stat', default='mean', choices=['mean', 'sum', 'min', 'max'],
help='Spatial aggregation statistic')
ts_parser.add_argument('--title', help='Plot title')
ts_parser.add_argument('--width', type=int, default=12, help='Figure width in inches')
ts_parser.add_argument('--height', type=int, default=6, help='Figure height in inches')
ts_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
ts_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(ts_parser)
ts_parser.set_defaults(func=cmd_plot)
# Climatology
clim_parser = plot_subparsers.add_parser('climatology', help='Monthly climatology bar chart')
clim_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
clim_parser.add_argument('--start-year', required=True, type=int, help='Start year')
clim_parser.add_argument('--end-year', required=True, type=int, help='End year')
clim_parser.add_argument('--stat', default='mean', choices=['mean', 'sum'],
help='Spatial aggregation statistic')
clim_parser.add_argument('--title', help='Plot title')
clim_parser.add_argument('--width', type=int, default=10, help='Figure width in inches')
clim_parser.add_argument('--height', type=int, default=6, help='Figure height in inches')
clim_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
clim_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(clim_parser)
clim_parser.set_defaults(func=cmd_plot)
# Map
map_parser = plot_subparsers.add_parser('map', help='Single raster map')
map_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
map_parser.add_argument('--year', '-y', required=True, type=int, help='Year')
map_parser.add_argument('--month', '-m', required=True, type=int, help='Month (1-12)')
map_parser.add_argument('--cmap', default='YlGnBu', help='Colormap name')
map_parser.add_argument('--title', help='Plot title')
map_parser.add_argument('--vmin', type=float, help='Minimum color scale value')
map_parser.add_argument('--vmax', type=float, help='Maximum color scale value')
map_parser.add_argument('--width', type=int, default=10, help='Figure width in inches')
map_parser.add_argument('--height', type=int, default=8, help='Figure height in inches')
map_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
map_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(map_parser)
map_parser.set_defaults(func=cmd_plot)
# Interactive map
interactive_parser = plot_subparsers.add_parser('interactive', help='Interactive map (HTML)')
interactive_parser.add_argument('--input', '-i', required=True, type=Path, help='Input directory')
interactive_parser.add_argument('--year', '-y', required=True, type=int, help='Year')
interactive_parser.add_argument('--month', '-m', required=True, type=int, help='Month (1-12)')
interactive_parser.add_argument('--cmap', default='YlGnBu', help='Colormap name')
interactive_parser.add_argument('--title', help='Map title')
interactive_parser.add_argument('--zoom', type=int, default=6, help='Initial zoom level')
interactive_parser.add_argument('--opacity', type=float, default=0.7, help='Layer opacity (0-1)')
interactive_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
interactive_parser.add_argument('--output', '-o', required=True, help='Output HTML file')
add_common_args(interactive_parser)
interactive_parser.set_defaults(func=cmd_plot)
# Comparison plot
compare_parser = plot_subparsers.add_parser('compare', help='Side-by-side dataset comparison')
compare_parser.add_argument('--input', '-i', required=True, type=Path, help='Primary input directory')
compare_parser.add_argument('--other-input', required=True, type=Path, help='Secondary input directory')
compare_parser.add_argument('--year', '-y', required=True, type=int, help='Year')
compare_parser.add_argument('--month', '-m', required=True, type=int, help='Month (1-12)')
compare_parser.add_argument('--label1', default='Dataset 1', help='Label for primary dataset')
compare_parser.add_argument('--label2', default='Dataset 2', help='Label for secondary dataset')
compare_parser.add_argument('--cmap', default='YlGnBu', help='Colormap for data')
compare_parser.add_argument('--diff-cmap', default='RdBu', help='Colormap for difference')
compare_parser.add_argument('--title', help='Plot title')
compare_parser.add_argument('--width', type=int, default=16, help='Figure width in inches')
compare_parser.add_argument('--height', type=int, default=5, help='Figure height in inches')
compare_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
compare_parser.add_argument('--other-pattern', default='effective_precip_[0-9]*.tif', help='Pattern for secondary dataset')
compare_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(compare_parser)
compare_parser.set_defaults(func=cmd_plot)
# Scatter plot comparison
scatter_parser = plot_subparsers.add_parser('scatter', help='Scatter plot comparison')
scatter_parser.add_argument('--input', '-i', required=True, type=Path, help='Primary input directory')
scatter_parser.add_argument('--other-input', required=True, type=Path, help='Secondary input directory')
scatter_parser.add_argument('--start-year', required=True, type=int, help='Start year')
scatter_parser.add_argument('--end-year', required=True, type=int, help='End year')
scatter_parser.add_argument('--months', '-m', type=int, nargs='+', help='Specific months')
scatter_parser.add_argument('--label1', default='Dataset 1', help='Label for primary dataset')
scatter_parser.add_argument('--label2', default='Dataset 2', help='Label for secondary dataset')
scatter_parser.add_argument('--sample-size', type=int, default=10000, help='Max samples for plot')
scatter_parser.add_argument('--title', help='Plot title')
scatter_parser.add_argument('--width', type=int, default=8, help='Figure width in inches')
scatter_parser.add_argument('--height', type=int, default=8, help='Figure height in inches')
scatter_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
scatter_parser.add_argument('--other-pattern', default='effective_precip_[0-9]*.tif', help='Pattern for secondary dataset')
scatter_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(scatter_parser)
scatter_parser.set_defaults(func=cmd_plot)
# Annual comparison
annual_compare_parser = plot_subparsers.add_parser('annual-compare', help='Annual totals comparison')
annual_compare_parser.add_argument('--input', '-i', required=True, type=Path, help='Primary input directory')
annual_compare_parser.add_argument('--other-input', required=True, type=Path, help='Secondary input directory')
annual_compare_parser.add_argument('--start-year', required=True, type=int, help='Start year')
annual_compare_parser.add_argument('--end-year', required=True, type=int, help='End year')
annual_compare_parser.add_argument('--label1', default='Dataset 1', help='Label for primary dataset')
annual_compare_parser.add_argument('--label2', default='Dataset 2', help='Label for secondary dataset')
annual_compare_parser.add_argument('--stat', default='mean', choices=['mean', 'sum'], help='Spatial statistic')
annual_compare_parser.add_argument('--title', help='Plot title')
annual_compare_parser.add_argument('--width', type=int, default=12, help='Figure width in inches')
annual_compare_parser.add_argument('--height', type=int, default=6, help='Figure height in inches')
annual_compare_parser.add_argument('--pattern', default='effective_precip_[0-9]*.tif', help='File glob pattern (use [0-9]* to exclude fraction files)')
annual_compare_parser.add_argument('--other-pattern', default='effective_precip_[0-9]*.tif', help='Pattern for secondary dataset')
annual_compare_parser.add_argument('--output', '-o', required=True, help='Output image file')
add_common_args(annual_compare_parser)
annual_compare_parser.set_defaults(func=cmd_plot)
return parser