invest/tests/test_ucm.py

695 lines
30 KiB
Python

"""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)