"""InVEST Urban Heat Island Mitigation model tests."""
import os
import shutil
import tempfile
import unittest

import numpy
import pandas
from osgeo import gdal, osr, ogr
import pygeoprocessing
from shapely import Polygon

gdal.UseExceptions()
REGRESSION_DATA = os.path.join(
    os.path.dirname(__file__), '..', 'data', 'invest-test-data', 'ucm')


def make_simple_vector(path_to_shp):
    """
    Generate shapefile with one rectangular polygon
    Args:
        path_to_shp (str): path to target shapefile
    Returns:
        None
    """
    # (xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax), (xmin, ymin)
    shapely_geometry_list = [
        Polygon([(461251, 4923195), (461501, 4923195),
                 (461501, 4923445), (461251, 4923445),
                 (461251, 4923195)])
    ]

    srs = osr.SpatialReference()
    srs.ImportFromEPSG(26910)
    projection_wkt = srs.ExportToWkt()

    vector_format = "ESRI Shapefile"
    fields = {"id": ogr.OFTReal}
    attribute_list = [{"id": 0}]

    pygeoprocessing.shapely_geometry_to_vector(shapely_geometry_list,
                                               path_to_shp, projection_wkt,
                                               vector_format, fields,
                                               attribute_list)


def make_simple_raster(base_raster_path, array):
    """Create a raster on designated path with arbitrary values.
    Args:
        base_raster_path (str): the raster path for making the new raster.
    Returns:
        None.
    """
    # UTM Zone 10N
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(26910)
    projection_wkt = srs.ExportToWkt()

    origin = (461251, 4923445)
    pixel_size = (30, -30)
    no_data = -1

    pygeoprocessing.numpy_array_to_raster(
        array, no_data, pixel_size, origin, projection_wkt,
        base_raster_path)


class UCMTests(unittest.TestCase):
    """Regression tests for InVEST Urban Cooling Model."""

    def setUp(self):
        """Initialize UCM Regression tests."""
        self.workspace_dir = tempfile.mkdtemp()

    def tearDown(self):
        """Clean up remaining files."""
        shutil.rmtree(self.workspace_dir)

    def test_ucm_regression_factors(self):
        """UCM: regression: CC Factors."""
        import natcap.invest.urban_cooling_model
        args = {
            'workspace_dir': os.path.join(self.workspace_dir, 'workspace'),
            'results_suffix': 'test_suffix',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'factors',
            'do_energy_valuation': True,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            'cc_weight_shade': '',  # to trigger default of 0.6
            'cc_weight_albedo': None,  # to trigger default of 0.2
            # Purposefully excluding cc_weight_eti to trigger default of 0.2
            'n_workers': -1,
        }

        natcap.invest.urban_cooling_model.execute(args)
        results_vector = gdal.OpenEx(os.path.join(
            args['workspace_dir'],
            'uhi_results_%s.shp' % args['results_suffix']))
        results_layer = results_vector.GetLayer()
        results_feature = results_layer.GetFeature(1)

        expected_results = {
            'avg_cc': 0.221991,
            'avg_tmp_v': 37.303789,
            'avg_tmp_an': 2.303789,
            'avd_eng_cn': 3602851.784639,
            'avg_wbgt_v': 32.585935,
            'avg_ltls_v': 75.000000000000000,
            'avg_hvls_v': 75.000000000000000,
        }

        try:
            for key, expected_value in expected_results.items():
                actual_value = float(results_feature.GetField(key))
                # These accumulated values (esp. avd_eng_cn) are accumulated
                # and may differ slightly from expected regression values.
                numpy.testing.assert_allclose(actual_value, expected_value,
                                              rtol=1e-4)
        finally:
            results_layer = None
            results_vector = None

        # Assert that the decimal value of the energy savings value is what we
        # expect.
        expected_energy_sav = 3641030.461044

        energy_sav = 0.0
        n_nonetype = 0
        stats_vector_path = (
            os.path.join(args['workspace_dir'],
                         ('buildings_with_stats_%s.shp' %
                          args['results_suffix'])))
        try:
            buildings_vector = gdal.OpenEx(stats_vector_path)
            buildings_layer = buildings_vector.GetLayer()
            for building_feature in buildings_layer:
                try:
                    energy_sav += building_feature.GetField('energy_sav')
                except TypeError:
                    # When energy_sav is NoneType
                    n_nonetype += 1

            # Expected energy savings is an accumulated value and may differ
            # past about 4 decimal places.
            numpy.testing.assert_allclose(energy_sav, expected_energy_sav, rtol=1e-4)
            self.assertEqual(n_nonetype, 136)
        finally:
            buildings_layer = None
            buildings_vector = None

        # Now, re-run the model with the cost column and verify cost sum is
        # reasonable.  Re-running within the same test function allows us to
        # take advantage of taskgraph.
        new_csv_path = os.path.join(self.workspace_dir, 'cost_csv.csv')
        multiplier = 3.0
        df = pandas.read_csv(args['energy_consumption_table_path'])
        df['Cost'] = pandas.Series([multiplier], index=df.index)
        df.to_csv(new_csv_path)
        args['energy_consumption_table_path'] = new_csv_path

        natcap.invest.urban_cooling_model.execute(args)

        # Assert that the decimal value of the energy savings value is what we
        # expect.  Because we're re-running the model in the same workspace,
        # taskgraph should only re-compute the output vector step.
        expected_energy_sav = expected_energy_sav * multiplier
        energy_sav = 0.0
        n_nonetype = 0
        try:
            buildings_vector = gdal.OpenEx(os.path.join(
                args['workspace_dir'], ('buildings_with_stats_%s.shp' %
                                        args['results_suffix'])))
            buildings_layer = buildings_vector.GetLayer()
            for building_feature in buildings_layer:
                try:
                    energy_sav += building_feature.GetField('energy_sav')
                except TypeError:
                    # When energy_sav is Nonetype
                    n_nonetype += 1

            # These accumulated values are accumulated
            # and may differ past about 4 decimal places.
            numpy.testing.assert_allclose(energy_sav, expected_energy_sav,
                                          rtol=1e-4)
            self.assertEqual(n_nonetype, 136)
        finally:
            buildings_layer = None
            buildings_vector = None

    def test_ucm_regression_intensity(self):
        """UCM: regression: CC Building Intensity."""
        import natcap.invest.urban_cooling_model
        args = {
            'workspace_dir': os.path.join(self.workspace_dir, 'workspace'),
            'results_suffix': 'test_suffix',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'intensity',  # main difference in the reg. tests
            'do_energy_valuation': True,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            'cc_weight_shade': '0.6',
            'cc_weight_albedo': '0.2',
            'cc_weight_eti': '0.2',
            'n_workers': -1,
        }

        natcap.invest.urban_cooling_model.execute(args)
        results_vector = gdal.OpenEx(os.path.join(
            args['workspace_dir'],
            'uhi_results_%s.shp' % args['results_suffix']))
        results_layer = results_vector.GetLayer()
        results_feature = results_layer.GetFeature(1)

        expected_results = {
            'avg_cc': 0.422250,
            'avg_tmp_v': 36.621779,
            'avg_tmp_an': 1.621779,
            'avd_eng_cn': 7148968.928616,
            'avg_wbgt_v': 31.92365,
            'avg_ltls_v': 29.380548,
            'avg_hvls_v': 75.000000000000000,
        }
        try:
            for key, expected_value in expected_results.items():
                actual_value = float(results_feature.GetField(key))
                # These accumulated values (esp. avd_eng_cn) are accumulated
                # and may differ slightly.
                numpy.testing.assert_allclose(actual_value, expected_value,
                                              rtol=1e-4)
        finally:
            results_layer = None
            results_vector = None

    def test_bad_building_type(self):
        """UCM: error on bad building type."""
        import natcap.invest.urban_cooling_model
        args = {
            'workspace_dir': self.workspace_dir,
            'results_suffix': 'test_suffix',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'factors',
            'do_energy_valuation': True,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            'cc_weight_shade': '0.6',
            'cc_weight_albedo': '0.2',
            'cc_weight_eti': '0.2',
            'n_workers': -1,
            }

        bad_building_vector_path = os.path.join(
            self.workspace_dir, 'bad_building_vector.gpkg')

        shutil.copyfile(args['building_vector_path'], bad_building_vector_path)
        bad_building_vector = gdal.OpenEx(bad_building_vector_path,
                                          gdal.OF_VECTOR | gdal.GA_Update)
        bad_building_layer = bad_building_vector.GetLayer()
        feature = bad_building_layer.GetNextFeature()
        feature.SetField('type', -999)
        bad_building_layer.SetFeature(feature)
        bad_building_layer.SyncToDisk()
        bad_building_layer = None
        bad_building_vector = None

        args['building_vector_path'] = bad_building_vector_path
        with self.assertRaises(ValueError) as context:
            natcap.invest.urban_cooling_model.execute(args)
        self.assertTrue(
            "Encountered a building 'type' of:" in
            str(context.exception))

    def test_missing_lulc_value_in_table(self):
        """UCM: error on missing lulc value in biophysical table."""
        import natcap.invest.urban_cooling_model
        import pandas

        args = {
            'workspace_dir': self.workspace_dir,
            'results_suffix': 'test_suffix',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'factors',
            'do_energy_valuation': True,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            'cc_weight_shade': '0.6',
            'cc_weight_albedo': '0.2',
            'cc_weight_eti': '0.2',
            'n_workers': -1,
            }

        # remove a row from the biophysical table so that lulc value is missing
        bad_biophysical_path = os.path.join(
            self.workspace_dir, 'bad_biophysical_table.csv')

        bio_df = pandas.read_csv(args['biophysical_table_path'])
        bio_df = bio_df[bio_df['lucode'] != 10]
        bio_df.to_csv(bad_biophysical_path)
        bio_df = None

        args['biophysical_table_path'] = bad_biophysical_path
        with self.assertRaises(ValueError) as context:
            natcap.invest.urban_cooling_model.execute(args)
        self.assertTrue(
            "The missing values found in the LULC raster but not the table"
            " are: [10]" in str(context.exception))

    def test_bad_args(self):
        """UCM: test validation of bad arguments."""
        from natcap.invest import urban_cooling_model
        from natcap.invest import validation
        args = {
            'workspace_dir': self.workspace_dir,
            'results_suffix': 'test_suffix',
            't_ref': 35.0,
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'factors',
            'do_energy_valuation': True,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            # Explicitly leaving CC weight parameters out.
            'n_workers': -1,
            }

        del args['t_ref']
        warnings = urban_cooling_model.validate(args)
        expected_warning = (['t_ref'], validation.MESSAGES['MISSING_KEY'])
        self.assertTrue(expected_warning in warnings)

        args['t_ref'] = ''
        result = urban_cooling_model.validate(args)
        self.assertEqual(result[0][1], validation.MESSAGES['MISSING_VALUE'])

        args['t_ref'] = 35.0
        args['cc_weight_shade'] = -0.6
        result = urban_cooling_model.validate(args)
        self.assertEqual(
            result[0][1], validation.MESSAGES['NOT_WITHIN_RANGE'].format(
                value=args['cc_weight_shade'], range='[0, 1]'))

        args['cc_weight_shade'] = "not a number"
        result = urban_cooling_model.validate(args)
        self.assertEqual(
            result[0][1],
            validation.MESSAGES['NOT_A_NUMBER'].format(value=args['cc_weight_shade']))

        args['cc_method'] = 'nope'
        result = urban_cooling_model.validate(args)
        self.assertEqual(
            result[0][1],
            validation.MESSAGES['INVALID_OPTION'].format(
                option_list=['factors', 'intensity']))

        args['cc_method'] = 'intensity'
        args['cc_weight_shade'] = 0.2  # reset this arg

        # Create a new table like the original one, but without the green area
        # column.
        old_df = pandas.read_csv(args['biophysical_table_path'])
        new_df = old_df.drop('Green_area', axis='columns')

        args['biophysical_table_path'] = os.path.join(
            self.workspace_dir, 'new_csv.csv')
        new_df.to_csv(args['biophysical_table_path'])

        result = urban_cooling_model.validate(args)
        expected = [(
            ['biophysical_table_path'],
            validation.MESSAGES['MATCHED_NO_HEADERS'].format(
                header='column', header_name='green_area'))]
        self.assertEqual(result, expected)

    def test_do_energy_valuation_option(self): 
        """UCM: test separate valuation options."""
        import natcap.invest.urban_cooling_model
        args = {
            'workspace_dir': os.path.join(self.workspace_dir, 'workspace'),
            'results_suffix': '',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'regression',
            'do_energy_valuation': True,
            'do_productivity_valuation': False,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'cc_weight_shade': '0.6',
            'cc_weight_albedo': '0.2',
            'cc_weight_eti': '0.2',
            'n_workers': -1,
        }
        natcap.invest.urban_cooling_model.execute(args)
        intermediate_dir = os.path.join(args['workspace_dir'], 'intermediate')

        wbgt_path = os.path.join(
            intermediate_dir, f'wbgt.tif')
        light_work_loss_path = os.path.join(
            intermediate_dir, f'light_work_loss_percent.tif')
        heavy_work_loss_path = os.path.join(
            intermediate_dir, f'heavy_work_loss_percent.tif')
        wbgt_stats_pickle_path = os.path.join(
            intermediate_dir, 'wbgt_stats.pickle')
        light_loss_stats_pickle_path = os.path.join(
            intermediate_dir, 'light_loss_stats.pickle')
        heavy_loss_stats_pickle_path = os.path.join(
            intermediate_dir, 'heavy_loss_stats.pickle')
        intermediate_building_vector_path = os.path.join(
            intermediate_dir, f'reprojected_buildings.shp')
        t_air_stats_pickle_path = os.path.join(
            intermediate_dir, 't_air_stats.pickle')
        energy_consumption_vector_path = os.path.join(
            args['workspace_dir'], f'buildings_with_stats.shp')

        # make sure the energy valuation outputs are there,
        # and the productivity valuation outputs aren't
        for path in [intermediate_building_vector_path, t_air_stats_pickle_path,
                     energy_consumption_vector_path]:
            self.assertTrue(os.path.exists(path))

        for path in [wbgt_path, light_work_loss_path, heavy_work_loss_path,
                     wbgt_stats_pickle_path, light_loss_stats_pickle_path, heavy_loss_stats_pickle_path]:
            self.assertFalse(os.path.exists(path))

    def test_do_productivity_valuation_option(self):
        """UCM: test separate valuation options."""
        import natcap.invest.urban_cooling_model
        args = {
            'workspace_dir': os.path.join(self.workspace_dir, 'workspace'),
            'results_suffix': '',
            't_ref': 35.0,
            't_obs_raster_path': os.path.join(
                REGRESSION_DATA, "Tair_Sept.tif"),
            'lulc_raster_path': os.path.join(
                REGRESSION_DATA, "LULC_SFBA.tif"),
            'ref_eto_raster_path': os.path.join(
                REGRESSION_DATA, "ETo_SFBA.tif"),
            'aoi_vector_path': os.path.join(
                REGRESSION_DATA,
                "watersheds_clippedDraft_Watersheds_SFEI.gpkg"),
            'biophysical_table_path': os.path.join(
                REGRESSION_DATA, "biophysical_table_ucm.csv"),
            'green_area_cooling_distance': 1000.0,
            'uhi_max': 3,
            'cc_method': 'regression',
            'do_energy_valuation': False,
            'do_productivity_valuation': True,
            't_air_average_radius': "1000.0",
            'building_vector_path': os.path.join(
                REGRESSION_DATA, "buildings_clip.gpkg"),
            'energy_consumption_table_path': os.path.join(
                REGRESSION_DATA, "Energy.csv"),
            'avg_rel_humidity': '30.0',
            'cc_weight_shade': '0.6',
            'cc_weight_albedo': '0.2',
            'cc_weight_eti': '0.2',
            'n_workers': -1,
        }
        natcap.invest.urban_cooling_model.execute(args)
        intermediate_dir = os.path.join(args['workspace_dir'], 'intermediate')

        wbgt_path = os.path.join(
            intermediate_dir, f'wbgt.tif')
        light_work_loss_path = os.path.join(
            intermediate_dir, f'light_work_loss_percent.tif')
        heavy_work_loss_path = os.path.join(
            intermediate_dir, f'heavy_work_loss_percent.tif')
        wbgt_stats_pickle_path = os.path.join(
            intermediate_dir, 'wbgt_stats.pickle')
        light_loss_stats_pickle_path = os.path.join(
            intermediate_dir, 'light_loss_stats.pickle')
        heavy_loss_stats_pickle_path = os.path.join(
            intermediate_dir, 'heavy_loss_stats.pickle')
        intermediate_building_vector_path = os.path.join(
            intermediate_dir, f'reprojected_buildings.shp')
        t_air_stats_pickle_path = os.path.join(
            intermediate_dir, 't_air_stats.pickle')
        energy_consumption_vector_path = os.path.join(
            args['workspace_dir'], f'buildings_with_stats.shp')

        # make sure the productivity valuation outputs are there,
        # and the energy valuation outputs aren't
        for path in [wbgt_path, light_work_loss_path, heavy_work_loss_path,
                     wbgt_stats_pickle_path, light_loss_stats_pickle_path, heavy_loss_stats_pickle_path]:
            self.assertTrue(os.path.exists(path))
        for path in [intermediate_building_vector_path, t_air_stats_pickle_path,
                     energy_consumption_vector_path]:
            self.assertFalse(os.path.exists(path))

    def test_cc_rasters(self):
        """Test that `execute` creates correct cooling coefficient rasters with
        synthetic data"""
        from natcap.invest import urban_cooling_model

        args = {}
        args['workspace_dir'] = self.workspace_dir
        args['results_suffix'] = "_01"
        args['t_ref'] = 23.5
        args['lulc_raster_path'] = os.path.join(self.workspace_dir,
                                                "lulc.tif")
        args['ref_eto_raster_path'] = os.path.join(self.workspace_dir,
                                                   "evapotranspiration.tif")
        args['aoi_vector_path'] = os.path.join(self.workspace_dir, "aoi.shp")
        args['biophysical_table_path'] = os.path.join(self.workspace_dir,
                                                      "biophysical_table.csv")
        args['green_area_cooling_distance'] = 90
        args['t_air_average_radius'] = 300
        args['uhi_max'] = 2.05
        args['do_energy_valuation'] = False
        args['do_productivity_valuation'] = False
        args['avg_rel_humidity'] = ''
        args['building_vector_path'] = os.path.join(self.workspace_dir,
                                                    "buildings.shp")
        args['energy_consumption_table_path'] = os.path.join(self.workspace_dir,
                                                             "ucm_energy.csv")
        args['cc_method'] = "factors"
        args['cc_weight_shade'] = ''  # 0.6
        args['cc_weight_albedo'] = ''  # 0.2
        args['cc_weight_eti'] = ''  # 0.2

        def _make_input_data(args):
            """ Create aoi shapefile, biophysical table csv, lulc tif,
            and evapotranspiration tif"""

            make_simple_vector(args['aoi_vector_path'])

            biophysical_table = pandas.DataFrame({
                "lucode": [1, 2, 3, 4, 5],
                "lu_desc": ["water", "forest", "grassland", "urban", "barren"],
                "green_area": [0, 1, 1, 0, 0],
                "kc": [1, 1.1, .9, .3, .2],
                "albedo": [.05, .1, .2, .3, .4],
                "shade": [0, 1, 0, 0.2, 0]
            })

            biophysical_csv_path = args['biophysical_table_path']
            biophysical_table.to_csv(biophysical_csv_path, index=False)

            lulc_array = numpy.array([
                [2, 3, 1, 5, 5, 5],
                [3, 3, 1, 1, 4, 5],
                [5, 5, 4, 2, 3, 1],
                [4, 1, 4, 2, 2, 1],
                [1, 5, 4, 1, 1, 2]
            ], dtype=numpy.float32)
            make_simple_raster(args['lulc_raster_path'], lulc_array)

            et_array = numpy.array([
                [800, 799, 567, 234, 422, 422],
                [765, 867, 765, 654, 456, 677],
                [556, 443, 456, 265, 876, 890],
                [433, 266, 677, 776, 900, 687],
                [456, 832, 234, 234, 234, 554]
            ], dtype=numpy.float32)
            make_simple_raster(args['ref_eto_raster_path'], et_array)

        _make_input_data(args)
        urban_cooling_model.execute(args)

        # This array was generated by manually running through calculations
        # Equation: cc = 0.6 * shade + 0.2 * albedo + 0.2 * eti
        cc_array = numpy.array(
            [[0.815556, 0.1998, 0.136, 0.0904, 0.098756, 0.098756],
             [0.193, 0.2134, 0.18, 0.155333, 0.2104, 0.110089],
             [0.10471112, 0.0996889, 0.2104, 0.6847778, 0.2152, 0.20777778],
             [0.20886667, 0.06911112, 0.22513334, 0.8096889,  0.84, 0.1626668],
             [0.11133333, 0.11697778, 0.1956, 0.062, 0.062, 0.75542223]]
            )

        cc_tif = gdal.Open(os.path.join(args["workspace_dir"], "intermediate",
                                        f"cc{args['results_suffix']}.tif"))
        band_cc = cc_tif.GetRasterBand(1)
        actual_cc = band_cc.ReadAsArray()

        numpy.testing.assert_allclose(actual_cc, cc_array, atol=1e-6)

        # Check CC_park
        cc_park_tif = gdal.Open(
            os.path.join(args["workspace_dir"], "intermediate",
                         f"cc_park{args['results_suffix']}.tif"))
        band = cc_park_tif.GetRasterBand(1)
        actual_cc_park = band.ReadAsArray()

        # This array was created by running `convolve_2d_by_exponential`
        # using a manually-calculated signal raster (cc * green_area)
        expected_cc_park = numpy.array(
            [[0.183541, 0.160865, 0.144615, 0.137877, 0.134337, 0.133191],
             [0.15858, 0.152832, 0.147821, 0.150366, 0.14931, 0.146379],
             [0.138345, 0.141573, 0.153495, 0.176796, 0.176097, 0.167245],
             [0.12765204, 0.13550324, 0.15505734, 0.18876731, 0.20118835, 0.18913657],
             [0.12391507, 0.13292464, 0.1511357, 0.17420742, 0.1924037, 0.21190241]])

        numpy.testing.assert_allclose(
            actual_cc_park, expected_cc_park, atol=1e-6)
