diff --git a/requirements-docs.yml b/requirements-docs.yml index 073eecec37..ebe9244d93 100644 --- a/requirements-docs.yml +++ b/requirements-docs.yml @@ -10,7 +10,7 @@ dependencies: - chardet>=3.0.4 - Cython - Flask -- GDAL>=3.1.2 +- GDAL>=3.1.2,<3.3.0 - numpy>=1.11.0,!=1.16.0 - pandas>=1.0,<1.2.0 - pip diff --git a/src/natcap/invest/carbon.py b/src/natcap/invest/carbon.py index 1eabcdcc21..0d17830c1d 100644 --- a/src/natcap/invest/carbon.py +++ b/src/natcap/invest/carbon.py @@ -32,109 +32,147 @@ "lulc_cur_path": { **spec_utils.LULC, "projected": True, + "projection_units": u.meter, "about": ( - "A GDAL-supported raster representing the land-cover of the " - "current scenario."), - "name": "Current Land Use/Land Cover" + "A map of LULC for the current scenario. " + "All values in this raster must have corresponding " + "entries in the Carbon Pools table."), + "name": "current LULC" }, "calc_sequestration": { "type": "boolean", "required": "do_valuation | do_redd", "about": ( - "Check to enable sequestration analysis. This requires inputs " - "of Land Use/Land Cover maps for both current and future " - "scenarios."), - "name": "Calculate Sequestration" + "Run sequestration analysis. This requires inputs " + "of LULC maps for both current and future " + "scenarios. Required if REDD scenario analysis or " + "run valuation model is selected."), + "name": "calculate sequestration" }, "lulc_fut_path": { **spec_utils.LULC, "projected": True, + "projection_units": u.meter, "required": "calc_sequestration", "about": ( - "A GDAL-supported raster representing the land-cover of the " - "future scenario. If REDD scenario analysis is enabled, this " - "should be the reference, or baseline, future scenario " - "against which to compare the REDD policy scenario."), - "name": "Future Landcover" + "A map of LULC for the future scenario. " + "If run valuation model is " + "selected, this should be the reference, or baseline, future " + "scenario against which to compare the REDD policy scenario. " + "All values in this raster must have corresponding entries in " + "the Carbon Pools table. Required if Calculate Sequestration " + "is selected."), + "name": "future LULC" }, "do_redd": { "type": "boolean", "required": False, "about": ( - "Check to enable REDD scenario analysis. This requires three " - "Land Use/Land Cover maps: one for the current scenario, one " + "Run REDD scenario analysis. This requires three " + "LULC maps: one for the current scenario, one " "for the future baseline scenario, and one for the future " "REDD policy scenario."), - "name": "REDD Scenario Analysis" + "name": "REDD scenario analysis" }, "lulc_redd_path": { **spec_utils.LULC, "projected": True, + "projection_units": u.meter, "required": "do_redd", "about": ( - "A GDAL-supported raster representing the land-cover of the " - "REDD policy future scenario. This scenario will be compared " - "to the baseline future scenario."), - "name": "REDD Policy" + "A map of LULC for the REDD policy scenario. " + "All values in this raster must have corresponding entries in " + "the Carbon Pools table. Required if REDD Scenario Analysis " + "is selected."), + "name": "REDD LULC" }, "carbon_pools_path": { "type": "csv", "columns": { - "lucode": {"type": "integer"}, - "c_above": {"type": "number", "units": u.metric_ton/u.hectare}, - "c_below": {"type": "number", "units": u.metric_ton/u.hectare}, - "c_soil": {"type": "number", "units": u.metric_ton/u.hectare}, - "c_dead": {"type": "number", "units": u.metric_ton/u.hectare} + "lucode": { + "type": "integer", + "about": ( + "LULC code. Every value in the " + "LULC maps must have a corresponding entry in " + "this column.") + }, + "c_above": { + "type": "number", + "units": u.metric_ton/u.hectare, + "about": "Carbon density of aboveground biomass."}, + "c_below": { + "type": "number", + "units": u.metric_ton/u.hectare, + "about": "Carbon density of belowground biomass."}, + "c_soil": { + "type": "number", + "units": u.metric_ton/u.hectare, + "about": "Carbon density of soil."}, + "c_dead": { + "type": "number", + "units": u.metric_ton/u.hectare, + "about": "Carbon density of dead matter."} }, "about": ( - "A table that maps the each LULC class from the LULC map(s)to " - "the amount of carbon in their carbon pools."), - "name": "Carbon Pools" + "A table that maps each LULC code to carbon pool data for " + "that LULC type."), + "name": "carbon pools" }, "lulc_cur_year": { "expression": "float(value).is_integer()", "type": "number", "units": u.year, "required": "do_valuation", - "about": "The calendar year of the current scenario.", - "name": "Current Land Cover Calendar Year" + "about": ( + "The calendar year of the current scenario depicted in the " + "current LULC map. Required if Run Valuation model is selected."), + "name": "current LULC year" }, "lulc_fut_year": { "expression": "float(value).is_integer()", "type": "number", "units": u.year, "required": "do_valuation", - "about": "The calendar year of the future scenario.", - "name": "Future Land Cover Calendar Year" + "about": ( + "The calendar year of the future scenario depicted in the " + "future LULC map. Required if Run Valuation model is selected."), + "name": f"future LULC year" }, "do_valuation": { "type": "boolean", "required": False, "about": ( - "Calculate NPV for a future or REDD scenario " - "and report in final HTML document."), - "name": "Run Valuation Model" + "Calculate net present value for the future scenario, and the " + "REDD scenario if provided, and report it in the final HTML " + "document."), + "name": "run valuation model" }, "price_per_metric_ton_of_c": { "type": "number", "units": u.currency/u.ton, "required": "do_valuation", - "about": "The present value of carbon per metric ton.", + "about": ( + "The present value of carbon. " + "Required if Run Valuation model is selected."), "name": "price of carbon" }, "discount_rate": { "type": "ratio", "required": "do_valuation", - "about": "The discount rate as a floating point percent.", - "name": "Market Discount in Price of Carbon (%)" + "about": ( + "The annual market discount rate in the price of carbon, " + "which reflects society's preference for immediate benefits " + "over future benefits. Required if Run Valuation model is " + "selected."), + "name": "annual market discount rate" }, "rate_change": { "type": "ratio", "required": "do_valuation", "about": ( - "The floating point percent increase of the price of carbon " - "per year."), - "name": "Annual Rate of Change in Price of Carbon" + "The relative annual increase of the price of carbon. " + "Required if Run Valuation model is selected."), + "name": "annual price change" } } } diff --git a/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py b/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py index 4f3542f773..d5091290b1 100644 --- a/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py +++ b/src/natcap/invest/coastal_blue_carbon/coastal_blue_carbon.py @@ -159,112 +159,124 @@ "n_workers": spec_utils.N_WORKERS, "landcover_snapshot_csv": { "type": "csv", - "required": False, "columns": { - "snapshot_year": {"type": "number", "units": u.year}, + "snapshot_year": { + "type": "number", + "units": u.year, + "about": ( + "The snapshot year that this row's LULC raster " + "represents. Each year in this table must be unique.") + }, "raster_path": { "type": "raster", - "bands": {1: {"type": "integer"}} + "bands": {1: {"type": "integer"}}, + "about": ( + "Map of LULC in the given snapshot " + "year. All values in this raster must have " + "corresponding entries in the Biophysical Table and " + "Landcover Transitions Table.") } }, "about": ( - "A CSV table where each row represents the year and path to a " - "raster file on disk representing the landcover raster " - "representing the state of the landscape in that year. " - "Landcover codes match those in the biophysical table and in " - "the landcover transitions table."), - "name": "Landcover Snapshots Table", + "A table mapping snapshot years to corresponding LULC maps."), + "name": "landcover snapshots table", }, "analysis_year": { "type": "number", "units": u.year, "required": False, - "name": "Analysis Year", + "name": "analysis year", "about": ( - "An analysis year extends the transient analysis beyond the " - "transition years. If not provided, the analysis will halt at " - "the final transition year."), + "A year that may be used to extend the analysis beyond the " + "last snapshot year. If used, the model assumes that carbon " + "will continue to accumulate or emit after the last snapshot " + "year until the analysis year. This value must be greater " + "than the final snapshot year."), }, "biophysical_table_path": { - "name": "Biophysical Table", + "name": "biophysical table", "type": "csv", "columns": { "code": { - "type": "freestyle_string", - "about": "Textual description of the landcover class."}, - "lulc-class": { "type": "integer", "about": ( - "The landcover code used in the LULC snapshot rasters " - "to represent this landcover class.")}, + "The LULC code that represents this LULC " + "class in the LULC snapshot rasters.")}, + "lulc-class": { + "type": "freestyle_string", + "about": ( + "Name of the LULC class. This label must be " + "unique among the all the LULC classes.")}, "biomass-initial": { "type": "number", "units": u.megatonne/u.hectare, "about": ( - "the initial carbon stocks in the biomass pool for " - "this landcover classification")}, + "The initial carbon stocks in the biomass pool for " + "this LULC class.")}, "soil-initial": { "type": "number", "units": u.megatonne/u.hectare, "about": ( - "the initial carbon stocks in the soil pool for this " - "landcover classification")}, + "The initial carbon stocks in the soil pool for this " + "LULC class.")}, "litter-initial": { "type": "number", "units": u.megatonne/u.hectare, "about": ( - "the initial carbon stocks in the litter pool for " - "this landcover classification")}, + "The initial carbon stocks in the litter pool for " + "this LULC class.")}, "biomass-half-life": { "type": "number", "units": u.year, - "about": "the half-life of carbon in the biomass pool."}, + "expression": "value > 0", + "about": "The half-life of carbon in the biomass pool."}, "biomass-low-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the biomass pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a low-impact disturbance.")}, + "Proportion of carbon stock in the biomass pool that " + "is disturbed when a cell transitions away from this " + " LULC class in a low-impact disturbance.")}, "biomass-med-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the biomass pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a medium-impact disturbance.")}, + "Proportion of carbon stock in the biomass pool that " + "is disturbed when a cell transitions away from this " + "LULC class in a medium-impact disturbance.")}, "biomass-high-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the biomass pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a high-impact disturbance.")}, + "Proportion of carbon stock in the biomass pool that " + "is disturbed when a cell transitions away from this " + "LULC class in a high-impact disturbance.")}, "biomass-yearly-accumulation": { "type": "number", "units": u.megatonne/u.hectare/u.year, "about": ( - "the annual rate of CO2E accumulation in the biomass " - "pool.")}, + "Annual rate of CO2E accumulation in the biomass pool." + )}, "soil-half-life": { "type": "number", "units": u.year, - "about": "the half-life of carbon in the soil pool."}, + "expression": "value > 0", + "about": "The half-life of carbon in the soil pool."}, "soil-low-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the soil pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a low-impact disturbance.")}, + "Proportion of carbon stock in the soil pool that " + "is disturbed when a cell transitions away from this " + "LULC class in a low-impact disturbance.")}, "soil-med-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the soil pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a medium-impact disturbance.")}, + "Proportion of carbon stock in the soil pool that " + "is disturbed when a cell transitions away from this " + "LULC class in a medium-impact disturbance.")}, "soil-high-impact-disturb": { "type": "ratio", "about": ( - "ratio of carbon stock in the soil pool that is " - "disturbed when a cell transitions away from this " - "landcover class in a high-impact disturbance.")}, + "Proportion of carbon stock in the soil pool that " + "is disturbed when a cell transitions away from this " + "LULC class in a high-impact disturbance.")}, "soil-yearly-accumulation": { "type": "number", "units": u.megatonne/u.hectare/u.year, @@ -276,17 +288,17 @@ "about": ( "Annual rate of CO2E accumulation in the litter pool.")} }, - "about": "Table of biophysical properties for each LULC class" + "about": "Table of biophysical properties for each LULC class." }, "landcover_transitions_table": { - "name": "Landcover Transitions Table", + "name": "landcover transitions table", "type": "csv", "columns": { "lulc-class": { "type": "integer", "about": ( "LULC codes matching the codes in the biophysical " - "table")}, + "table.")}, "[LULC CODE]": { "type": "option_string", "options": { @@ -295,52 +307,62 @@ "med-impact-disturb": "medium carbon disturbance rate", "low-impact-disturb": "low carbon disturbance rate", "NCC": "no change in carbon", - "": "cell may be empty if the transition never occurs" }, "about": ( - "Field values within the " - "transition matrix describe the change in carbon when " - "transitioning from the LULC class along the x axis " - "to the LULC class along the y axis")} + "A transition matrix describing the type of carbon " + "action that occurs when each LULC type transitions " + "to each other type. Values in the first column, " + "'lulc-class', represents the original LULC class " + "that is transitioned away from. Values in the first " + "row represents the LULC class that is transitioned " + "to. Each cell in the matrix is filled with an option " + "indicating the effect on carbon when transitioning " + "from that cell's row's LULC class to that cell's " + "column's LULC class. The classes in this table must " + "exactly match the classes in the Biophysical Table " + "'lulc-class' column. A cell may be left empty if " + "the transition never occurs.")} }, "about": ( "A transition matrix mapping the type of carbon action " - "undergone when one landcover type transitions to another. " - "The Coastal Blue Carbon preprocessor exists to help create " - "this table for you."), + "undergone when one LULC type transitions to another."), }, "do_economic_analysis": { - "name": "Calculate Net Present Value of Sequestered Carbon", + "name": "run valuation", "type": "boolean", "required": False, "about": ( - "A boolean value indicating whether the model should run an " - "economic analysis."), + "Enable net present valuation analysis based on " + "carbon prices from either a yearly price table, or an " + "initial price and yearly interest rate."), }, "use_price_table": { - "name": "Use Price Table", + "name": "use price table", "type": "boolean", "required": False, "about": ( - "boolean value indicating whether a price table is included " - "in the arguments and to be used or a price and interest rate " - "is provided and to be used instead."), + "Use a yearly price table, rather than an initial " + "price and interest rate, to indicate carbon value over time."), }, "price": { - "name": "Price", + "name": "price", "type": "number", "units": u.currency/u.megatonne, "required": "do_economic_analysis and (not use_price_table)", - "about": "The price of CO2E at the base year.", + "about": ( + "The price of CO2E at the baseline year. Required if Do " + "Valuation is selected and Use Price Table is not selected."), }, "inflation_rate": { - "name": "Interest Rate", + "name": "interest rate", "type": "percent", "required": "do_economic_analysis and (not use_price_table)", - "about": "Annual change in the price per unit of carbon.", + "about": ( + "Annual increase in the price of CO2E. Required if Do " + "Valuation is selected and Use Price Table is not selected.") }, "price_table_path": { - "name": "Price Table", + "name": "price table", "type": "csv", "required": "use_price_table", "columns": { @@ -348,23 +370,25 @@ "type": "number", "units": u.year, "about": ( - "Each year from the snapshot year to analysis year")}, + "Each year from the snapshot year to analysis year.")}, "price": { "type": "number", "units": u.currency/u.megatonne, - "about": "Price of CO2E in that year"} + "about": "Price of CO2E in that year."} }, "about": ( - "Table of annual CO2E prices. Can be used in place of price " - "and interest rate inputs."), + "Table of annual CO2E prices for each year from the baseline " + "year to the final snapshot or analysis year. Required if Do " + "Valuation is selected and Use Price Table is selected."), }, "discount_rate": { - "name": "Discount Rate", + "name": "discount rate", "type": "percent", "required": "do_economic_analysis", "about": ( - "The discount rate on future valuations of sequestered " - "carbon, compounded yearly."), + "Annual discount rate on the price of carbon. This is " + "compounded each year after the baseline year. " + "Required if Run Valuation is selected."), }, } } diff --git a/src/natcap/invest/coastal_blue_carbon/preprocessor.py b/src/natcap/invest/coastal_blue_carbon/preprocessor.py index cba52b5478..b8d044a705 100644 --- a/src/natcap/invest/coastal_blue_carbon/preprocessor.py +++ b/src/natcap/invest/coastal_blue_carbon/preprocessor.py @@ -25,24 +25,27 @@ "results_suffix": spec_utils.SUFFIX, "n_workers": spec_utils.N_WORKERS, "lulc_lookup_table_path": { - "name": "LULC Lookup Table", + "name": "LULC lookup table", "type": "csv", "about": ( - "A CSV table used to map lulc classes to their values in a " - "raster, as well as to indicate whether or not the lulc class " - "is a coastal blue carbon habitat."), + "A table mapping LULC codes from the snapshot rasters to the " + "corresponding LULC class names, and whether or not the " + "class is a coastal blue carbon habitat."), "columns": { "code": { "type": "integer", - "about": "LULC code"}, + "about": ( + "LULC code. Every value in the " + "snapshot LULC maps must have a corresponding entry " + "in this column.")}, "lulc-class": { "type": "freestyle_string", - "about": "Text description of the LULC class"}, + "about": "Name of the LULC class."}, "is_coastal_blue_carbon_habitat": { "type": "boolean", "about": ( "Enter TRUE if this LULC class is a coastal blue " - "carbon habitat, FALSE if not")} + "carbon habitat, FALSE if not.")} } }, "landcover_snapshot_csv": { @@ -51,23 +54,20 @@ "snapshot_year": { "type": "number", "units": u.year, - "about": "Year to snapshot"}, + "about": "Year to snapshot."}, "raster_path": { "type": "raster", - "bands": { - 1: { - "type": "integer", - "about": "Map of LULC codes" - } - } + "bands": {1: {"type": "integer"}}, + "about": ( + "Map of LULC in the snapshot year. " + "All values in this raster must have corresponding " + "entries in the LULC Lookup table.") } }, "about": ( - "A CSV table where each row represents the year and path to a " - "raster file on disk representing the landcover raster " - "representing the state of the landscape in that year. " - "Landcover codes match those in the LULC lookup table."), - "name": "LULC Snapshots Table", + "A table mapping snapshot years to corresponding LULC maps " + "for each year."), + "name": "LULC snapshots table", }, } } diff --git a/src/natcap/invest/coastal_vulnerability.py b/src/natcap/invest/coastal_vulnerability.py index d014b2d3a6..cf51f52f69 100644 --- a/src/natcap/invest/coastal_vulnerability.py +++ b/src/natcap/invest/coastal_vulnerability.py @@ -49,36 +49,31 @@ "results_suffix": spec_utils.SUFFIX, "n_workers": spec_utils.N_WORKERS, "aoi_vector_path": { - "type": "vector", - "fields": {}, - "geometries": spec_utils.POLYGONS, + **spec_utils.AOI, "projected": True, "projection_units": u.meter, - "name": "area of interest", - "about": ( - "Path to a polygon vector that is projected in a coordinate " - "system with units of meters. The polygon should intersect " - "the landmass and the shelf contour line"), + "about": "Map of the region over which to run the model." }, "model_resolution": { "type": "number", "expression": "value > 0", "units": u.meter, "about": ( - "Distance at which the coastline will be resolved. Coastline " - "features smaller than this distance will not be represented " - "by the shoreline points. Points will be spaced at intervals " - "of half the model resolution."), - "name": "Model resolution" + "Interval at which to space shore points along the coastline."), + "name": "model resolution" }, "landmass_vector_path": { "type": "vector", "fields": {}, "geometries": spec_utils.POLYGONS, "about": ( - "Path to a polygon vector representing landmasses in the " - "region of interest."), - "name": "Landmass vector" + "Map of all landmasses in and around the region of interest. " + "It is not recommended to clip this landmass to the AOI " + "polygon because some functions in the model require " + "searching for landmasses around shore points up to the " + "distance defined in Maximum Fetch Distance, which likely " + "extends beyond the AOI polygon."), + "name": "landmasses" }, "wwiii_vector_path": { "type": "vector", @@ -86,26 +81,26 @@ "rei_pct[SECTOR]": { "type": "percent", "about": ( - "The proportion of the highest 10% of wind speeds in " + "Proportion of the highest 10% of wind speeds in " "the record of interest that blow in the direction of " - "each sector")}, + "each sector.")}, "rei_v[SECTOR]": { "type": "number", "units": u.meter/u.second, "about": ( "Average of the highest 10% of wind speeds that blow " - "in the direction of each sector")}, + "in the direction of each sector.")}, "wavppct[SECTOR]": { "type": "percent", "about": ( "Proportion of the highest 10% of wave power values " - "on record that are in each sector")}, + "on record that are in each sector.")}, "wavp_[SECTOR]": { "type": "number", "units": u.kilowatt/u.meter, "about": ( "Average of the highest 10% of wave power values on " - "record in the direction of each sector")}, + "record in the direction of each sector.")}, "v10pct_[SECTOR]": { "type": "percent", "about": ( @@ -114,57 +109,59 @@ }, "geometries": spec_utils.POINT, "about": ( - "Path to a point vector containing wind and wave data. This " - "global dataset is provided with the InVEST sample data. " - "There are 80 required columns; each of the 5 types is " - "repeated for each sixteenth sector of the 360° compass: " + "Map of gridded wind and wave data that represent storm " + "conditions. This global dataset is provided with the InVEST " + "sample data. There are 80 required columns; each of the 5 " + "types is repeated for each sixteenth sector of the 360° " + "compass: " "[0,22,45,67,90,112,135,157,180,202,225,247,270,292,315,337]. " - "For example: REI_PCT0, V10PCT_90"), - "name": "WaveWatchIII vector" + "For example: REI_PCT0, V10PCT_90."), + "name": "WaveWatchIII" }, "max_fetch_distance": { "type": "number", "units": u.meter, - "expression": "int(value) > 0", + "expression": "value > 0", "about": ( "Maximum distance in meters to extend rays from shore points. " "Points with rays equal to this distance accumulate ocean- " - "driven wave exposure along those rays and local-wind- driven " + "driven wave exposure along those rays and local-wind-driven " "wave exposure along the shorter rays."), - "name": "Maximum Fetch Distance" + "name": "maximum fetch distance" }, "bathymetry_raster_path": { "type": "raster", - "bands": { - 1: { - "type": "number", - "units": u.meter - } - }, + "bands": {1: { + "type": "number", + "units": u.meter + }}, "about": ( - "Path to a raster representing bathymetry. Bathymetry values " - "should be negative. Positive values will be ignored. This " + "Map of bathymetry (ocean depth). Bathymetry values " + "should be negative, and any positive values will be ignored. This " "should cover the area extending beyond the AOI to the " - "max_fetch_distance."), - "name": "Bathymetry raster" + "maximum fetch distance."), + + "name": "Bathymetry" }, "shelf_contour_vector_path": { "type": "vector", "fields": {}, "geometries": spec_utils.LINES, "about": ( - "Path to a polyline vector delineating the edge of the " - "continental shelf or another bathymetry contour."), - "name": "Continental Shelf Contour vector" + "Map of the edges of the continental shelf or other locally " + "relevant bathymetry contour."), + "name": "continental shelf contour" }, "dem_path": { - "type": "raster", + **spec_utils.DEM, "bands": {1: { "type": "number", - "units": u.linear_unit # any unit of length is ok + "units": u.none # any unit of length is ok }}, - "about": "Path to a raster representing elevation on land.", - "name": "Digital Elevation Model" + "about": ( + "Map of elevation above sea level on land. This should cover " + "the area extending beyond the AOI by at least the elevation " + "averaging radius. Elevation may be measured in any unit.") }, "dem_averaging_radius": { "type": "number", @@ -172,64 +169,81 @@ "expression": "value > 0", "about": ( "A radius around each shore point within which to average the " - "elevation values of the DEM raster."), - "name": "Elevation averaging radius" + "elevation values in the DEM raster."), + "name": "elevation averaging radius" }, "habitat_table_path": { "type": "csv", "columns": { "id": { "type": "freestyle_string", - "about": ( - "Text string (no spaces allowed) that uniquely " - "describes the habitat.")}, + "about": "Unique name for the habitat. No spaces allowed."}, "path": { "type": "vector", "fields": {}, - "geometries": {"POLYGON", "MULTIPOLYGON"} - }, + "geometries": {"POLYGON", "MULTIPOLYGON"}, + "about": "Map of area(s) where the habitat is present."}, "rank": { - "type": "number", - "units": u.none, - "expression": "value in {1, 2, 3, 4, 5}" + "type": "option_string", + "options": { + "1": "very high protection", + "2": "high protection", + "3": "moderate protection", + "4": "low protection", + "5": "very low protection" + }, + "about": ( + "Relative amount of coastline protection this habitat " + "provides.") }, "protection distance (m)": { "type": "number", "units": u.meter, - "expression": "value >= 0" + "expression": "value >= 0", + "about": ( + "The distance beyond which this habitat will provide " + "no protection to the coastline.") }, }, "about": ( - "Path to a CSV file that specifies habitat layer input data " - "and parameters."), - "name": "Habitats Table" + "Table that specifies spatial habitat data and parameters."), + "name": "habitats table" }, "geomorphology_vector_path": { "type": "vector", "fields": { "rank": { - "type": "number", - "units": u.none, - "expression": "value in {1, 2, 3, 4, 5}" + "type": "option_string", + "options": { + "1": "very low exposure", + "2": "low exposure", + "3": "moderate exposure", + "4": "high exposure", + "5": "very high exposure" + }, + "about": "Relative exposure of the segment of coastline." } }, "geometries": spec_utils.LINES, "required": False, - "about": ( - "Path to a polyline vector that has a field called 'RANK' " - "with values from 1 to 5."), - "name": "Geomorphology vector" + "about": "Map of relative exposure of each segment of coastline.", + "name": "geomorphology" }, "geomorphology_fill_value": { - "type": "number", - "units": u.none, - "expression": "(int(value) >= 1) & (int(value) <= 5)", + "type": "option_string", + "options": { + "1": "very low exposure", + "2": "low exposure", + "3": "moderate exposure", + "4": "high exposure", + "5": "very high exposure" + }, "required": "geomorphology_vector_path", "about": ( - "A value from 1 to 5 that will be used as a geomorphology " - "rank for any points not proximate (given the model " - "resolution) to the geomorphology_vector_path."), - "name": "Geomorphology fill value" + "Exposure rank to assign to any shore points that are not " + "near to any segment in the geomorphology vector. " + "Required if a Geomorphology vector is provided."), + "name": "geomorphology fill value" }, "population_raster_path": { "type": "raster", @@ -237,10 +251,8 @@ 1: {"type": "number", "units": u.none} }, "required": False, - "about": ( - "Path to a raster with values representing population totals " - "per pixel."), - "name": "Human Population Raster" + "about": "Map of total human population on each pixel.", + "name": "human population" }, "population_radius": { "type": "number", @@ -248,15 +260,18 @@ "expression": "value > 0", "required": "population_raster_path", "about": ( - "A radius around each shore point within which to compute the " - "average population density."), - "name": "Population search radius" + "The radius around each shore point within which to compute " + "the average population density. " + "Required if a Human Population map is provided."), + "name": "population search radius" }, "slr_vector_path": { "type": "vector", "fields": { - "[SLR_FIELD]": { # may be anything, will be chosen as the slr_field - "about": "sea level rise", + "[SLR_FIELD]": { + "about": ( + "Sea level rise rate or amount. This field name must " + "be chosen as the Sea Level Rise Field."), "type": "number", "units": u.none } @@ -264,18 +279,19 @@ "geometries": spec_utils.POINT, "required": False, "about": ( - "Path to a point vector with a field of sea-level-rise rates " - "or amounts."), - "name": "Sea Level Rise Vector" + "Map of sea level rise rates or amounts. May be any sea level " + "rise metric of interest, such as rate, or net rise/fall."), + "name": "sea level rise" }, "slr_field": { "type": "option_string", "options": {}, "required": "slr_vector_path", "about": ( - "The name of a field in the SLR vector table from which to " - "load values"), - "name": "Sea Level Rise field name" + "Name of the field in the sea level rise vector which " + "contains the sea level rise metric of interest. " + "Required if a Sea Level Rise vector is provided."), + "name": "sea level rise field" } } } diff --git a/src/natcap/invest/crop_production_percentile.py b/src/natcap/invest/crop_production_percentile.py index 656e450784..9071edd8de 100644 --- a/src/natcap/invest/crop_production_percentile.py +++ b/src/natcap/invest/crop_production_percentile.py @@ -37,13 +37,7 @@ "landcover_raster_path": { **spec_utils.LULC, "projected": True, - "projection_units": u.meter, - "about": ( - "A raster file, representing integer land use/land code " - "covers for each cell. This raster should have a projected " - "coordinate system with units of meters (e.g. UTM) because " - "pixel areas are divided by 10000 in order to report some " - "results in hectares."), + "projection_units": u.meter }, "landcover_to_crop_table_path": { "type": "csv", @@ -95,9 +89,10 @@ } }, "about": ( - "A CSV table mapping canonical crop names to the land use " - "codes in the landcover/use raster."), - "name": "Landcover to Crop Table" + "A table that maps each LULC code from the LULC map to one of " + "the 175 canonical crop names representing the crop grown in " + "that LULC class."), + "name": "LULC to Crop Table" }, "aggregate_polygon_path": { **spec_utils.AOI, @@ -111,7 +106,7 @@ "type": "directory", "about": ( "Table mapping each climate bin to yield percentiles " - "for each crop"), + "for each crop."), "contents": { "[CROP]_percentile_yield_table.csv": { "type": "csv", @@ -139,7 +134,7 @@ }, "extended_climate_bin_maps": { "type": "directory", - "about": "Maps of climate bins for each crop", + "about": "Maps of climate bins for each crop.", "contents": { "extendedclimatebins[CROP]": { "type": "raster", @@ -149,7 +144,7 @@ }, "observed_yield": { "type": "directory", - "about": "Maps of actual observed yield for each crop", + "about": "Maps of actual observed yield for each crop.", "contents": { "[CROP]_observed_yield.tif": { "type": "raster", @@ -204,7 +199,7 @@ } } }, - "about": "Path to the InVEST Crop Production Data directory", + "about": "Path to the InVEST Crop Production Data directory.", "name": "model data directory" } } diff --git a/src/natcap/invest/crop_production_regression.py b/src/natcap/invest/crop_production_regression.py index e3b2ef7b64..59c74172ee 100644 --- a/src/natcap/invest/crop_production_regression.py +++ b/src/natcap/invest/crop_production_regression.py @@ -17,6 +17,9 @@ LOGGER = logging.getLogger(__name__) +CROPS = [ + "barley", "maize", "oilpalm", "potato", "rice", "soybean", + "sugarbeet", "sugarcane", "sunflower", "wheat"] ARGS_SPEC = { "model_name": "Crop Production Regression Model", @@ -33,53 +36,40 @@ "landcover_raster_path": { **spec_utils.LULC, "projected": True, - "projection_units": u.meter, - "about": ( - "A raster file, representing integer land use/land code " - "covers for each cell. This raster should have a projected " - "coordinate system with units of meters (e.g. UTM) because " - "pixel areas are divided by 10000 in order to report some " - "results in hectares."), + "projection_units": u.meter }, "landcover_to_crop_table_path": { "type": "csv", "columns": { + "lucode": {"type": "integer"}, "crop_name": { "type": "option_string", - "options": [ - "barley", "maize", "oilpalm", "potato", "rice", - "rye", "soybean", "sugarbeet", "sugarcane", - "sunflower", "wheat" - ] - }, - "lucode": {"type": "integer"} + "options": CROPS + } }, "about": ( - "A CSV table mapping canonical crop names to land use codes " - "contained in the landcover/use raster."), - "name": "Landcover to Crop Table" + "A table that maps each LULC code from the LULC map to one of " + "the 10 canonical crop names representing the crop grown in " + "that LULC class."), + "name": "LULC to crop table" }, "fertilization_rate_table_path": { "type": "csv", "columns": { - "crop_name": {"type": "freestyle_string"}, - "nitrogen_rate": { - "type": "number", - "units": u.kilogram/u.hectare - }, - "phosphorus_rate": { - "type": "number", - "units": u.kilogram/u.hectare + "crop_name": { + "type": "option_string", + "options": CROPS, + "about": "One of the supported crop types." }, - "potassium_rate": { + **{f"{nutrient}_rate": { "type": "number", - "units": u.kilogram/u.hectare - } + "units": u.kilogram/u.hectare, + "about": f"Rate of {nutrient} application for the crop." + } for nutrient in ["nitrogen", "phosphorus", "potassium"]} }, "about": ( - "A table that maps crops to fertilization rates for nitrogen, " - "phosphorus, and potassium."), - "name": "Fertilization Rate Table Path" + "A table that maps crops to fertilizer application rates."), + "name": "fertilization rate table" }, "aggregate_polygon_path": { **spec_utils.AOI, @@ -152,13 +142,8 @@ } } }, - "about": ( - "A path to the InVEST Crop Production Data directory. These " - "data would have been included with the InVEST installer if " - "selected, or can be manually downloaded from " - "http://releases.naturalcapitalproject.org/invest. If " - "downloaded with InVEST, the default value should be used."), - "name": "Directory to model data" + "about": "The Crop Production datasets provided with the model.", + "name": "model data" } } } diff --git a/src/natcap/invest/delineateit/delineateit.py b/src/natcap/invest/delineateit/delineateit.py index 3cb0342094..d78502112c 100644 --- a/src/natcap/invest/delineateit/delineateit.py +++ b/src/natcap/invest/delineateit/delineateit.py @@ -43,9 +43,10 @@ "type": "boolean", "required": False, "about": ( - "If ``True``, the pour point detection algorithm will run, " - "creating a point vector file pour_points.gpkg."), - "name": "Detect pour points" + "Detect pour points (watershed outlets) based on " + "the DEM, and use these instead of a user-provided outlet " + "features vector."), + "name": "detect pour points" }, "outlet_vector_path": { "type": "vector", @@ -53,30 +54,24 @@ "geometries": spec_utils.ALL_GEOMS, "required": "not detect_pour_points", "about": ( - "This is a layer of geometries representing watershed outlets " - "such as municipal water intakes or lakes."), - "name": "Outlet Features" + "A map of watershed outlets from which to delineate the " + "watersheds. Required if Detect Pour Points is not checked."), + "name": "watershed outlets" }, "snap_points": { "type": "boolean", "required": False, "about": ( - "Whether to snap point geometries to the nearest stream " - "pixel. If ``True``, ``args['flow_threshold']`` and " - "``args['snap_distance']`` must also be defined."), - "name": "Snap points to the nearest stream" + "Snap point geometries to the center of the nearest stream " + "pixel. This has no effect if Detect Pour Points is selected."), + "name": "snap points to nearest stream" }, "flow_threshold": { - "expression": "value > 0", - "type": "number", - "units": u.pixel, + **spec_utils.THRESHOLD_FLOW_ACCUMULATION, "required": "snap_points", "about": ( - "The number of upstream cells that must flow into a cell " - "before it's considered part of a stream such that retention " - "stops and the remaining export is exported to the stream. " - "Used to define streams from the DEM."), - "name": "Threshold Flow Accumulation" + spec_utils.THRESHOLD_FLOW_ACCUMULATION["about"] + + " Required if Snap Points is selected."), }, "snap_distance": { "expression": "value > 0", @@ -84,23 +79,20 @@ "units": u.pixels, "required": "snap_points", "about": ( - "If provided, the maximum search radius in pixels to look for " - "stream pixels. If a stream pixel is found within the snap " - "distance, the outflow point will be snapped to the center of " - "the nearest stream pixel. Geometries that are not points " - "(such as Lines and Polygons) will not be snapped. MultiPoint " - "geometries will also not be snapped."), - "name": "Pixel Distance to Snap Outlet Points" + "Maximum distance to relocate watershed outlet points in " + "order to snap them to a stream. Required if Snap Points " + "is selected."), + "name": "snap distance" }, "skip_invalid_geometry": { "type": "boolean", "required": False, "about": ( - "If ``True``, any invalid geometries encountered in the " - "outlet vector will not be included in the delineation. If " - "``False``, an invalid geometry will cause DelineateIt to " - "crash."), - "name": "Crash on invalid geometries" + "Skip delineation for any invalid geometries found in the " + "Outlet Features. Otherwise, an invalid geometry will cause " + "the model to crash." + ), + "name": "skip invalid geometries" } } } diff --git a/src/natcap/invest/hydropower/hydropower_water_yield.py b/src/natcap/invest/hydropower/hydropower_water_yield.py index 2d09ee0e67..202242ef81 100644 --- a/src/natcap/invest/hydropower/hydropower_water_yield.py +++ b/src/natcap/invest/hydropower/hydropower_water_yield.py @@ -22,7 +22,8 @@ "module": __name__, "userguide_html": "reservoirhydropowerproduction.html", "args_with_spatial_overlap": { - "spatial_keys": ["depth_to_root_rest_layer_path", + "spatial_keys": ["lulc_path", + "depth_to_root_rest_layer_path", "precipitation_path", "pawc_path", "eto_path", diff --git a/src/natcap/invest/spec_utils.py b/src/natcap/invest/spec_utils.py index c66e012d23..9894142b08 100644 --- a/src/natcap/invest/spec_utils.py +++ b/src/natcap/invest/spec_utils.py @@ -29,21 +29,21 @@ # overwrite the default use of the symbol 'h' for henries u.define('henry = weber / ampere') u.define('hour = 60 * minute = h = hr') -# overwrite the year definitionto use 'yr' rather than 'a' as default symbol +# overwrite the year definition to use 'year' rather than 'a' as default symbol # the symbol 'yr' is english-specific and the international symbol 'a' may # not be well-known, so we will need to translate this -u.define('year = 365.25 * day = yr = a = julian_year') +u.define('year = 365.25 * day = _ = yr = a = julian_year') # Use u.none for unitless measurements u.define('none = []') # Specs for common arg types ################################################## WORKSPACE = { - "name": "Workspace", + "name": "workspace", "about": ( - "The folder where all intermediate and output files of the model " - "will be written. If this folder does not exist, it will be " - "created."), + "The folder where all the model's output files will be written. If " + "this folder does not exist, it will be created. If data already " + "exists in the folder, it will be overwritten."), "type": "directory", "contents": {}, "must_exist": False, @@ -51,17 +51,17 @@ } SUFFIX = { - "name": "File suffix", + "name": "file suffix", "about": ( - 'A string that will be added to the end of all files ' - 'written to the workspace.'), + "Suffix that will be appended to all output file names. Useful to " + "differentiate between model runs."), "type": "freestyle_string", "required": False, "regexp": "[a-zA-Z0-9_-]*" } N_WORKERS = { - "name": "Taskgraph n_workers parameter", + "name": "taskgraph n_workers parameter", "about": ( "The n_workers parameter to provide to taskgraph. " "-1 will cause all jobs to run synchronously. " @@ -118,10 +118,10 @@ } }, "about": "Map of average annual precipitation.", - "name": "Precipitation" + "name": "precipitation" } ETO = { - "name": "Evapotranspiration", + "name": "evapotranspiration", "type": "raster", "bands": { 1: { @@ -145,12 +145,11 @@ "type": "number", "units": u.pixel, "about": ( - "The number of upstream cells that must flow into a cell " + "The number of upstream pixels that must flow into a pixel " "before it is classified as a stream."), - "name": "Threshold Flow Accumulation Limit" + "name": "threshold flow accumulation" } - # geometry types ############################################################## # the full list of ogr geometry types is in an enum in # https://github.com/OSGeo/gdal/blob/master/gdal/ogr/ogr_core.h diff --git a/src/natcap/invest/validation.py b/src/natcap/invest/validation.py index 9f4d87d0c9..29912337f3 100644 --- a/src/natcap/invest/validation.py +++ b/src/natcap/invest/validation.py @@ -386,7 +386,8 @@ def check_option_string(value, options, **kwargs): """Validate that a string is in a set of options. Args: - value (string): The string value to test. + value: The value to test. Will be cast to a string before comparing + against the allowed options. options (list | dict): strings to test against. If a dict, test against the keys. @@ -397,7 +398,7 @@ def check_option_string(value, options, **kwargs): """ # if options is an empty set, that means it's dynamically populated # so validation should be left to the model's validate function. - if options and value not in options: + if options and str(value) not in options: return "Value must be one of: %s" % sorted(options) diff --git a/tests/test_args_specs.py b/tests/test_args_specs.py index 58d51bb37a..5641602c99 100644 --- a/tests/test_args_specs.py +++ b/tests/test_args_specs.py @@ -118,10 +118,14 @@ def validate(self, arg, name, valid_types, is_pattern=False, required_attrs=[]): isinstance(arg['options'], list)) if isinstance(arg['options'], list): for item in arg['options']: - self.assertTrue(isinstance(item, str)) + self.assertTrue( + isinstance(item, str) or + isinstance(item, int)) else: for key, val in arg['options'].items(): - self.assertTrue(isinstance(key, str)) + self.assertTrue( + isinstance(key, str) or + isinstance(key, int)) self.assertTrue(isinstance(val, str)) attrs.remove('options') @@ -351,7 +355,7 @@ def test_format_unit(self): ('meter / second', 'm/s'), ('foot * mm', 'ft · mm'), ('t * hr * ha / ha / MJ / mm', 't · h · ha / (ha · MJ · mm)'), - ('mm^3 / year', 'mm³/yr') + ('mm^3 / year', 'mm³/year') ]: unit = spec_utils.u.Unit(unit_name) actual = spec_utils.format_unit(unit) diff --git a/tests/test_delineateit.py b/tests/test_delineateit.py index 79a9a2793a..c17e977a9c 100644 --- a/tests/test_delineateit.py +++ b/tests/test_delineateit.py @@ -160,7 +160,7 @@ def test_delineateit_validate(self): self.assertEqual( validation_warnings, [(['dem_path'], 'File could not be opened as a GDAL raster'), - (['flow_threshold'], 'Value does not meet condition value > 0'), + (['flow_threshold'], 'Value does not meet condition value >= 0'), (['outlet_vector_path'], ( 'File could not be opened as a GDAL vector')), (['snap_distance'], ( diff --git a/tests/test_sdr.py b/tests/test_sdr.py index baca238ab1..a9165de31d 100644 --- a/tests/test_sdr.py +++ b/tests/test_sdr.py @@ -206,7 +206,7 @@ def test_base_regression(self): from natcap.invest.sdr import sdr # use predefined directory so test can clean up files during teardown - args = SDRTests.generate_base_args('sdr_test_workspace') #self.workspace_dir) + args = SDRTests.generate_base_args(self.workspace_dir) # make args explicit that this is a base run of SWY sdr.execute(args) @@ -231,8 +231,6 @@ def test_regression_with_undefined_nodata(self): # use predefined directory so test can clean up files during teardown args = SDRTests.generate_base_args(self.workspace_dir) - # args_copy = args.copy() - # args_copy['workspace_dir'] = 'sdr_test_workspace' # set all input rasters to have undefined nodata values tmp_dir = os.path.join(args['workspace_dir'], 'nodata_raster_dir')