Merge branch 'release/3.10' into task/568

This commit is contained in:
emlys 2021-11-03 14:28:59 -07:00
commit 31691cdb67
81 changed files with 777 additions and 524 deletions

View File

@ -79,11 +79,6 @@ jobs:
- name: Build userguide, binaries, installer
shell: bash -l {0}
run: |
# resolves ModuleNotFoundError during pyinstaller build on windows:
# On Windows, with Python >= 3.8, DLLs are no longer imported from the PATH.
# If gdalXXX.dll is in the PATH, then set the USE_PATH_FOR_GDAL_PYTHON=YES environment variable
# to feed the PATH into os.add_dll_directory().
export USE_PATH_FOR_GDAL_PYTHON=YES
# This builds the users guide, binaries, and installer
make windows_installer

View File

@ -34,10 +34,51 @@
.. :changelog:
..
Unreleased Changes
------------------
3.9.2 (2021-10-29)
------------------
* General:
* Improving our binary build by including a data file needed for the
``charset-normalizer`` python package. This eliminates a warning that
was printed to stdout on Windows.
* The Annual Water Yield model name is now standardized throughout InVEST.
This model has been known in different contexts as Hydropower, Hydropower
Water Yield, or Annual Water Yield. This name was chosen to emphasize
that the model can be used for purposes other than hydropower (though the
valuation component is hydropower-specific) and to highlight its
difference from the Seasonal Water Yield model. The corresponding python
module, formerly ``natcap.invest.hydropower.hydropower_water_yield``, is
now ``natcap.invest.annual_water_yield``.
* Minor changes to some other models' display names.
* Update and expand on the instructions in the API docs for installing
the ``natcap.invest`` package.
* The InVEST binaries on Windows now no longer inspect the ``%PATH%``
when looking for GDAL DLLs. This fixes an issue where InVEST would not
launch on computers where the ``%PATH%`` either contained other
environment variables or was malformed.
* invest processes announce their logfile path at a very high logging level
that cannot be filtered out by the user.
* JSON sample data parameter sets are now included in the complete sample
data archives.
* Seasonal Water Yield
* Fixed a bug in validation where providing the monthly alpha table would
cause a "Spatial file <monthly alpha table> has no projection" error.
The montly alpha table was mistakenly being validated as a spatial file.
* Crop Production Regression
* Corrected a misspelled column name. The fertilization rate table column
must now be named ``phosphorus_rate``, not ``phosphorous_rate``.
* Habitat Quality
* Fixed a bug where optional input Allow Accessibility to Threats could
not be passed as an empty string argument. Now handles falsey values.
* Urban Flood Risk
* Fixed a bug where lucodes present in the LULC raster but missing from
the biophysical table would either raise a cryptic IndexError or silently
apply invalid curve numbers. Now a helpful ValueError is raised.
Unreleased Changes (3.10)
-------------------------
* General:

View File

@ -2,15 +2,15 @@
DATA_DIR := data
GIT_SAMPLE_DATA_REPO := https://bitbucket.org/natcap/invest-sample-data.git
GIT_SAMPLE_DATA_REPO_PATH := $(DATA_DIR)/invest-sample-data
GIT_SAMPLE_DATA_REPO_REV := f8c3ef11d06cee9d6f5a07f48057c05939e83028
GIT_SAMPLE_DATA_REPO_REV := c07883c44e00d4c977849e551891eb27e1ba3b1e
GIT_TEST_DATA_REPO := https://bitbucket.org/natcap/invest-test-data.git
GIT_TEST_DATA_REPO_PATH := $(DATA_DIR)/invest-test-data
GIT_TEST_DATA_REPO_REV := bc55f553c9d57baac0931f5691598baa1bcf3923
GIT_TEST_DATA_REPO_REV := 8361823d5927f712a1aec2ee61620f92ff4f49b3
GIT_UG_REPO := https://github.com/natcap/invest.users-guide
GIT_UG_REPO_PATH := doc/users-guide
GIT_UG_REPO_REV := c53f85cec40c830ddd5b18a61e97c48607dd0ef9
GIT_UG_REPO := https://github.com/natcap/invest.users-guide
GIT_UG_REPO_PATH := doc/users-guide
GIT_UG_REPO_REV := 7d0128ed9341acbcc73daef254be171a6cfba844
ENV = "./env"
ifeq ($(OS),Windows_NT)
@ -329,7 +329,7 @@ SAMPLEDATA_SINGLE_ARCHIVE := dist/InVEST_$(VERSION)_sample_data.zip
sampledata_single: $(SAMPLEDATA_SINGLE_ARCHIVE)
$(SAMPLEDATA_SINGLE_ARCHIVE): $(GIT_SAMPLE_DATA_REPO_PATH) dist
$(BASHLIKE_SHELL_COMMAND) "cd $(GIT_SAMPLE_DATA_REPO_PATH) && $(ZIP) -r ../../$(SAMPLEDATA_SINGLE_ARCHIVE) ./* -x .svn -x .git -x *.json"
$(BASHLIKE_SHELL_COMMAND) "cd $(GIT_SAMPLE_DATA_REPO_PATH) && $(ZIP) -r ../../$(SAMPLEDATA_SINGLE_ARCHIVE) ./* -x .svn -x .git"
# Installers for each platform.

View File

@ -15,10 +15,11 @@ choco install make vcredist140 pandoc zip 7zip unzip
$env:PATH += ";C:\ProgramData\chocolatey\bin"
# Install Zip. This has been failing recently, so better to just install directly.
# See https://sourceforge.net/p/forge/documentation/Mirrors/ for mirror options
# See http://gnuwin32.sourceforge.net/setup.html for full list of installer CLI flags.
# The installer doesn't add the target directory to the PATH, so we need to do that too.
Write-Host "Installing GNU Zip"
Invoke-WebRequest https://managedway.dl.sourceforge.net/project/gnuwin32/zip/3.0/zip-3.0-setup.exe -OutFile zip-setup.exe
Invoke-WebRequest https://pilotfiber.dl.sourceforge.net/project/gnuwin32/zip/3.0/zip-3.0-setup.exe -OutFile zip-setup.exe
& ./zip-setup.exe /VERYSILENT /SP /SUPPRESSMSGBOXES
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files (x86)\GnuWin32\bin", "Machine")

View File

@ -1,101 +1,84 @@
.. _installing:
=============================
Installing InVEST From Source
=============================
====================================
Installing the InVEST Python Package
====================================
.. attention::
Most users will want to install the InVEST app (Mac disk image or Windows
installer) from the `InVEST download page <https://naturalcapitalproject.stanford.edu/software/invest>`_.
The instructions here are for more advanced use cases.
.. note::
To install the ``natcap.invest`` package, you must have a C/C++ compiler
installed and configured for your system. MacOS and Linux users should
not need to do anything. Windows users should install Microsoft Visual
Studio, or at least the Build Tools for Visual Studio, if they have
not already. See the `python wiki page on compilation under Windows <https://wiki.python.org/moin/WindowsCompilers>`_ for more information.
.. _BinaryDependencies:
Suggested method
----------------
Binary Dependencies
-------------------
**Pattern**::
InVEST itself depends only on python packages, but many of these package
dependencies depend on low-level libraries or have complex build processes.
Some of these packages (notably, numpy and scipy) have started to release
precompiled binary packages of their own. Recently we have had success
installing all dependencies through ``conda`` and ``pip``; however you may
find it easier to install some through a system package manager.
conda create -y -c conda-forge -n <name> python=<python version>
conda activate <name>
conda install -y -c conda-forge gdal=<gdal version>
pip install natcap.invest==<invest version>
Replace ``<name>`` with any name you'd like to give your environment.
Replace ``<python version>`` with a python version known to be compatible with the desired invest version.
Replace ``<gdal version>`` with a GDAL version known to be compatible with the desired invest version.
Replace ``<invest version>`` with the desired invest version.
Most of the time, it is not really necessary to specify the versions of ``python``, ``gdal``, and ``natcap.invest``. If you do not specify a version, the latest version will be installed. Usually the latest versions are compatible with each other, but not always. Specifying versions that are known to work can prevent some problems. You can find the supported range of GDAL versions in the [requirements.txt](https://github.com/natcap/invest/blob/main/requirements.txt) (be sure to switch to the desired release tag in the dropdown).
**Example for InVEST 3.9.1**::
conda create -y -c conda-forge -n invest391 python=3.9.7
conda activate invest391
conda install -y -c conda-forge gdal=3.3.1
pip install natcap.invest==3.9.1
Conda
--------------
**Condensed into one line**::
If you're using a conda environment to manage your ``natcap.invest`` installation,
it's easiest to install a few binary packages first before using pip to install
the rest::
$ conda install "gdal>=3" numpy shapely rtree
$ pip install natcap.invest
conda create -y -c conda-forge -n invest391 python=3.9.7 gdal=3.3.1 && conda activate invest391 && pip install natcap.invest==3.9.1
System Package Managers
-----------------------
Details
-------
Here is an explanation of what the commands are doing:
.. _InstallingOnLinux:
1. Create a brand-new environment with the correct python version.
Linux
*****
``conda create -y -c conda-forge -n <name> python=<python version>``
Linux users have it easy, as almost every package required to use
natcap.invest is available in the package repositories. The provided
commands will install only the libraries and binaries that are needed, allowing
``pip`` to install the rest.
To be safe, you should **always install ``natcap.invest``` into a brand-new virtual environment**. This way you can be sure you have all the right versions of dependencies. Many issues with installing or using the ``natcap.invest`` package arise from dependency problems, and it's a lot easier to create a new environment than it is to fix an existing one.
2. Activate the brand-new environment just created.
Ubuntu & Debian
^^^^^^^^^^^^^^^
``conda activate <name>``
.. attention::
The package versions in the debian:stable repositories often lag far
behind the latest releases. It may be necessary to install a later
version of a library from a different package repository, or else build
the library from source.
If you run ``conda list`` after this, you'll see the specified python version is there along with around 15 other packages that are included with python by default. None of these are specific to invest. You're now in an isolated environment so you can control which versions of dependencies are available to invest.
3. Install GDAL before installing invest
::
``conda install -y -c conda-forge gdal=<gdal version>``
$ sudo apt-get install python3-dev python3-setuptools python3-gdal python3-rtree python3-shapely
This is important because GDAL is not an ordinary python package. When you install the ``natcap.invest`` package in step 4, ``pip`` will also install all the dependencies of ``natcap.invest``. When ``pip`` tries to install GDAL, you will get an error unless the underlying GDAL binaries are already installed. That's because the ``gdal`` package that ``pip`` installs is just a python wrapper that depends on the GDAL binaries. GDAL itself is not a python package and can't be installed with ``pip``. Luckily, it can be installed with ``conda``!
4. Install invest
Fedora
^^^^^^
``pip install natcap.invest=<invest version>``
::
``pip`` will also install the correct versions of all dependencies of ``natcap.invest``.
$ sudo yum install python3-devel python3-setuptools python3-gdal python3-rtree python3-shapely
Since sometimes we don't need to use the UI at all, the basic ``natcap.invest`` package does not include the dependencies required for the UI. If you try to use the UI without having installed the UI dependencies, you'll get an error. If you do want to use the invest UI via the python package, install ``natcap.invest`` with the UI package extra: ::
.. _InstallingOnMac:
Mac OS X
********
The easiest way to install binary packages on Mac OS X is through a package
manager such as `Homebrew <http://brew.sh>`_::
$ brew install gdal spatialindex pyqt
The GDAL and PyQt packages include their respective python packages.
The others will allow their corresponding python packages to be compiled
against these binaries via ``pip``.
.. _InstallingOnWindows:
Windows
*******
While many packages are available for Windows on the Python Package Index, some
may need to be fetched from a different source. Many are available from
Christogh Gohlke's unofficial build page:
http://www.lfd.uci.edu/~gohlke/pythonlibs/
PyQt4 installers can also be downloaded from the `Riverbank Computing website <https://www.riverbankcomputing.com/software/pyqt/download>`_.
pip install natcap.invest[ui]=<invest version>
The ``[ui]`` tells `pip` to also install all the dependencies needed for the UI.
Python Dependencies
@ -104,59 +87,44 @@ Python Dependencies
Dependencies for ``natcap.invest`` are listed in ``requirements.txt``:
.. include:: ../../requirements.txt
:literal:
:start-line: 14
Additional dependencies for the UI are listed in ``requirements-gui.txt``:
.. include:: ../../requirements-gui.txt
:literal:
:start-line: 9
Please use ``conda`` and ``pip`` to install the correct versions of these dependencies automatically as described above.
.. _BinaryDependencies:
Binary Dependencies
-------------------
Optional Qt User Interface
--------------------------
InVEST's user interface is built with PyQt. Because of the hefty binary
requirement of Qt and the relative difficulty of installing PyQt, these
dependencies will not be installed with the standard
``pip install natcap.invest``. These dependencies are available
as extras, however, and can be installed via pip::
$ pip install natcap.invest[ui]
.. _installing-from-source:
Installing from Source
----------------------
.. note::
Python 3.6 users will need to install Microsoft Visual Studio 2017, or at
least the Build Tools for Visual Studio 2017.
See the `python wiki page on compilation under Windows <https://wiki.python.org/moin/WindowsCompilers>`_
for more information.
Assuming you have a C/C++ compiler installed and configured for your system, and
dependencies installed, the easiest way to install InVEST as a python package
is::
$ pip install natcap.invest
InVEST itself depends only on python packages, but many of these package
dependencies, such as numpy, scipy, and GDAL, depend on low-level libraries
or have complex build processes. Precompiled binaries of all these dependencies
are now available through ``conda`` and/or ``pip``. We recommend using ``conda``
to manage these dependencies because it simplifies the install process and
helps ensure versions are compatible. However, they may also be available through
your system package manager.
Installing the latest development version
-----------------------------------------
Pre-built binaries
******************
Pre-built binaries for Windows
******************************
Pre-built installers and wheels of development versions of ``natcap.invest``
for 32-bit Windows python installations are available from
Pre-built installers and wheels of development versions are available from
http://releases.naturalcapitalproject.org/?prefix=invest/, along with other
distributions of InVEST. Once downloaded, wheels can be installed locally via
distributions of InVEST. Once downloaded, wheels can be installed locally via
pip.
Installing from our source tree
*******************************
Installing from source
**********************
The latest development version of InVEST can be installed from our
git source tree if you have a compiler installed::

View File

@ -65,7 +65,7 @@ Setting up your Python environment
See the `pip docs for installing a package from a wheel
<https://pip.pypa.io/en/stable/user_guide/#installing-from-wheels>`_
* ``.zip`` and ``.tar.gz`` files are source archives.
See :ref:`installing-from-source` for details, including how to
See :ref:`installing` for details, including how to
install specific development versions of ``natcap.invest``.

View File

@ -0,0 +1,4 @@
from PyInstaller.utils.hooks import (collect_data_files,
copy_metadata)
datas = copy_metadata("charset_normalizer") + collect_data_files('charset_normalizer')

18
exe/hooks/hook-osgeo.py Normal file
View File

@ -0,0 +1,18 @@
import os.path
import glob
from PyInstaller.compat import is_win
from PyInstaller.utils.hooks import collect_dynamic_libs, get_package_paths
if is_win:
# GDAL appears to need `_gdal.cp38-win_amd64.pyd` located specifically in
# `osgeo/_gdal....pyd` in order to work. This is because the GDAL python
# __init__ script specifically looks in the `osgeo` directory in order to
# find it. This is apparently only an issue on Windows.
#
# This will take the dynamic libraries in osgeo and put them into osgeo,
# relative to the binaries directory.
binaries = collect_dynamic_libs('osgeo', 'osgeo')
pkg_base, pkg_dir = get_package_paths('osgeo')
for pyd_file in glob.glob(os.path.join(pkg_dir, '*.pyd')):
binaries.append((pyd_file, 'osgeo'))

View File

@ -19,9 +19,3 @@ if platform.system() == 'Darwin':
# sys._MEIPASS is the path to where the pyinstaller entrypoint bundle
# lives. See the pyinstaller docs for more details.
os.environ['SPATIALINDEX_C_LIBRARY'] = sys._MEIPASS
if platform.system() == 'Windows':
# On Windows, with Python >= 3.8, DLLs are no longer imported from the PATH.
# If gdalXXX.dll is in the PATH, then set the USE_PATH_FOR_GDAL_PYTHON=YES environment variable
# to feed the PATH into os.add_dll_directory().
os.environ['USE_PATH_FOR_GDAL_PYTHON'] = 'YES'

View File

@ -31,7 +31,8 @@ kwargs = {
'distutils.dist',
'rtree', # mac builds aren't picking up rtree by default.
'pkg_resources.py2_warn',
'cmath'
'cmath',
'charset_normalizer',
],
'datas': [('qt.conf', '.'), proj_datas],
'cipher': block_cipher,

View File

@ -37,7 +37,7 @@ DATASTACKS = {
'globio': ['globio/globio_demo.invs.json'],
'habitat_quality': ['HabitatQuality/habitat_quality_willamette.invs.json'],
'hra': ['HabitatRiskAssess/hra_wcvi.invs.json'],
'hydropower_water_yield': ['Annual_Water_Yield/annual_water_yield_gura.invs.json'],
'annual_water_yield': ['Annual_Water_Yield/annual_water_yield_gura.invs.json'],
'ndr': ['NDR/ndr_gura.invs.json'],
'pollination': ['pollination/pollination_willamette.invs.json'],
'recreation': ['recreation/recreation_andros.invs.json'],

View File

@ -66,7 +66,6 @@ setup(
'natcap.invest.delineateit',
'natcap.invest.finfish_aquaculture',
'natcap.invest.fisheries',
'natcap.invest.hydropower',
'natcap.invest.ui',
'natcap.invest.ndr',
'natcap.invest.sdr',

View File

@ -1,6 +1,6 @@
"""init module for natcap.invest."""
import builtins
import collections
import dataclasses
import gettext
import logging
import os
@ -22,7 +22,6 @@ except pkg_resources.DistributionNotFound:
# package is not installed. Log the exception for debugging.
LOGGER.exception('Could not load natcap.invest version information')
# Check if the function _() is available
# If not, define it as the identity function
# _() is installed into builtins by gettext when we set up to translate
@ -33,142 +32,185 @@ if not callable(getattr(builtins, '_', None)):
def identity(x): return x
builtins.__dict__['_'] = identity
_UIMETA = collections.namedtuple('UIMeta', 'humanname pyname gui aliases')
MODEL_UIS = {
'carbon': _UIMETA(
humanname=_('Carbon Storage and Sequestration'),
@dataclasses.dataclass
class _MODELMETA:
"""Dataclass to store frequently used model metadata."""
model_title: str # display name for the model
pyname: str # importable python module name for the model
gui: str # importable python class for the corresponding Qt UI
userguide: str # name of the corresponding built userguide file
aliases: tuple # alternate names for the model, if any
MODEL_METADATA = {
'annual_water_yield': _MODELMETA(
model_title='Annual Water Yield',
pyname='natcap.invest.annual_water_yield',
gui='annual_water_yield.AnnualWaterYield',
userguide='annual_water_yield.html',
aliases=('hwy', 'awy')),
'carbon': _MODELMETA(
model_title='Carbon Storage and Sequestration',
pyname='natcap.invest.carbon',
gui='carbon.Carbon',
userguide='carbonstorage.html',
aliases=()),
'coastal_blue_carbon': _UIMETA(
humanname=_('Coastal Blue Carbon'),
'coastal_blue_carbon': _MODELMETA(
model_title='Coastal Blue Carbon',
pyname='natcap.invest.coastal_blue_carbon.coastal_blue_carbon',
gui='cbc.CoastalBlueCarbon',
userguide='coastal_blue_carbon.html',
aliases=('cbc',)),
'coastal_blue_carbon_preprocessor': _UIMETA(
humanname=_('Coastal Blue Carbon: Preprocessor'),
'coastal_blue_carbon_preprocessor': _MODELMETA(
model_title='Coastal Blue Carbon Preprocessor',
pyname='natcap.invest.coastal_blue_carbon.preprocessor',
gui='cbc.CoastalBlueCarbonPreprocessor',
userguide='coastal_blue_carbon.html',
aliases=('cbc_pre',)),
'coastal_vulnerability': _UIMETA(
humanname=_('Coastal Vulnerability'),
'coastal_vulnerability': _MODELMETA(
model_title='Coastal Vulnerability',
pyname='natcap.invest.coastal_vulnerability',
gui='coastal_vulnerability.CoastalVulnerability',
userguide='coastal_vulnerability.html',
aliases=('cv',)),
'crop_production_percentile': _UIMETA(
humanname=_('Crop Production: Percentile Model'),
'crop_production_percentile': _MODELMETA(
model_title='Crop Production: Percentile',
pyname='natcap.invest.crop_production_percentile',
gui='crop_production.CropProductionPercentile',
userguide='crop_production.html',
aliases=('cpp',)),
'crop_production_regression': _UIMETA(
humanname=_('Crop Production: Regression Model'),
'crop_production_regression': _MODELMETA(
model_title='Crop Production: Regression',
pyname='natcap.invest.crop_production_regression',
gui='crop_production.CropProductionRegression',
userguide='crop_production.html',
aliases=('cpr',)),
'delineateit': _UIMETA(
humanname=_('DelineateIt'),
'delineateit': _MODELMETA(
model_title='DelineateIt',
pyname='natcap.invest.delineateit.delineateit',
gui='delineateit.Delineateit',
userguide='delineateit.html',
aliases=()),
'finfish_aquaculture': _UIMETA(
humanname=_('Marine Finfish Aquaculture Production'),
'finfish_aquaculture': _MODELMETA(
model_title='Finfish Aquaculture',
pyname='natcap.invest.finfish_aquaculture.finfish_aquaculture',
gui='finfish.FinfishAquaculture',
userguide='marine_fish.html',
aliases=()),
'fisheries': _UIMETA(
humanname=_('Fisheries'),
'fisheries': _MODELMETA(
model_title='Fisheries',
pyname='natcap.invest.fisheries.fisheries',
gui='fisheries.Fisheries',
userguide='fisheries.html',
aliases=()),
'fisheries_hst': _UIMETA(
humanname=_('Fisheries: Habitat Scenario Tool'),
'fisheries_hst': _MODELMETA(
model_title='Fisheries Habitat Scenario Tool',
pyname='natcap.invest.fisheries.fisheries_hst',
gui='fisheries.FisheriesHST',
userguide='fisheries.html',
aliases=()),
'forest_carbon_edge_effect': _UIMETA(
humanname=_('Forest Carbon Edge Effect'),
'forest_carbon_edge_effect': _MODELMETA(
model_title='Forest Carbon Edge Effect',
pyname='natcap.invest.forest_carbon_edge_effect',
gui='forest_carbon.ForestCarbonEdgeEffect',
userguide='carbon_edge.html',
aliases=('fc',)),
'globio': _UIMETA(
humanname=_('GLOBIO'),
'globio': _MODELMETA(
model_title='GLOBIO',
pyname='natcap.invest.globio',
gui='globio.GLOBIO',
userguide='globio.html',
aliases=()),
'habitat_quality': _UIMETA(
humanname=_('Habitat Quality'),
'habitat_quality': _MODELMETA(
model_title='Habitat Quality',
pyname='natcap.invest.habitat_quality',
gui='habitat_quality.HabitatQuality',
userguide='habitat_quality.html',
aliases=('hq',)),
'habitat_risk_assessment': _UIMETA(
humanname=_('Habitat Risk Assessment'),
'habitat_risk_assessment': _MODELMETA(
model_title='Habitat Risk Assessment',
pyname='natcap.invest.hra',
gui='hra.HabitatRiskAssessment',
userguide='habitat_risk_assessment.html',
aliases=('hra',)),
'hydropower_water_yield': _UIMETA(
humanname=_('Annual Water Yield'),
pyname='natcap.invest.hydropower.hydropower_water_yield',
gui='hydropower.HydropowerWaterYield',
aliases=('hwy',)),
'ndr': _UIMETA(
humanname=_('NDR: Nutrient Delivery Ratio'),
'ndr': _MODELMETA(
model_title='Nutrient Delivery Ratio',
pyname='natcap.invest.ndr.ndr',
gui='ndr.Nutrient',
userguide='ndr.html',
aliases=()),
'pollination': _UIMETA(
humanname=_('Pollinator Abundance: Crop Pollination'),
'pollination': _MODELMETA(
model_title='Crop Pollination',
pyname='natcap.invest.pollination',
gui='pollination.Pollination',
userguide='croppollination.html',
aliases=()),
'recreation': _UIMETA(
humanname=_('Visitation: Recreation and Tourism'),
'recreation': _MODELMETA(
model_title='Visitation: Recreation and Tourism',
pyname='natcap.invest.recreation.recmodel_client',
gui='recreation.Recreation',
userguide='recreation.html',
aliases=()),
'routedem': _UIMETA(
humanname=_('RouteDEM'),
'routedem': _MODELMETA(
model_title='RouteDEM',
pyname='natcap.invest.routedem',
gui='routedem.RouteDEM',
userguide='routedem.html',
aliases=()),
'scenario_generator_proximity': _UIMETA(
humanname=_('Scenario Generator: Proximity Based'),
'scenario_generator_proximity': _MODELMETA(
model_title='Scenario Generator: Proximity Based',
pyname='natcap.invest.scenario_gen_proximity',
gui='scenario_gen.ScenarioGenProximity',
userguide='scenario_gen_proximity.html',
aliases=('sgp',)),
'scenic_quality': _UIMETA(
humanname=_('Unobstructed Views: Scenic Quality Provision'),
'scenic_quality': _MODELMETA(
model_title='Unobstructed Views: Scenic Quality Provision',
pyname='natcap.invest.scenic_quality.scenic_quality',
gui='scenic_quality.ScenicQuality',
userguide='scenic_quality.html',
aliases=('sq',)),
'sdr': _UIMETA(
humanname=_('SDR: Sediment Delivery Ratio'),
'sdr': _MODELMETA(
model_title='Sediment Delivery Ratio',
pyname='natcap.invest.sdr.sdr',
gui='sdr.SDR',
userguide='sdr.html',
aliases=()),
'seasonal_water_yield': _UIMETA(
humanname=_('Seasonal Water Yield'),
'seasonal_water_yield': _MODELMETA(
model_title='Seasonal Water Yield',
pyname='natcap.invest.seasonal_water_yield.seasonal_water_yield',
gui='seasonal_water_yield.SeasonalWaterYield',
userguide='seasonal_water_yield.html',
aliases=('swy',)),
'wind_energy': _UIMETA(
humanname=_('Offshore Wind Energy Production'),
pyname='natcap.invest.wind_energy',
gui='wind_energy.WindEnergy',
'stormwater': _MODELMETA(
model_title='Stormwater',
pyname='natcap.invest.stormwater',
gui='stormwater.Stormwater',
userguide='stormwater.html',
aliases=()),
'wave_energy': _UIMETA(
humanname=_('Wave Energy Production'),
'wave_energy': _MODELMETA(
model_title='Wave Energy Production',
pyname='natcap.invest.wave_energy',
gui='wave_energy.WaveEnergy',
userguide='wave_energy.html',
aliases=()),
'urban_flood_risk_mitigation': _UIMETA(
humanname=_('Urban Flood Risk Mitigation'),
'wind_energy': _MODELMETA(
model_title='Wind Energy Production',
pyname='natcap.invest.wind_energy',
gui='wind_energy.WindEnergy',
userguide='wind_energy.html',
aliases=()),
'urban_flood_risk_mitigation': _MODELMETA(
model_title='Urban Flood Risk Mitigation',
pyname='natcap.invest.urban_flood_risk_mitigation',
gui='urban_flood_risk_mitigation.UrbanFloodRiskMitigation',
userguide='urban_flood_risk_mitigation.html',
aliases=('ufrm',)),
'urban_cooling_model': _UIMETA(
humanname=_('Urban Cooling'),
'urban_cooling_model': _MODELMETA(
model_title='Urban Cooling',
pyname='natcap.invest.urban_cooling_model',
gui='urban_cooling_model.UrbanCoolingModel',
userguide='urban_cooling_model.html',
aliases=('ucm',)),
}

View File

@ -1,4 +1,4 @@
"""InVEST Hydropower Water Yield model."""
"""InVEST Annual Water Yield model."""
import logging
import os
import math
@ -10,17 +10,18 @@ from osgeo import ogr
import pygeoprocessing
import taskgraph
from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Hydropower Water Yield"),
"module": __name__,
"userguide_html": "reservoirhydropowerproduction.html",
"model_name": MODEL_METADATA["annual_water_yield"].model_title,
"pyname": MODEL_METADATA["annual_water_yield"].pyname,
"userguide_html": MODEL_METADATA["annual_water_yield"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["lulc_path",
"depth_to_root_rest_layer_path",
@ -218,7 +219,7 @@ ARGS_SPEC = {
def execute(args):
"""Annual Water Yield: Reservoir Hydropower Production.
Executes the hydropower/water_yield model
Executes the hydropower/annual water yield model
Args:
args['workspace_dir'] (string): a path to the directory that will write

View File

@ -15,13 +15,14 @@ from . import validation
from . import utils
from . import spec_utils
from .spec_utils import u
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("InVEST Carbon Model"),
"module": __name__,
"userguide_html": "carbonstorage.html",
"model_name": MODEL_METADATA["carbon"].model_title,
"pyname": MODEL_METADATA["carbon"].pyname,
"userguide_html": MODEL_METADATA["carbon"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["lulc_cur_path", "lulc_fut_path", "lulc_redd_path"],
},

View File

@ -18,27 +18,27 @@ try:
from . import __version__
from . import utils
from . import datastack
from . import MODEL_UIS
from . import MODEL_METADATA
except (ValueError, ImportError):
# When we're in a PyInstaller build, this isn't a module.
from natcap.invest import __version__
from natcap.invest import utils
from natcap.invest import datastack
from natcap.invest import MODEL_UIS
from natcap.invest import MODEL_METADATA
DEFAULT_EXIT_CODE = 1
LOGGER = logging.getLogger(__name__)
# Build up an index mapping aliases to modelname.
# ``modelname`` is the key to the MODEL_UIS dict, above.
# Build up an index mapping aliases to model_name.
# ``model_name`` is the key to the MODEL_METADATA dict.
_MODEL_ALIASES = {}
for _modelname, _meta in MODEL_UIS.items():
for _alias in _meta.aliases:
assert _alias not in _MODEL_ALIASES, (
for model_name, meta in MODEL_METADATA.items():
for alias in meta.aliases:
assert alias not in _MODEL_ALIASES, (
'Alias %s already defined for model %s') % (
_alias, _MODEL_ALIASES[_alias])
_MODEL_ALIASES[_alias] = _modelname
alias, _MODEL_ALIASES[alias])
_MODEL_ALIASES[alias] = model_name
def build_model_list_table():
@ -51,27 +51,27 @@ def build_model_list_table():
Returns:
A string representation of the formatted table.
"""
model_names = sorted(MODEL_UIS.keys())
model_names = sorted(MODEL_METADATA.keys())
max_model_name_length = max(len(name) for name in model_names)
# Adding 3 to max alias name length for the parentheses plus some padding.
max_alias_name_length = max(len(', '.join(meta.aliases))
for meta in MODEL_UIS.values()) + 3
template_string = ' {modelname} {aliases} {humanname} {usage}'
strings = [_('Available models:')]
for model_name in sorted(MODEL_UIS.keys()):
for meta in MODEL_METADATA.values()) + 3
template_string = ' {model_name} {aliases} {model_title} {usage}'
strings = ['Available models:']
for model_name in model_names:
usage_string = '(No GUI available)'
if MODEL_UIS[model_name].gui is not None:
if MODEL_METADATA[model_name].gui is not None:
usage_string = ''
alias_string = ', '.join(MODEL_UIS[model_name].aliases)
alias_string = ', '.join(MODEL_METADATA[model_name].aliases)
if alias_string:
alias_string = '(%s)' % alias_string
strings.append(template_string.format(
modelname=model_name.ljust(max_model_name_length),
model_name=model_name.ljust(max_model_name_length),
aliases=alias_string.ljust(max_alias_name_length),
humanname=MODEL_UIS[model_name].humanname,
model_title=MODEL_METADATA[model_name].model_title,
usage=usage_string))
return '\n'.join(strings) + '\n'
@ -80,7 +80,7 @@ def build_model_list_json():
"""Build a json object of relevant information for the CLI.
The json object returned uses the human-readable model names for keys
and the values are another dict containing the internal python name
and the values are another dict containing the internal name
of the model and the aliases recognized by the CLI.
Returns:
@ -88,9 +88,9 @@ def build_model_list_json():
"""
json_object = {}
for internal_model_name, model_data in MODEL_UIS.items():
json_object[model_data.humanname] = {
'internal_name': internal_model_name,
for model_name, model_data in MODEL_METADATA.items():
json_object[model_data.model_title] = {
'model_name': model_name,
'aliases': model_data.aliases
}
@ -102,12 +102,12 @@ def export_to_python(target_filepath, model, args_dict=None):
# coding=UTF-8
# -----------------------------------------------
# Generated by InVEST {invest_version} on {today}
# Model: {modelname}
# Model: {model_title}
import logging
import sys
import {py_model}
import {pyname}
import natcap.invest.utils
LOGGER = logging.getLogger(__name__)
@ -123,12 +123,11 @@ def export_to_python(target_filepath, model, args_dict=None):
args = {model_args}
if __name__ == '__main__':
{py_model}.execute(args)
{pyname}.execute(args)
""")
target_model = MODEL_UIS[model].pyname
if args_dict is None:
model_module = importlib.import_module(name=target_model)
model_module = importlib.import_module(name=MODEL_METADATA[model].pyname)
spec = model_module.ARGS_SPEC
cast_args = {key: '' for key in spec['args'].keys()}
else:
@ -147,8 +146,8 @@ def export_to_python(target_filepath, model, args_dict=None):
py_file.write(script_template.format(
invest_version=__version__,
today=datetime.datetime.now().strftime('%c'),
modelname=MODEL_UIS[model].humanname,
py_model=target_model,
model_title=MODEL_METADATA[model].model_title,
pyname=MODEL_METADATA[model].pyname,
model_args=args))
@ -167,11 +166,11 @@ class SelectModelAction(argparse.Action):
Identifiable model names are:
* the model name (verbatim) as identified in the keys of MODEL_UIS
* the model name (verbatim) as identified in the keys of MODEL_METADATA
* a uniquely identifiable prefix for the model name (e.g. "d"
matches "delineateit", but "fi" matches both "fisheries" and
"finfish"
* a known model alias, as registered in MODEL_UIS
* a known model alias, as registered in MODEL_METADATA
If no single model can be identified based on these rules, an error
message is printed and the parser exits with a nonzero exit code.
@ -182,7 +181,7 @@ class SelectModelAction(argparse.Action):
Overridden from argparse.Action.__call__.
"""
known_models = sorted(list(MODEL_UIS.keys()))
known_models = sorted(list(MODEL_METADATA.keys()))
matching_models = [model for model in known_models if
model.startswith(values)]
@ -251,7 +250,7 @@ def main(user_args=None):
verbosity_group.add_argument(
'--debug', dest='log_level', default=logging.ERROR,
action='store_const', const=logging.DEBUG,
help='Enable debug logging. Alias for -vvvv')
help='Enable debug logging. Alias for -vvv')
parser.add_argument(
'-L', '--language', default='en', choices=['en', 'es'],
@ -417,7 +416,7 @@ def main(user_args=None):
parser.exit(0)
if args.subcommand == 'getspec':
target_model = MODEL_UIS[args.model].pyname
target_model = MODEL_METADATA[args.model].pyname
model_module = importlib.import_module(name=target_model)
spec = model_module.ARGS_SPEC
@ -447,7 +446,7 @@ def main(user_args=None):
else:
parsed_datastack.args['workspace_dir'] = args.workspace
target_model = MODEL_UIS[args.model].pyname
target_model = MODEL_METADATA[args.model].pyname
model_module = importlib.import_module(name=target_model)
LOGGER.info('Imported target %s from %s',
model_module.__name__, model_module)
@ -488,7 +487,7 @@ def main(user_args=None):
from natcap.invest.ui import inputs
gui_class = MODEL_UIS[args.model].gui
gui_class = MODEL_METADATA[args.model].gui
module_name, classname = gui_class.split('.')
module = importlib.import_module(
name='.ui.%s' % module_name,

View File

@ -106,6 +106,7 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -150,9 +151,9 @@ TASKGRAPH_CACHE_DIR_NAME = 'task_cache'
OUTPUT_DIR_NAME = 'output'
ARGS_SPEC = {
"model_name": _("Coastal Blue Carbon"),
"module": __name__,
"userguide_html": "coastal_blue_carbon.html",
"model_name": MODEL_METADATA["coastal_blue_carbon"].model_title,
"pyname": MODEL_METADATA["coastal_blue_carbon"].pyname,
"userguide_html": MODEL_METADATA["coastal_blue_carbon"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -12,14 +12,15 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
from . import coastal_blue_carbon
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Coastal Blue Carbon Preprocessor"),
"module": __name__,
"userguide_html": "coastal_blue_carbon.html",
"model_name": MODEL_METADATA["coastal_blue_carbon_preprocessor"].model_title,
"pyname": MODEL_METADATA["coastal_blue_carbon_preprocessor"].pyname,
"userguide_html": MODEL_METADATA["coastal_blue_carbon_preprocessor"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -24,14 +24,15 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Coastal Vulnerability"),
"module": __name__,
"userguide_html": "coastal_vulnerability.html",
"model_name": MODEL_METADATA["coastal_vulnerability"].model_title,
"pyname": MODEL_METADATA["coastal_vulnerability"].pyname,
"userguide_html": MODEL_METADATA["coastal_vulnerability"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": [
"aoi_vector_path",

View File

@ -15,14 +15,15 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Crop Production Percentile Model"),
"module": __name__,
"userguide_html": "crop_production.html",
"model_name": MODEL_METADATA["crop_production_percentile"].model_title,
"pyname": MODEL_METADATA["crop_production_percentile"].pyname,
"userguide_html": MODEL_METADATA["crop_production_percentile"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": [
"landcover_raster_path",

View File

@ -13,6 +13,7 @@ from .spec_utils import u
from . import spec_utils
from . import utils
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -22,9 +23,9 @@ CROPS = [
"sugarbeet", "sugarcane", "sunflower", "wheat"]
ARGS_SPEC = {
"model_name": _("Crop Production Regression Model"),
"module": __name__,
"userguide_html": "crop_production.html",
"model_name": MODEL_METADATA["crop_production_regression"].model_title,
"pyname": MODEL_METADATA["crop_production_regression"].pyname,
"userguide_html": MODEL_METADATA["crop_production_regression"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["landcover_raster_path", "aggregate_polygon_path"],
"different_projections_ok": True,

View File

@ -18,15 +18,16 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
from . import delineateit_core
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("DelineateIt: Watershed Delineation"),
"module": __name__,
"userguide_html": "delineateit.html",
"model_name": MODEL_METADATA["delineateit"].model_title,
"pyname": MODEL_METADATA["delineateit"].pyname,
"userguide_html": MODEL_METADATA["delineateit"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["dem_path", "outlet_vector_path"],
"different_projections_ok": True,

View File

@ -8,14 +8,15 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Finfish Aquaculture"),
"module": __name__,
"userguide_html": "marine_fish.html",
"model_name": MODEL_METADATA["finfish_aquaculture"].model_title,
"pyname": MODEL_METADATA["finfish_aquaculture"].pyname,
"userguide_html": MODEL_METADATA["finfish_aquaculture"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -11,14 +11,15 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
LABEL = 'Fisheries'
ARGS_SPEC = {
"model_name": _("Fisheries"),
"module": __name__,
"userguide_html": "fisheries.html",
"model_name": MODEL_METADATA["fisheries"].model_title,
"pyname": MODEL_METADATA["fisheries"].pyname,
"userguide_html": MODEL_METADATA["fisheries"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -11,13 +11,14 @@ import numpy as np
from . import fisheries_hst_io as io
from .. import validation
from .. import spec_utils
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Fisheries Habitat Scenario Tool"),
"module": __name__,
"userguide_html": "fisheries.html",
"model_name": MODEL_METADATA["fisheries_hst"].model_title,
"pyname": MODEL_METADATA["fisheries_hst"].pyname,
"userguide_html": MODEL_METADATA["fisheries_hst"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -20,6 +20,7 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -30,9 +31,9 @@ DISTANCE_UPPER_BOUND = 500e3
NODATA_VALUE = -1
ARGS_SPEC = {
"model_name": _("Forest Carbon Edge Effect Model"),
"module": __name__,
"userguide_html": "carbon_edge.html",
"model_name": MODEL_METADATA["forest_carbon_edge_effect"].model_title,
"pyname": MODEL_METADATA["forest_carbon_edge_effect"].pyname,
"userguide_html": MODEL_METADATA["forest_carbon_edge_effect"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["aoi_vector_path", "lulc_raster_path"],
},

View File

@ -15,6 +15,7 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -25,9 +26,9 @@ LOGGER = logging.getLogger(__name__)
SIGMA = 9.0
ARGS_SPEC = {
"model_name": _("GLOBIO"),
"module": __name__,
"userguide_html": "../documentation/globio.html",
"model_name": MODEL_METADATA["globio"].model_title,
"pyname": MODEL_METADATA["globio"].pyname,
"userguide_html": MODEL_METADATA["globio"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": [
"lulc_path", "pasture_path", "potential_vegetation_path",

View File

@ -14,6 +14,7 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -22,9 +23,9 @@ MISSING_SENSITIVITY_TABLE_THREATS_MSG = (
'Sensitivity columns: %s') # (set of missing threats, set of found columns)
ARGS_SPEC = {
"model_name": _("Habitat Quality"),
"module": __name__,
"userguide_html": "habitat_quality.html",
"model_name": MODEL_METADATA["habitat_quality"].model_title,
"pyname": MODEL_METADATA["habitat_quality"].pyname,
"userguide_html": MODEL_METADATA["habitat_quality"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": [
"lulc_cur_path", "lulc_fut_path", "lulc_bas_path",
@ -442,7 +443,7 @@ def execute(args):
task_name='access_raster')
access_task_list = [create_access_raster_task]
if 'access_vector_path' in args:
if 'access_vector_path' in args and args['access_vector_path']:
LOGGER.debug("Rasterize Access vector")
rasterize_access_task = task_graph.add_task(
func=pygeoprocessing.rasterize,

View File

@ -18,6 +18,7 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger('natcap.invest.hra')
@ -66,9 +67,9 @@ _DEFAULT_GTIFF_CREATION_OPTIONS = (
'BLOCKXSIZE=256', 'BLOCKYSIZE=256')
ARGS_SPEC = {
"model_name": _("Habitat Risk Assessment"),
"module": __name__,
"userguide_html": "habitat_risk_assessment.html",
"model_name": MODEL_METADATA["habitat_risk_assessment"].model_title,
"pyname": MODEL_METADATA["habitat_risk_assessment"].pyname,
"userguide_html": MODEL_METADATA["habitat_risk_assessment"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -14,14 +14,15 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
from . import ndr_core
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Nutrient Delivery Ratio Model (NDR)"),
"module": __name__,
"userguide_html": "ndr.html",
"model_name": MODEL_METADATA["ndr"].model_title,
"pyname": MODEL_METADATA["ndr"].pyname,
"userguide_html": MODEL_METADATA["ndr"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["dem_path", "lulc_path", "runoff_proxy_path",
"watersheds_path"],
@ -116,8 +117,8 @@ ARGS_SPEC = {
},
"calc_p": {
"type": "boolean",
"about": _("Select to calculate phosphorous export."),
"name": _("Calculate phosphorous retention")
"about": _("Select to calculate phosphorus export."),
"name": _("Calculate phosphorus retention")
},
"calc_n": {
"type": "boolean",
@ -165,7 +166,7 @@ ARGS_SPEC = {
"type": "number",
"units": u.meter,
"required": "calc_p",
"name": _("Subsurface Critical Length (Phosphorous)"),
"name": _("Subsurface Critical Length (Phosphorus)"),
"about": _(
"The distance (traveled subsurface and downslope) after which "
"it is assumed that soil retains nutrient at its maximum "
@ -188,7 +189,7 @@ ARGS_SPEC = {
"subsurface_eff_p": {
"type": "ratio",
"required": "calc_p",
"name": _("Subsurface Maximum Retention Efficiency (Phosphorous)"),
"name": _("Subsurface Maximum Retention Efficiency (Phosphorus)"),
"about": _(
"The maximum nutrient retention efficiency that can be "
"reached through subsurface flow. This field characterizes "
@ -282,7 +283,7 @@ def execute(args):
If args['calc_n'] is True, must also contain the header
'proportion_subsurface_n' field.
args['calc_p'] (boolean): if True, phosphorous is modeled,
args['calc_p'] (boolean): if True, phosphorus is modeled,
additionally if True then biophysical table must have p fields in
them
args['calc_n'] (boolean): if True nitrogen will be modeled,
@ -348,7 +349,7 @@ def execute(args):
"""
# Make sure all the nutrient inputs are good
if len(nutrients_to_process) == 0:
raise ValueError("Neither phosphorous nor nitrogen was selected"
raise ValueError("Neither phosphorus nor nitrogen was selected"
" to be processed. Choose at least one.")
# Build up a list that'll let us iterate through all the input tables
@ -371,7 +372,7 @@ def execute(args):
"Missing header %s from %s" % (
header, table_type))
# proportion_subsurface_n is a special case in which phosphorous does
# proportion_subsurface_n is a special case in which phosphorus does
# not have an equivalent.
if ('n' in nutrients_to_process and
'proportion_subsurface_n' not in lu_parameter_row):

View File

@ -17,13 +17,14 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Crop Pollination"),
"module": __name__,
"userguide_html": "croppollination.html",
"model_name": MODEL_METADATA["pollination"].model_title,
"pyname": MODEL_METADATA["pollination"].pyname,
"userguide_html": MODEL_METADATA["pollination"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -34,6 +34,7 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -88,9 +89,9 @@ predictor_table_columns = {
ARGS_SPEC = {
"model_name": _("Recreation Model"),
"module": __name__,
"userguide_html": "recreation.html",
"model_name": MODEL_METADATA["recreation"].model_title,
"pyname": MODEL_METADATA["recreation"].pyname,
"userguide_html": MODEL_METADATA["recreation"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -12,13 +12,14 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("RouteDEM"),
"module": __name__,
"userguide_html": "routedem.html",
"model_name": MODEL_METADATA["routedem"].model_title,
"pyname": MODEL_METADATA["routedem"].pyname,
"userguide_html": MODEL_METADATA["routedem"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -20,13 +20,14 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Scenario Generator: Proximity Based"),
"module": __name__,
"userguide_html": "scenario_gen_proximity.html",
"model_name": MODEL_METADATA["scenario_generator_proximity"].model_title,
"pyname": MODEL_METADATA["scenario_generator_proximity"].pyname,
"userguide_html": MODEL_METADATA["scenario_generator_proximity"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -19,6 +19,7 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
_VALUATION_NODATA = -99999 # largish negative nodata value.
@ -47,9 +48,9 @@ _INTERMEDIATE_BASE_FILES = {
ARGS_SPEC = {
"model_name": _("Unobstructed Views: Scenic Quality Provision"),
"module": __name__,
"userguide_html": "scenic_quality.html",
"model_name": MODEL_METADATA["scenic_quality"].model_title,
"pyname": MODEL_METADATA["scenic_quality"].pyname,
"userguide_html": MODEL_METADATA["scenic_quality"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["aoi_path", "structure_path", "dem_path"],
"different_projections_ok": True,

View File

@ -19,14 +19,15 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
from . import sdr_core
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Sediment Delivery Ratio Model (SDR)"),
"module": __name__,
"userguide_html": "sdr.html",
"model_name": MODEL_METADATA["sdr"].model_title,
"pyname": MODEL_METADATA["sdr"].pyname,
"userguide_html": MODEL_METADATA["sdr"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["dem_path", "erosivity_path", "erodibility_path",
"lulc_path", "drainage_path", "watersheds_path", ],

View File

@ -17,6 +17,7 @@ from .. import utils
from .. import spec_utils
from ..spec_utils import u
from .. import validation
from .. import MODEL_METADATA
from . import seasonal_water_yield_core
@ -31,13 +32,13 @@ MONTH_ID_TO_LABEL = [
'nov', 'dec']
ARGS_SPEC = {
"model_name": _("Seasonal Water Yield"),
"module": __name__,
"userguide_html": "seasonal_water_yield.html",
"model_name": MODEL_METADATA["seasonal_water_yield"].model_title,
"pyname": MODEL_METADATA["seasonal_water_yield"].pyname,
"userguide_html": MODEL_METADATA["seasonal_water_yield"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["dem_raster_path", "lulc_raster_path",
"soil_group_path", "aoi_path", "l_path",
"monthly_alpha_path"],
"climate_zone_raster_path"],
"different_projections_ok": True,
},
"args": {
@ -245,7 +246,7 @@ ARGS_SPEC = {
"Values are the numbers 1-12 corresponding to each "
"month.")
},
"events": {
"alpha": {
"type": "number",
"units": u.none,
"about": _("The alpha value for that month")

View File

@ -12,6 +12,7 @@ from . import spec_utils
from .spec_utils import u
from . import utils
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -22,9 +23,9 @@ UINT8_NODATA = 255
UINT16_NODATA = 65535
ARGS_SPEC = {
"model_name": _("Stormwater Retention"),
"module": __name__,
"userguide_html": "stormwater.html",
"model_name": MODEL_METADATA["stormwater"].model_title,
"pyname": MODEL_METADATA["stormwater"].pyname,
"userguide_html": MODEL_METADATA["stormwater"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["lulc_path", "soil_group_path", "precipitation_path",
"road_centerlines_path", "aggregate_areas_path"],

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield, MODEL_METADATA
class HydropowerWaterYield(model.InVESTModel):
class AnnualWaterYield(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Hydropower Water Yield',
target=hydropower_water_yield.execute,
validator=hydropower_water_yield.validate,
localdoc='reservoirhydropowerproduction.html')
label=MODEL_METADATA['annual_water_yield'].model_title,
target=annual_water_yield.execute,
validator=annual_water_yield.validate,
localdoc=MODEL_METADATA['annual_water_yield'].userguide)
self.precipitation = inputs.File(
args_key='precipitation_path',

View File

@ -1,16 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.carbon
from natcap.invest import carbon, MODEL_METADATA
class Carbon(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(self,
label=_('InVEST Carbon Model'),
target=natcap.invest.carbon.execute,
validator=natcap.invest.carbon.validate,
localdoc='carbonstorage.html')
model.InVESTModel.__init__(
self,
label=MODEL_METADATA['carbon'].model_title,
target=carbon.execute,
validator=carbon.validate,
localdoc=MODEL_METADATA['carbon'].userguide)
self.cur_lulc_raster = inputs.File(
args_key='lulc_cur_path',

View File

@ -3,8 +3,8 @@
import functools
from natcap.invest.ui import model, inputs
from natcap.invest.coastal_blue_carbon import coastal_blue_carbon
from natcap.invest.coastal_blue_carbon import preprocessor
from natcap.invest.coastal_blue_carbon import coastal_blue_carbon, preprocessor
from natcap.invest import MODEL_METADATA
def _create_input_kwargs_from_args_spec(
@ -35,10 +35,10 @@ class CoastalBlueCarbonPreprocessor(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Coastal Blue Carbon Preprocessor',
label=MODEL_METADATA['coastal_blue_carbon_preprocessor'].model_title,
target=preprocessor.execute,
validator=preprocessor.validate,
localdoc='coastal_blue_carbon.html')
localdoc=MODEL_METADATA['coastal_blue_carbon_preprocessor'].userguide)
_ui_keys = functools.partial(
_create_input_kwargs_from_args_spec,
@ -68,10 +68,10 @@ class CoastalBlueCarbon(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Coastal Blue Carbon',
label=MODEL_METADATA['coastal_blue_carbon'].model_title,
target=coastal_blue_carbon.execute,
validator=coastal_blue_carbon.validate,
localdoc='coastal_blue_carbon.html')
localdoc=MODEL_METADATA['coastal_blue_carbon'].userguide)
_ui_keys = functools.partial(
_create_input_kwargs_from_args_spec,

View File

@ -1,7 +1,7 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
from natcap.invest import coastal_vulnerability
from natcap.invest import coastal_vulnerability, MODEL_METADATA
from osgeo import gdal
@ -10,10 +10,10 @@ class CoastalVulnerability(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Coastal Vulnerability',
label=MODEL_METADATA['coastal_vulnerability'].model_title,
target=coastal_vulnerability.execute,
validator=coastal_vulnerability.validate,
localdoc='coastal_vulnerability.html')
localdoc=MODEL_METADATA['coastal_vulnerability'].userguide)
self.aoi_vector_path = inputs.File(
args_key='aoi_vector_path',

View File

@ -3,16 +3,17 @@ from natcap.invest.ui import model, inputs
import natcap.invest.crop_production_percentile
import natcap.invest.crop_production_regression
from natcap.invest import MODEL_METADATA
class CropProductionPercentile(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Crop Production Percentile Model',
label=MODEL_METADATA['crop_production_percentile'].model_title,
target=natcap.invest.crop_production_percentile.execute,
validator=natcap.invest.crop_production_percentile.validate,
localdoc='crop_production.html')
localdoc=MODEL_METADATA['crop_production_percentile'].userguide)
self.model_data_path = inputs.Folder(
args_key='model_data_path',
@ -107,10 +108,10 @@ class CropProductionRegression(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Crop Production Regression Model',
label=MODEL_METADATA['crop_production_regression'].model_title,
target=natcap.invest.crop_production_regression.execute,
validator=natcap.invest.crop_production_regression.validate,
localdoc='crop_production.html')
localdoc=MODEL_METADATA['crop_production_regression'].userguide)
self.model_data_path = inputs.Folder(
args_key='model_data_path',
@ -151,7 +152,7 @@ class CropProductionRegression(model.InVESTModel):
helptext=(
"A table that maps fertilization rates to crops in "
"the simulation. Must include the headers "
"'crop_name', 'nitrogen_rate', 'phosphorous_rate', "
"'crop_name', 'nitrogen_rate', 'phosphorus_rate', "
"and 'potassium_rate'."),
label='Fertilization Rate Table Path (csv)',
validator=self.validator)

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
from natcap.invest.delineateit import delineateit
from natcap.invest import MODEL_METADATA
class Delineateit(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='DelineateIt: Watershed Delineation',
label=MODEL_METADATA['delineateit'].model_title,
target=delineateit.execute,
validator=delineateit.validate,
localdoc='delineateit.html')
localdoc=MODEL_METADATA['delineateit'].userguide)
self.dem_path = inputs.File(
label='Digital Elevation Model (Raster)',

View File

@ -2,6 +2,7 @@
from natcap.invest.ui import model, inputs
from natcap.invest.finfish_aquaculture import finfish_aquaculture
from natcap.invest import MODEL_METADATA
from osgeo import gdal
@ -10,10 +11,10 @@ class FinfishAquaculture(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Marine Aquaculture: Finfish',
label=MODEL_METADATA['finfish_aquaculture'].model_title,
target=finfish_aquaculture.execute,
validator=finfish_aquaculture.validate,
localdoc='marine_fish.html')
localdoc=MODEL_METADATA['finfish_aquaculture'].userguide)
self.farm_location = inputs.File(
args_key='ff_farm_loc',

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
from natcap.invest.fisheries import fisheries, fisheries_hst
from natcap.invest import MODEL_METADATA
class Fisheries(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Fisheries',
label=MODEL_METADATA['fisheries'].model_title,
target=fisheries.execute,
validator=fisheries.validate,
localdoc='fisheries.html')
localdoc=MODEL_METADATA['fisheries'].userguide)
self.alpha_only = inputs.Label(
text=(
@ -315,10 +316,10 @@ class FisheriesHST(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Fisheries Habitat Scenario Tool',
label=MODEL_METADATA['fisheries_hst'].model_title,
target=fisheries_hst.execute,
validator=fisheries_hst.validate,
localdoc='fisheries.html')
localdoc=MODEL_METADATA['fisheries_hst'].userguide)
self.alpha_only = inputs.Label(
text=(

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.forest_carbon_edge_effect
from natcap.invest import forest_carbon_edge_effect, MODEL_METADATA
class ForestCarbonEdgeEffect(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Forest Carbon Edge Effect Model',
target=natcap.invest.forest_carbon_edge_effect.execute,
validator=natcap.invest.forest_carbon_edge_effect.validate,
localdoc=natcap.invest.forest_carbon_edge_effect.ARGS_SPEC['userguide_html'])
label=MODEL_METADATA['forest_carbon_edge_effect'].model_title,
target=forest_carbon_edge_effect.execute,
validator=forest_carbon_edge_effect.validate,
localdoc=MODEL_METADATA['forest_carbon_edge_effect'].userguide)
self.lulc_raster_path = inputs.File(
args_key='lulc_raster_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.globio
from natcap.invest import globio, MODEL_METADATA
class GLOBIO(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='GLOBIO',
target=natcap.invest.globio.execute,
validator=natcap.invest.globio.validate,
localdoc='globio.html')
label=MODEL_METADATA['globio'].model_title,
target=globio.execute,
validator=globio.validate,
localdoc=MODEL_METADATA['globio'].userguide)
self.lulc_to_globio_table_path = inputs.File(
args_key='lulc_to_globio_table_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.habitat_quality
from natcap.invest import habitat_quality, MODEL_METADATA
class HabitatQuality(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Habitat Quality',
target=natcap.invest.habitat_quality.execute,
validator=natcap.invest.habitat_quality.validate,
localdoc='habitat_quality.html')
label=MODEL_METADATA['habitat_quality'].model_title,
target=habitat_quality.execute,
validator=habitat_quality.validate,
localdoc=MODEL_METADATA['habitat_quality'].userguide)
self.current_landcover = inputs.File(
args_key='lulc_cur_path',
helptext=(

View File

@ -1,16 +1,16 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
from natcap.invest import hra
from natcap.invest import hra, MODEL_METADATA
class HabitatRiskAssessment(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Habitat Risk Assessment',
label=MODEL_METADATA['habitat_risk_assessment'].model_title,
target=hra.execute,
validator=hra.validate,
localdoc='habitat_risk_assessment.html')
localdoc=MODEL_METADATA['habitat_risk_assessment'].userguide)
self.info_table_path = inputs.File(
args_key='info_table_path',

View File

@ -57,14 +57,16 @@ def main():
scroll_area.setWidget(main_widget)
labels_and_buttons = []
for model, model_data in sorted(natcap.invest.MODEL_UIS.items()):
for model_name, model_data in sorted(natcap.invest.MODEL_METADATA.items(),
# sort alphabetically by display name
key=lambda item: item[1].model_title):
row = layout.rowCount()
label = QtWidgets.QLabel()
button = ModelLaunchButton('Launch', model)
button = ModelLaunchButton('Launch', model_name)
labels_and_buttons.append((label, button))
layout.addWidget(
QtWidgets.QLabel(model_data.humanname), row, 0,
QtWidgets.QLabel(model_data.model_title), row, 0,
QtCore.Qt.AlignRight)
layout.addWidget(button, row, 1)

View File

@ -27,11 +27,11 @@ import qtawesome
import natcap.invest
from . import inputs
from . import usage
from . import execution
from .. import cli, MODEL_UIS
from .. import utils
from .. import cli, MODEL_METADATA, utils
from .. import datastack
from .. import usage
from .. import utils
from .. import validation
LOGGER = logging.getLogger(__name__)
@ -1970,7 +1970,7 @@ class InVESTModel(QtWidgets.QMainWindow):
else:
save_filepath = filepath
for internal_model_name, _meta in MODEL_UIS.items():
for internal_model_name, _meta in MODEL_METADATA.items():
if _meta.pyname == self.target.__module__:
break
cli.export_to_python(

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
import natcap.invest.ndr.ndr
from natcap.invest import MODEL_METADATA
class Nutrient(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Nutrient Delivery Ratio Model (NDR)',
label=MODEL_METADATA['ndr'].model_title,
target=natcap.invest.ndr.ndr.execute,
validator=natcap.invest.ndr.ndr.validate,
localdoc='ndr.html')
localdoc=MODEL_METADATA['ndr'].userguide)
self.dem_path = inputs.File(
args_key='dem_path',
@ -70,8 +71,8 @@ class Nutrient(model.InVESTModel):
self.add_input(self.biophysical_table_path)
self.calc_p = inputs.Checkbox(
args_key='calc_p',
helptext='Select to calculate phosphorous export.',
label='Calculate phosphorous retention')
helptext='Select to calculate phosphorus export.',
label='Calculate phosphorus retention')
self.add_input(self.calc_p)
self.calc_n = inputs.Checkbox(
args_key='calc_n',
@ -106,7 +107,7 @@ class Nutrient(model.InVESTModel):
args_key='subsurface_critical_length_p',
helptext='',
interactive=False,
label='Subsurface Critical Length (Phosphorous)',
label='Subsurface Critical Length (Phosphorus)',
validator=self.validator)
self.add_input(self.subsurface_critical_length_p)
self.subsurface_eff_n = inputs.Text(
@ -120,7 +121,7 @@ class Nutrient(model.InVESTModel):
args_key='subsurface_eff_p',
helptext='',
interactive=False,
label='Subsurface Maximum Retention Efficiency (Phosphorous)',
label='Subsurface Maximum Retention Efficiency (Phosphorus)',
validator=self.validator)
self.add_input(self.subsurface_eff_p)

View File

@ -1,9 +1,7 @@
#TODO: all the other UI modules have a # coding=UTF-8 here, does this one need it too?
import logging
from . import inputs
from . import model
from .. import pollination
from natcap.invest.ui import inputs, model
from natcap.invest import pollination, MODEL_METADATA
LOGGER = logging.getLogger(__name__)
@ -12,10 +10,10 @@ class Pollination(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Crop Pollination',
label=MODEL_METADATA['pollination'].model_title,
target=pollination.execute,
validator=pollination.validate,
localdoc='croppollination.html')
localdoc=MODEL_METADATA['pollination'].userguide)
self.landcover_raster_path = inputs.File(
args_key='landcover_raster_path',

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
from natcap.invest.recreation import recmodel_client
from natcap.invest import MODEL_METADATA
class Recreation(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Recreation Model',
label=MODEL_METADATA['recreation'].model_title,
target=recmodel_client.execute,
validator=recmodel_client.validate,
localdoc='recreation.html')
localdoc=MODEL_METADATA['recreation'].userguide)
self.internet_warning = inputs.Label(
text=(

View File

@ -1,16 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
from natcap.invest import routedem
from natcap.invest import routedem, MODEL_METADATA
class RouteDEM(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='RouteDEM',
label=MODEL_METADATA['routedem'].model_title,
target=routedem.execute,
validator=routedem.validate,
localdoc='routedem.html')
localdoc=MODEL_METADATA['routedem'].userguide)
self.dem_path = inputs.File(
args_key='dem_path',

View File

@ -2,9 +2,8 @@
import logging
from natcap.invest.ui import model, inputs
import natcap.invest.scenario_gen_proximity
from natcap.invest import scenario_gen_proximity, MODEL_METADATA
from osgeo import gdal
LOGGER = logging.getLogger(__name__)
@ -13,10 +12,10 @@ class ScenarioGenProximity(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Scenario Generator: Proximity Based',
target=natcap.invest.scenario_gen_proximity.execute,
validator=natcap.invest.scenario_gen_proximity.validate,
localdoc='scenario_gen_proximity.html')
label=MODEL_METADATA['scenario_generator_proximity'].model_title,
target=scenario_gen_proximity.execute,
validator=scenario_gen_proximity.validate,
localdoc=MODEL_METADATA['scenario_generator_proximity'].userguide)
self.base_lulc_path = inputs.File(
args_key='base_lulc_path',

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
from natcap.invest.scenic_quality import scenic_quality
from natcap.invest import MODEL_METADATA
class ScenicQuality(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Scenic Quality',
label=MODEL_METADATA['scenic_quality'].model_title,
target=scenic_quality.execute,
validator=scenic_quality.validate,
localdoc='scenic_quality.html')
localdoc=MODEL_METADATA['scenic_quality'].userguide)
self.general_tab = inputs.Container(
interactive=True,

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
import natcap.invest.sdr.sdr
from natcap.invest import MODEL_METADATA
class SDR(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Sediment Delivery Ratio Model (SDR)',
label=MODEL_METADATA['sdr'].model_title,
target=natcap.invest.sdr.sdr.execute,
validator=natcap.invest.sdr.sdr.validate,
localdoc='sdr.html')
localdoc=MODEL_METADATA['sdr'].userguide)
self.dem_path = inputs.File(
args_key='dem_path',
helptext=(

View File

@ -2,16 +2,17 @@
from natcap.invest.ui import model, inputs
from natcap.invest.seasonal_water_yield import seasonal_water_yield
from natcap.invest import MODEL_METADATA
class SeasonalWaterYield(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Seasonal Water Yield',
label=MODEL_METADATA['seasonal_water_yield'].model_title,
target=seasonal_water_yield.execute,
validator=seasonal_water_yield.validate,
localdoc='seasonal_water_yield.html')
localdoc=MODEL_METADATA['seasonal_water_yield'].userguide)
self.threshold_flow_accumulation = inputs.Text(
args_key='threshold_flow_accumulation',

View File

@ -1,15 +1,15 @@
from natcap.invest.ui import model, inputs
import natcap.invest.stormwater
from natcap.invest import MODEL_METADATA, stormwater
class Stormwater(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='InVEST Stormwater Model',
target=natcap.invest.stormwater.execute,
validator=natcap.invest.stormwater.validate,
localdoc='stormwater.html')
label=MODEL_METADATA['stormwater'].model_title,
target=stormwater.execute,
validator=stormwater.validate,
localdoc=MODEL_METADATA['stormwater'].userguide)
self.lulc_path = inputs.File(
args_key='lulc_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.urban_cooling_model
from natcap.invest import urban_cooling_model, MODEL_METADATA
class UrbanCoolingModel(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Urban Cooling Model',
target=natcap.invest.urban_cooling_model.execute,
validator=natcap.invest.urban_cooling_model.validate,
localdoc='urban_cooling_model.html')
label=MODEL_METADATA['urban_cooling_model'].model_title,
target=urban_cooling_model.execute,
validator=urban_cooling_model.validate,
localdoc=MODEL_METADATA['urban_cooling_model'].userguide)
self.lulc_raster_path = inputs.File(
args_key='lulc_raster_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.urban_flood_risk_mitigation
from natcap.invest import urban_flood_risk_mitigation, MODEL_METADATA
class UrbanFloodRiskMitigation(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='UrbanFloodRiskMitigation',
target=natcap.invest.urban_flood_risk_mitigation.execute,
validator=natcap.invest.urban_flood_risk_mitigation.validate,
localdoc='urban_flood_mitigation.html')
label=MODEL_METADATA['urban_flood_risk_mitigation'].model_title,
target=urban_flood_risk_mitigation.execute,
validator=urban_flood_risk_mitigation.validate,
localdoc=MODEL_METADATA['urban_flood_risk_mitigation'].userguide)
self.aoi_watersheds_path = inputs.File(
args_key='aoi_watersheds_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
import natcap.invest.wave_energy
from natcap.invest import wave_energy, MODEL_METADATA
class WaveEnergy(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Wave Energy',
target=natcap.invest.wave_energy.execute,
validator=natcap.invest.wave_energy.validate,
localdoc='wave_energy.html')
label=MODEL_METADATA['wave_energy'].model_title,
target=wave_energy.execute,
validator=wave_energy.validate,
localdoc=MODEL_METADATA['wave_energy'].userguide)
self.wave_base_data = inputs.Folder(
args_key='wave_base_data_path',

View File

@ -1,17 +1,17 @@
# coding=UTF-8
from natcap.invest.ui import model, inputs
from natcap.invest import wind_energy
from natcap.invest import wind_energy, MODEL_METADATA
class WindEnergy(model.InVESTModel):
def __init__(self):
model.InVESTModel.__init__(
self,
label='Wind Energy',
label=MODEL_METADATA['wind_energy'].model_title,
target=wind_energy.execute,
validator=wind_energy.validate,
localdoc='wind_energy.html'
localdoc=MODEL_METADATA['wind_energy'].userguide
)
self.wind_data = inputs.File(

View File

@ -1,5 +1,4 @@
"""A Flask app with HTTP endpoints used by the InVEST Workbench."""
import collections
import importlib
import json
import logging
@ -9,21 +8,19 @@ from flask import Flask
from flask import request
from natcap.invest import cli, MODEL_UIS
from natcap.invest import datastack
from natcap.invest import install_language
from natcap.invest import install_language, MODEL_METADATA
from natcap.invest import spec_utils
from natcap.invest import usage
logging.basicConfig(level=logging.DEBUG)
LOGGER = logging.getLogger(__name__)
app = Flask(__name__)
# Lookup names to pass to `invest run` based on python module names
_UI_META = collections.namedtuple('UIMeta', ['run_name', 'human_name'])
MODULE_MODELRUN_MAP = {
value.pyname: _UI_META(
run_name=key,
human_name=value.humanname)
for key, value in MODEL_UIS.items()}
PYNAME_TO_MODEL_NAME_MAP = {
metadata.pyname: model_name
for model_name, metadata in MODEL_METADATA.items()
}
def shutdown_server():
@ -73,7 +70,7 @@ def get_invest_getspec():
"""
# this will be the model key name, not language specific
target_model = request.get_json()
target_module = MODEL_UIS[target_model].pyname
target_module = MODEL_METADATA[target_model].pyname
install_language(request.args.get('language', 'en'))
model_module = importlib.import_module(name=target_module)
return spec_utils.serialize_args_spec(model_module.ARGS_SPEC)
@ -151,13 +148,13 @@ def post_datastack_file():
filepath = request.get_json()
stack_type, stack_info = datastack.get_datastack_info(
filepath)
run_name, human_name = MODULE_MODELRUN_MAP[stack_info.model_name]
model_name = PYNAME_TO_MODEL_NAME_MAP[stack_info.model_name]
result_dict = {
'type': stack_type,
'args': stack_info.args,
'module_name': stack_info.model_name,
'model_run_name': run_name,
'model_human_name': human_name,
'model_run_name': model_name,
'model_human_name': MODEL_METADATA[model_name].model_title,
'invest_version': stack_info.invest_version
}
return json.dumps(result_dict)
@ -208,3 +205,23 @@ def save_to_python():
save_filepath, modelname, args_dict)
return 'python script saved'
@app.route('/log_model_start', methods=['POST'])
def log_model_start():
payload = request.get_json()
usage._log_model(
payload['model_pyname'],
json.loads(payload['model_args']),
payload['invest_interface'],
payload['session_id'])
return 'OK'
@app.route('/log_model_exit', methods=['POST'])
def log_model_exit():
payload = request.get_json()
usage._log_exit_status(
payload['session_id'],
payload['status'])
return 'OK'

View File

@ -21,15 +21,16 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
TARGET_NODATA = -1
_LOGGING_PERIOD = 5.0
ARGS_SPEC = {
"model_name": _("Urban Cooling Model"),
'module': __name__,
"userguide_html": "urban_cooling_model.html",
"model_name": MODEL_METADATA["urban_cooling_model"].model_title,
"pyname": MODEL_METADATA["urban_cooling_model"].pyname,
"userguide_html": MODEL_METADATA["urban_cooling_model"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["lulc_raster_path", "ref_eto_raster_path",
"aoi_vector_path", "building_vector_path"],

View File

@ -17,13 +17,14 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Urban Flood Risk Mitigation"),
"module": __name__,
"userguide_html": "urban_flood_risk_mitigation.html",
"model_name": MODEL_METADATA["urban_flood_risk_mitigation"].model_title,
"pyname": MODEL_METADATA["urban_flood_risk_mitigation"].pyname,
"userguide_html": MODEL_METADATA["urban_flood_risk_mitigation"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ["aoi_watersheds_path", "lulc_path",
"built_infrastructure_vector_path",
@ -757,8 +758,30 @@ def _lu_to_cn_op(
# pixel and the rows are the curve number index for the landcover
# type under that pixel (0..3 are CN_A..CN_D and 4 is "unknown")
valid_lucodes = lucode_array[valid_mask].astype(int)
try:
cn_matrix = lucode_to_cn_table[valid_lucodes]
except IndexError:
# Find the code that raised the IndexError, and possibly
# any others that also would have.
lucodes = numpy.unique(valid_lucodes)
missing_codes = lucodes[lucodes >= lucode_to_cn_table.shape[0]]
raise ValueError(
f'The biophysical table is missing a row for lucode(s) '
f'{missing_codes.tolist()}')
# Even without an IndexError, still must guard against
# lucodes that can index into the sparse matrix but were
# missing from the biophysical table. They have rows of all 0.
if not cn_matrix.sum(1).all():
empty_rows = numpy.where(lucode_to_cn_table.sum(1) == 0)
missing_codes = numpy.intersect1d(valid_lucodes, empty_rows)
raise ValueError(
f'The biophysical table is missing a row for lucode(s) '
f'{missing_codes.tolist()}')
per_pixel_cn_array = (
lucode_to_cn_table[valid_lucodes].toarray().reshape(
cn_matrix.toarray().reshape(
(-1, 4))).transpose()
# this is the soil type array with values ranging from 0..4 that will

View File

@ -15,12 +15,11 @@ import importlib
from urllib.request import urlopen, Request
from urllib.parse import urlencode
from osgeo import gdal
from osgeo import osr
import natcap.invest
import pygeoprocessing
from .. import utils
from . import utils
ENCODING = sys.getfilesystemencoding()
LOGGER = logging.getLogger(__name__)
@ -36,19 +35,21 @@ _USAGE_LOGGING_THREAD_NAME = 'usage-logging-thread'
@contextlib.contextmanager
def log_run(module, args):
def log_run(model_pyname, args):
"""Context manager to log an InVEST model run and exit status.
Args:
module (string): The string module name that identifies the model.
model_pyname (string): The string module name that identifies the model.
args (dict): The full args dictionary.
Returns:
``None``
"""
invest_interface = 'Qt' # this cm is only used by the Qt interface
session_id = str(uuid.uuid4())
log_thread = threading.Thread(
target=_log_model, args=(module, args, session_id),
target=_log_model,
args=(model_pyname, args, invest_interface, session_id),
name=_USAGE_LOGGING_THREAD_NAME)
log_thread.start()
@ -191,17 +192,19 @@ def _log_exit_status(session_id, status):
urlopen(Request(log_finish_url, urlencode(payload).encode('utf-8')))
except Exception as exception:
# An exception was thrown, we don't care.
logger.warn(
logger.warning(
'an exception encountered when _log_exit_status %s',
str(exception))
def _log_model(model_name, model_args, session_id=None):
def _log_model(pyname, model_args, invest_interface, session_id=None):
"""Log information about a model run to a remote server.
Args:
model_name (string): a python string of the package version.
pyname (string): a python string of the package version.
model_args (dict): the traditional InVEST argument dictionary.
invest_interface (string): a string identifying the calling UI,
e.g. `Qt` or 'Workbench'.
Returns:
None
@ -221,15 +224,16 @@ def _log_model(model_name, model_args, session_id=None):
md5.update(json.dumps(data).encode('utf-8'))
return md5.hexdigest()
args_spec = importlib.import_module(model_name).ARGS_SPEC
args_spec = importlib.import_module(pyname).ARGS_SPEC
try:
bounding_box_intersection, bounding_box_union = (
_calculate_args_bounding_box(model_args, args_spec))
payload = {
'model_name': model_name,
'model_name': pyname,
'invest_release': natcap.invest.__version__,
'invest_interface': invest_interface,
'node_hash': _node_hash(),
'system_full_platform_string': platform.platform(),
'system_preferred_encoding': locale.getdefaultlocale()[1],
@ -246,5 +250,5 @@ def _log_model(model_name, model_args, session_id=None):
urlopen(Request(log_start_url, urlencode(payload).encode('utf-8')))
except Exception as exception:
# An exception was thrown, we don't care.
logger.warn(
logger.warning(
'an exception encountered when logging %s', repr(exception))

View File

@ -155,7 +155,10 @@ def prepare_workspace(
logging_level=logging_level):
with sandbox_tempdir(dir=workspace):
logging.captureWarnings(True)
LOGGER.info('Writing log messages to %s', logfile)
# If invest is launched as a subprocess (e.g. the Workbench)
# the parent process can rely on this announcement to know the
# logfile path, and to know the invest process has started.
LOGGER.log(100, 'Writing log messages to %s', logfile)
start_time = time.time()
try:
yield

View File

@ -21,13 +21,14 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
ARGS_SPEC = {
"model_name": _("Wave Energy"),
"module": __name__,
"userguide_html": "wave_energy.html",
"model_name": MODEL_METADATA["wave_energy"].model_title,
"pyname": MODEL_METADATA["wave_energy"].pyname,
"userguide_html": MODEL_METADATA["wave_energy"].userguide,
"args": {
"workspace_dir": spec_utils.WORKSPACE,
"results_suffix": spec_utils.SUFFIX,

View File

@ -26,15 +26,16 @@ from . import utils
from . import spec_utils
from .spec_utils import u
from . import validation
from . import MODEL_METADATA
LOGGER = logging.getLogger(__name__)
speedups.enable()
ARGS_SPEC = {
"model_name": _("Wind Energy"),
"module": __name__,
"userguide_html": "wind_energy.html",
"model_name": MODEL_METADATA["wind_energy"].model_title,
"pyname": MODEL_METADATA["wind_energy"].pyname,
"userguide_html": MODEL_METADATA["wind_energy"].userguide,
"args_with_spatial_overlap": {
"spatial_keys": ['aoi_vector_path', 'bathymetry_path',
'land_polygon_vector_path'],

View File

@ -1,4 +1,4 @@
"""Module for Regression Testing the InVEST Hydropower module."""
"""Module for Regression Testing the InVEST Annual Water Yield module."""
import unittest
import tempfile
import shutil
@ -8,15 +8,14 @@ import pandas
import numpy
import pygeoprocessing
SAMPLE_DATA = os.path.join(
os.path.dirname(__file__), '..', 'data', 'invest-test-data', 'hydropower',
'input')
REGRESSION_DATA = os.path.join(
os.path.dirname(__file__), '..', 'data', 'invest-test-data', 'hydropower')
os.path.dirname(__file__), '..', 'data', 'invest-test-data', 'annual_water_yield')
SAMPLE_DATA = os.path.join(REGRESSION_DATA, 'input')
class HydropowerTests(unittest.TestCase):
"""Regression Tests for Annual Water Yield Hydropower Model."""
class AnnualWaterYieldTests(unittest.TestCase):
"""Regression Tests for Annual Water Yield Model."""
def setUp(self):
"""Overriding setUp func. to create temporary workspace directory."""
@ -50,9 +49,9 @@ class HydropowerTests(unittest.TestCase):
def test_invalid_lulc_veg(self):
"""Hydro: catching invalid LULC_veg values."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
new_lulc_veg_path = os.path.join(self.workspace_dir,
'new_lulc_veg.csv')
@ -63,7 +62,7 @@ class HydropowerTests(unittest.TestCase):
args['biophysical_table_path'] = new_lulc_veg_path
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
self.assertTrue('veg value must be either 1 or 0' in str(cm.exception))
table_df = pandas.read_csv(args['biophysical_table_path'])
@ -72,14 +71,14 @@ class HydropowerTests(unittest.TestCase):
args['biophysical_table_path'] = new_lulc_veg_path
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
self.assertTrue('veg value must be either 1 or 0' in str(cm.exception))
def test_missing_lulc_value(self):
"""Hydro: catching missing LULC value in Biophysical table."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
# remove a row from the biophysical table so that lulc value is missing
bad_biophysical_path = os.path.join(
@ -93,16 +92,16 @@ class HydropowerTests(unittest.TestCase):
args['biophysical_table_path'] = bad_biophysical_path
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
self.assertTrue(
"The missing values found in the LULC raster but not the table"
" are: [2]" in str(cm.exception))
def test_missing_lulc_demand_value(self):
"""Hydro: catching missing LULC value in Demand table."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
args['demand_table_path'] = os.path.join(
SAMPLE_DATA, 'water_demand_table.csv')
@ -121,21 +120,21 @@ class HydropowerTests(unittest.TestCase):
args['demand_table_path'] = bad_demand_path
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
self.assertTrue(
"The missing values found in the LULC raster but not the table"
" are: [2]" in str(cm.exception))
def test_water_yield_subshed(self):
"""Hydro: testing water yield component only w/ subwatershed."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
from natcap.invest import utils
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
args['sub_watersheds_path'] = os.path.join(
SAMPLE_DATA, 'subwatersheds.shp')
args['results_suffix'] = 'test'
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
raster_results = ['aet_test.tif', 'fractp_test.tif', 'wyield_test.tif']
for raster_path in raster_results:
@ -169,16 +168,16 @@ class HydropowerTests(unittest.TestCase):
def test_scarcity_subshed(self):
"""Hydro: testing Scarcity component w/ subwatershed."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
from natcap.invest import utils
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
args['demand_table_path'] = os.path.join(
SAMPLE_DATA, 'water_demand_table.csv')
args['sub_watersheds_path'] = os.path.join(
SAMPLE_DATA, 'subwatersheds.shp')
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
raster_results = ['aet.tif', 'fractp.tif', 'wyield.tif']
for raster_path in raster_results:
@ -207,10 +206,10 @@ class HydropowerTests(unittest.TestCase):
def test_valuation_subshed(self):
"""Hydro: testing Valuation component w/ subwatershed."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import annual_water_yield
from natcap.invest import utils
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
args['demand_table_path'] = os.path.join(
SAMPLE_DATA, 'water_demand_table.csv')
args['valuation_table_path'] = os.path.join(
@ -218,7 +217,7 @@ class HydropowerTests(unittest.TestCase):
args['sub_watersheds_path'] = os.path.join(
SAMPLE_DATA, 'subwatersheds.shp')
hydropower_water_yield.execute(args)
annual_water_yield.execute(args)
raster_results = ['aet.tif', 'fractp.tif', 'wyield.tif']
for raster_path in raster_results:
@ -247,34 +246,33 @@ class HydropowerTests(unittest.TestCase):
def test_validation(self):
"""Hydro: test failure cases on the validation function."""
from natcap.invest.hydropower import hydropower_water_yield
from natcap.invest import validation
from natcap.invest import annual_water_yield, validation
args = HydropowerTests.generate_base_args(self.workspace_dir)
args = AnnualWaterYieldTests.generate_base_args(self.workspace_dir)
# default args should be fine
self.assertEqual(hydropower_water_yield.validate(args), [])
self.assertEqual(annual_water_yield.validate(args), [])
args_bad_vector = args.copy()
args_bad_vector['watersheds_path'] = args_bad_vector['eto_path']
bad_vector_list = hydropower_water_yield.validate(args_bad_vector)
bad_vector_list = annual_water_yield.validate(args_bad_vector)
self.assertTrue('not be opened as a GDAL vector'
in bad_vector_list[0][1])
args_bad_raster = args.copy()
args_bad_raster['eto_path'] = args_bad_raster['watersheds_path']
bad_raster_list = hydropower_water_yield.validate(args_bad_raster)
bad_raster_list = annual_water_yield.validate(args_bad_raster)
self.assertTrue('not be opened as a GDAL raster'
in bad_raster_list[0][1])
args_bad_file = args.copy()
args_bad_file['eto_path'] = 'non_existant_file.tif'
bad_file_list = hydropower_water_yield.validate(args_bad_file)
bad_file_list = annual_water_yield.validate(args_bad_file)
self.assertTrue('File not found' in bad_file_list[0][1])
args_missing_key = args.copy()
del args_missing_key['eto_path']
validation_warnings = hydropower_water_yield.validate(
validation_warnings = annual_water_yield.validate(
args_missing_key)
self.assertEqual(
validation_warnings,
@ -297,7 +295,7 @@ class HydropowerTests(unittest.TestCase):
args_bad_biophysical_table['biophysical_table_path'] = (
bad_biophysical_path)
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args_bad_biophysical_table)
annual_water_yield.execute(args_bad_biophysical_table)
actual_message = str(cm.exception)
self.assertTrue(
"The missing values found in the LULC raster but not the table"
@ -336,7 +334,7 @@ class HydropowerTests(unittest.TestCase):
# ensure that a missing watershed id the valuation table will
# raise an exception that's helpful
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args_bad_demand_table)
annual_water_yield.execute(args_bad_demand_table)
actual_message = str(cm.exception)
self.assertTrue(
"The missing values found in the LULC raster but not the table"
@ -363,7 +361,7 @@ class HydropowerTests(unittest.TestCase):
break
with self.assertRaises(ValueError) as cm:
hydropower_water_yield.execute(args_bad_valuation_table)
annual_water_yield.execute(args_bad_valuation_table)
actual_message = str(cm.exception)
self.assertTrue(
'but are not found in the valuation table' in

View File

@ -2,7 +2,7 @@ import importlib
import re
import unittest
from natcap.invest import MODEL_UIS
from natcap.invest import MODEL_METADATA
import pint
@ -49,9 +49,10 @@ class ValidateArgsSpecs(unittest.TestCase):
def test_model_specs_are_valid(self):
"""ARGS_SPEC: test each spec meets the expected pattern."""
required_keys = {'model_name', 'module', 'userguide_html', 'args'}
required_keys = {'model_name', 'pyname', 'userguide_html', 'args'}
optional_spatial_key = 'args_with_spatial_overlap'
for model_name, metadata in MODEL_UIS.items():
for model_name, metadata in MODEL_METADATA.items():
# metadata is a collections.namedtuple, fields accessible by name
model = importlib.import_module(metadata.pyname)
@ -333,7 +334,7 @@ class ValidateArgsSpecs(unittest.TestCase):
"""ARGS_SPEC: test each ARGS_SPEC can serialize to JSON."""
from natcap.invest import spec_utils
for model_name, metadata in MODEL_UIS.items():
for model_name, metadata in MODEL_METADATA.items():
model = importlib.import_module(metadata.pyname)
try:
_ = spec_utils.serialize_args_spec(model.ARGS_SPEC)

View File

@ -406,7 +406,7 @@ class CLIUnitTests(unittest.TestCase):
def test_export_to_python_default_args(self):
"""Export a python script w/ default args for a model."""
from natcap.invest import cli
from natcap.invest import cli, MODEL_METADATA
filename = 'foo.py'
target_filepath = os.path.join(self.workspace_dir, filename)
@ -416,7 +416,7 @@ class CLIUnitTests(unittest.TestCase):
self.assertTrue(os.path.exists(target_filepath))
target_model = cli._MODEL_UIS[target_model].pyname
target_model = MODEL_METADATA[target_model].pyname
model_module = importlib.import_module(name=target_model)
spec = model_module.ARGS_SPEC
expected_args = {key: '' for key in spec['args'].keys()}

View File

@ -53,7 +53,7 @@ def make_access_shp(access_shp_path):
# Setup parameters for creating point shapefile
fields = {'FID': ogr.OFTInteger64, 'ACCESS': ogr.OFTReal}
attrs = [{'FID': 0, 'ACCESS': 0.2}, {'FI': 1, 'ACCESS': 1.0}]
attrs = [{'FID': 0, 'ACCESS': 0.2}, {'FID': 1, 'ACCESS': 1.0}]
poly_geoms = {
'poly_1': [(pos_x, pos_y), (pos_x + 100, pos_y),

View File

@ -1148,3 +1148,39 @@ class SWYValidationTests(unittest.TestCase):
expected_missing_keys.difference_update(
{'monthly_alpha', 'alpha_m'})
self.assertEqual(invalid_keys, expected_missing_keys)
def test_all_inputs_valid(self):
"""SWY Validate: assert valid inputs have no validation errors."""
from natcap.invest.seasonal_water_yield import seasonal_water_yield
args = SeasonalWaterYieldRegressionTests.generate_base_args(
self.workspace_dir)
args.update({
'user_defined_climate_zones': False,
'user_defined_local_recharge': False,
'monthly_alpha': False})
# first test with none of the optional params
validation_errors = seasonal_water_yield.validate(args)
self.assertEqual(validation_errors, [])
cz_csv_path = os.path.join(self.workspace_dir, 'cz.csv')
make_climate_zone_csv(cz_csv_path)
cz_ras_path = os.path.join(args['workspace_dir'], 'dem.tif')
make_gradient_raster(cz_ras_path)
args['climate_zone_raster_path'] = cz_ras_path
args['climate_zone_table_path'] = cz_csv_path
args['user_defined_climate_zones'] = True
recharge_ras_path = os.path.join(self.workspace_dir, 'L.tif')
make_recharge_raster(recharge_ras_path)
args['l_path'] = recharge_ras_path
args['user_defined_local_recharge'] = True
alpha_csv_path = os.path.join(self.workspace_dir, 'monthly_alpha.csv')
make_alpha_csv(alpha_csv_path)
args['monthly_alpha_path'] = alpha_csv_path
args['monthly_alpha'] = True
# test with all of the optional params
validation_errors = seasonal_water_yield.validate(args)
self.assertEqual(validation_errors, [])

View File

@ -149,10 +149,53 @@ class UFRMTests(unittest.TestCase):
with self.assertRaises(ValueError) as cm:
urban_flood_risk_mitigation.execute(args)
actual_message = str(cm.exception)
expected_message = (
'Check that the Soil Group raster does not contain')
self.assertTrue(expected_message in actual_message)
actual_message = str(cm.exception)
expected_message = (
'Check that the Soil Group raster does not contain')
self.assertTrue(expected_message in actual_message)
def test_ufrm_value_error_on_bad_lucode(self):
"""UFRM: assert exception on missing lucodes."""
import pandas
from natcap.invest import urban_flood_risk_mitigation
args = self._make_args()
bad_cn_table_path = os.path.join(
self.workspace_dir, 'bad_cn_table.csv')
cn_table = pandas.read_csv(args['curve_number_table_path'])
# drop a row with an lucode known to exist in lulc raster
# This is a code that will successfully index into the
# CN table sparse matrix, but will not return valid data.
bad_cn_table = cn_table[cn_table['lucode'] != 0]
bad_cn_table.to_csv(bad_cn_table_path, index=False)
args['curve_number_table_path'] = bad_cn_table_path
with self.assertRaises(ValueError) as cm:
urban_flood_risk_mitigation.execute(args)
actual_message = str(cm.exception)
expected_message = (
f'The biophysical table is missing a row for lucode(s) {[0]}')
self.assertEqual(expected_message, actual_message)
# drop rows with lucodes known to exist in lulc raster
# These are codes that will raise an IndexError on
# indexing into the CN table sparse matrix. The test
# LULC raster has values from 0 to 21.
bad_cn_table = cn_table[cn_table['lucode'] < 15]
bad_cn_table.to_csv(bad_cn_table_path, index=False)
args['curve_number_table_path'] = bad_cn_table_path
with self.assertRaises(ValueError) as cm:
urban_flood_risk_mitigation.execute(args)
actual_message = str(cm.exception)
expected_message = (
f'The biophysical table is missing a row for lucode(s) '
f'{[16, 17, 18, 21]}')
self.assertEqual(expected_message, actual_message)
def test_ufrm_string_damage_to_infrastructure(self):
"""UFRM: handle str(int) structure indices.

View File

@ -3,6 +3,9 @@ import os
import shutil
import tempfile
import unittest
from unittest.mock import Mock, patch
from natcap.invest import ui_server
TEST_DATA_PATH = os.path.join(
os.path.dirname(__file__), '..', 'data', 'invest-test-data')
@ -23,7 +26,6 @@ class EndpointFunctionTests(unittest.TestCase):
def test_get_vector_colnames(self):
"""UI server: get_vector_colnames endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
# an empty path
response = test_client.post('/colnames', json={'vector_path': ''})
@ -45,33 +47,31 @@ class EndpointFunctionTests(unittest.TestCase):
def test_get_invest_models(self):
"""UI server: get_invest_models endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
response = test_client.get('/models')
models_dict = json.loads(response.get_data(as_text=True))
for model in models_dict.values():
self.assertEqual(set(model), {'internal_name', 'aliases'})
self.assertEqual(set(model), {'model_name', 'aliases'})
def test_get_invest_spec(self):
"""UI server: get_invest_spec endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
response = test_client.post('/getspec', json='sdr')
spec = json.loads(response.get_data(as_text=True))
self.assertEqual(
set(spec),
{'model_name', 'module', 'userguide_html',
{'model_name', 'pyname', 'userguide_html',
'args_with_spatial_overlap', 'args'})
def test_get_invest_validate(self):
"""UI server: get_invest_validate endpoint."""
from natcap.invest import ui_server, carbon
from natcap.invest import carbon
test_client = ui_server.app.test_client()
args = {
'workspace_dir': 'foo'
}
payload = {
'model_module': carbon.ARGS_SPEC['module'],
'model_module': carbon.ARGS_SPEC['pyname'],
'args': json.dumps(args)
}
response = test_client.post('/validate', json=payload)
@ -83,7 +83,6 @@ class EndpointFunctionTests(unittest.TestCase):
def test_post_datastack_file(self):
"""UI server: post_datastack_file endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
self.workspace_dir = tempfile.mkdtemp()
expected_datastack = {
@ -105,7 +104,6 @@ class EndpointFunctionTests(unittest.TestCase):
def test_write_parameter_set_file(self):
"""UI server: write_parameter_set_file endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
self.workspace_dir = tempfile.mkdtemp()
filepath = os.path.join(self.workspace_dir, 'datastack.json')
@ -126,7 +124,6 @@ class EndpointFunctionTests(unittest.TestCase):
def test_save_to_python(self):
"""UI server: save_to_python endpoint."""
from natcap.invest import ui_server
test_client = ui_server.app.test_client()
self.workspace_dir = tempfile.mkdtemp()
filepath = os.path.join(self.workspace_dir, 'script.py')
@ -140,3 +137,35 @@ class EndpointFunctionTests(unittest.TestCase):
_ = test_client.post('/save_to_python', json=payload)
# test_cli.py asserts the actual contents of the file
self.assertTrue(os.path.exists(filepath))
@patch('natcap.invest.ui_server.usage.urlopen')
def test_log_model_start(self, mock_urlopen):
"""UI server: log_model_start endpoint."""
mock_response = Mock()
mock_response.read.return_value = '{"START": "http://foo.org/bar.html"}'
mock_urlopen.return_value = mock_response
test_client = ui_server.app.test_client()
payload = {
'model_pyname': 'natcap.invest.carbon',
'model_args': json.dumps({
'workspace_dir': 'foo'
}),
'invest_interface': 'Workbench',
'session_id': '12345'
}
response = test_client.post('/log_model_start', json=payload)
self.assertEqual(response.get_data(as_text=True), 'OK')
@patch('natcap.invest.ui_server.usage.urlopen')
def test_log_model_exit(self, mock_urlopen):
"""UI server: log_model_start endpoint."""
mock_response = Mock()
mock_response.read.return_value = '{"FINISH": "http://foo.org/bar.html"}'
mock_urlopen.return_value = mock_response
test_client = ui_server.app.test_client()
payload = {
'session_id': '12345',
'status': ''
}
response = test_client.post('/log_model_exit', json=payload)
self.assertEqual(response.get_data(as_text=True), 'OK')

View File

@ -14,8 +14,8 @@ import numpy
import numpy.testing
class ModelLoggingTests(unittest.TestCase):
"""Tests for the InVEST model logging framework."""
class UsageLoggingTests(unittest.TestCase):
"""Tests for the InVEST usage logging framework."""
def setUp(self):
"""Initalize a workspace."""
@ -28,7 +28,7 @@ class ModelLoggingTests(unittest.TestCase):
def test_bounding_boxes(self):
"""Usage logger test that we can extract bounding boxes."""
from natcap.invest import utils
from natcap.invest.ui import usage
from natcap.invest import usage
srs = osr.SpatialReference()
srs.ImportFromEPSG(32731) # WGS84 / UTM zone 31s
@ -81,7 +81,7 @@ class ModelLoggingTests(unittest.TestCase):
numpy.testing.assert_allclose(
bb_inter, [-87.234108, -85.526151, -87.233424, -85.526205])
numpy.testing.assert_allclose(
bb_union, [-87.237771, -85.526132, -87.23321 , -85.526491])
bb_union, [-87.237771, -85.526132, -87.23321, -85.526491])
# Verify that no errors were raised in calculating the bounding boxes.
self.assertTrue('ERROR' not in open(output_logfile).read(),