Renaming files, variables, everything to 'urban_nature'*.

RE:#1179
This commit is contained in:
James Douglass 2023-02-11 15:57:31 -08:00
parent 1e2959378f
commit 8787e918e6
2 changed files with 230 additions and 230 deletions

View File

@ -131,7 +131,7 @@ ARGS_SPEC = {
"summarized."
),
},
'greenspace_demand': {
'urban_nature_demand': {
'type': 'number',
'name': 'urban nature demand per capita',
'units': u.m**2, # defined as m² per capita
@ -293,9 +293,9 @@ ARGS_SPEC = {
_OUTPUT_BASE_FILES = {
'greenspace_supply': 'greenspace_supply.tif',
'urban_nature_supply': 'urban_nature_supply.tif',
'admin_boundaries': 'admin_boundaries.gpkg',
'greenspace_balance': 'greenspace_balance.tif',
'urban_nature_balance': 'urban_nature_balance.tif',
}
_INTERMEDIATE_BASE_FILES = {
@ -304,10 +304,10 @@ _INTERMEDIATE_BASE_FILES = {
'aligned_lulc': 'aligned_lulc.tif',
'masked_lulc': 'masked_lulc.tif',
'aligned_mask': 'aligned_valid_pixels_mask.tif',
'greenspace_area': 'greenspace_area.tif',
'greenspace_population_ratio': 'greenspace_population_ratio.tif',
'urban_nature_area': 'urban_nature_area.tif',
'urban_nature_population_ratio': 'urban_nature_population_ratio.tif',
'convolved_population': 'convolved_population.tif',
'greenspace_supply_demand_budget': 'greenspace_supply_demand_budget.tif',
'urban_nature_supply_demand_budget': 'urban_nature_supply_demand_budget.tif',
'undersupplied_population': 'undersupplied_population.tif',
'oversupplied_population': 'oversupplied_population.tif',
'reprojected_admin_boundaries': 'reprojected_admin_boundaries.gpkg',
@ -352,8 +352,8 @@ def execute(args):
unit belonging to the given population group. The name of the
population group (everything other than a leading ``"pop_"``) must
uniquely identify the group.
args['greenspace_demand'] (number): (required) A positive, nonzero
number indicating the required greenspace, in per capita.
args['urban_nature_demand'] (number): (required) A positive, nonzero
number indicating the required urban_nature, in per capita.
args['decay_function'] (string): (required) The selected kernel type.
Must be one of the keys in ``KERNEL_TYPES``.
args['search_radius_mode'] (string): (required). The selected search
@ -601,16 +601,16 @@ def execute(args):
if args['search_radius_mode'] == RADIUS_OPT_UNIFORM:
search_radii = set([float(args['search_radius'])])
elif args['search_radius_mode'] == RADIUS_OPT_URBAN_NATURE:
greenspace_attrs = attr_table[attr_table['urban_nature'] == 1]
urban_nature_attrs = attr_table[attr_table['urban_nature'] == 1]
try:
search_radii = set(greenspace_attrs['search_radius_m'].unique())
search_radii = set(urban_nature_attrs['search_radius_m'].unique())
except KeyError as missing_key:
raise ValueError(
f"The column {str(missing_key)} is missing from the LULC "
f"attribute table {args['lulc_attribute_table']}")
# Build an iterable of plain tuples: (lucode, search_radius_m)
lucode_to_search_radii = list(
greenspace_attrs[['lucode', 'search_radius_m']].itertuples(
urban_nature_attrs[['lucode', 'search_radius_m']].itertuples(
index=False, name=None))
elif args['search_radius_mode'] == RADIUS_OPT_POP_GROUP:
pop_group_table = utils.read_csv_to_dataframe(
@ -667,50 +667,50 @@ def execute(args):
dependent_task_list=[
kernel_tasks[search_radius_m], population_mask_task])
greenspace_pixels_path = os.path.join(
intermediate_dir, f'greenspace_area{suffix}.tif')
greenspace_reclassification_task = graph.add_task(
_reclassify_greenspace_area,
urban_nature_pixels_path = os.path.join(
intermediate_dir, f'urban_nature_area{suffix}.tif')
urban_nature_reclassification_task = graph.add_task(
_reclassify_urban_nature_area,
kwargs={
'lulc_raster_path': file_registry['masked_lulc'],
'lulc_attribute_table': args['lulc_attribute_table'],
'target_raster_path': greenspace_pixels_path,
'target_raster_path': urban_nature_pixels_path,
},
target_path_list=[greenspace_pixels_path],
target_path_list=[urban_nature_pixels_path],
task_name='Identify urban nature areas',
dependent_task_list=[lulc_mask_task]
)
greenspace_population_ratio_path = os.path.join(
urban_nature_population_ratio_path = os.path.join(
intermediate_dir,
f'greenspace_population_ratio{suffix}.tif')
greenspace_population_ratio_task = graph.add_task(
_calculate_greenspace_population_ratio,
args=(greenspace_pixels_path,
f'urban_nature_population_ratio{suffix}.tif')
urban_nature_population_ratio_task = graph.add_task(
_calculate_urban_nature_population_ratio,
args=(urban_nature_pixels_path,
decayed_population_path,
greenspace_population_ratio_path),
urban_nature_population_ratio_path),
task_name=(
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[greenspace_population_ratio_path],
target_path_list=[urban_nature_population_ratio_path],
dependent_task_list=[
greenspace_reclassification_task, decayed_population_task,
urban_nature_reclassification_task, decayed_population_task,
])
greenspace_supply_task = graph.add_task(
urban_nature_supply_task = graph.add_task(
_convolve_and_set_lower_bound,
kwargs={
'signal_path_band': (
greenspace_population_ratio_path, 1),
urban_nature_population_ratio_path, 1),
'kernel_path_band': (kernel_path, 1),
'target_path': file_registry['greenspace_supply'],
'target_path': file_registry['urban_nature_supply'],
'working_dir': intermediate_dir,
},
task_name='2SFCA - urban nature supply',
target_path_list=[file_registry['greenspace_supply']],
target_path_list=[file_registry['urban_nature_supply']],
dependent_task_list=[
kernel_tasks[search_radius_m],
greenspace_population_ratio_task])
urban_nature_population_ratio_task])
# Search radius mode 2: Search radii are defined per greenspace lulc class.
elif args['search_radius_mode'] == RADIUS_OPT_URBAN_NATURE:
@ -736,87 +736,87 @@ def execute(args):
dependent_task_list=[
kernel_tasks[search_radius_m], population_mask_task])
partial_greenspace_supply_paths = []
partial_greenspace_supply_tasks = []
partial_urban_nature_supply_paths = []
partial_urban_nature_supply_tasks = []
for lucode, search_radius_m in lucode_to_search_radii:
greenspace_pixels_path = os.path.join(
urban_nature_pixels_path = os.path.join(
intermediate_dir,
f'greenspace_area_lucode_{lucode}{suffix}.tif')
greenspace_reclassification_task = graph.add_task(
_reclassify_greenspace_area,
f'urban_nature_area_lucode_{lucode}{suffix}.tif')
urban_nature_reclassification_task = graph.add_task(
_reclassify_urban_nature_area,
kwargs={
'lulc_raster_path': file_registry['masked_lulc'],
'lulc_attribute_table': args['lulc_attribute_table'],
'target_raster_path': greenspace_pixels_path,
'only_these_greenspace_codes': set([lucode]),
'target_raster_path': urban_nature_pixels_path,
'only_these_urban_nature_codes': set([lucode]),
},
target_path_list=[greenspace_pixels_path],
target_path_list=[urban_nature_pixels_path],
task_name=f'Identify urban nature areas with lucode {lucode}',
dependent_task_list=[lulc_mask_task]
)
greenspace_population_ratio_path = os.path.join(
urban_nature_population_ratio_path = os.path.join(
intermediate_dir,
f'greenspace_population_ratio_lucode_{lucode}{suffix}.tif')
greenspace_population_ratio_task = graph.add_task(
_calculate_greenspace_population_ratio,
args=(greenspace_pixels_path,
f'urban_nature_population_ratio_lucode_{lucode}{suffix}.tif')
urban_nature_population_ratio_task = graph.add_task(
_calculate_urban_nature_population_ratio,
args=(urban_nature_pixels_path,
decayed_population_paths[search_radius_m],
greenspace_population_ratio_path),
urban_nature_population_ratio_path),
task_name=(
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[greenspace_population_ratio_path],
target_path_list=[urban_nature_population_ratio_path],
dependent_task_list=[
greenspace_reclassification_task,
urban_nature_reclassification_task,
decayed_population_tasks[search_radius_m],
])
greenspace_supply_path = os.path.join(
urban_nature_supply_path = os.path.join(
intermediate_dir,
f'greenspace_supply_lucode_{lucode}{suffix}.tif')
partial_greenspace_supply_paths.append(greenspace_supply_path)
partial_greenspace_supply_tasks.append(graph.add_task(
f'urban_nature_supply_lucode_{lucode}{suffix}.tif')
partial_urban_nature_supply_paths.append(urban_nature_supply_path)
partial_urban_nature_supply_tasks.append(graph.add_task(
pygeoprocessing.convolve_2d,
kwargs={
'signal_path_band': (
greenspace_population_ratio_path, 1),
urban_nature_population_ratio_path, 1),
'kernel_path_band': (kernel_paths[search_radius_m], 1),
'target_path': greenspace_supply_path,
'target_path': urban_nature_supply_path,
'working_dir': intermediate_dir,
},
task_name=f'2SFCA - greenspace supply for lucode {lucode}',
target_path_list=[greenspace_supply_path],
task_name=f'2SFCA - urban_nature supply for lucode {lucode}',
target_path_list=[urban_nature_supply_path],
dependent_task_list=[
kernel_tasks[search_radius_m],
greenspace_population_ratio_task]))
urban_nature_population_ratio_task]))
greenspace_supply_task = graph.add_task(
urban_nature_supply_task = graph.add_task(
ndr._sum_rasters,
kwargs={
'raster_path_list': partial_greenspace_supply_paths,
'raster_path_list': partial_urban_nature_supply_paths,
'target_nodata': FLOAT32_NODATA,
'target_result_path': file_registry['greenspace_supply'],
'target_result_path': file_registry['urban_nature_supply'],
},
task_name='2SFCA - urban nature supply total',
target_path_list=[file_registry['greenspace_supply']],
dependent_task_list=partial_greenspace_supply_tasks
target_path_list=[file_registry['urban_nature_supply']],
dependent_task_list=partial_urban_nature_supply_tasks
)
# Search radius mode 3: search radii are defined per population group.
elif args['search_radius_mode'] == RADIUS_OPT_POP_GROUP:
LOGGER.info("Running model with search radius mode "
f"{RADIUS_OPT_POP_GROUP}")
greenspace_pixels_path = os.path.join(
intermediate_dir, f'greenspace_area{suffix}.tif')
greenspace_reclassification_task = graph.add_task(
_reclassify_greenspace_area,
urban_nature_pixels_path = os.path.join(
intermediate_dir, f'urban_nature_area{suffix}.tif')
urban_nature_reclassification_task = graph.add_task(
_reclassify_urban_nature_area,
kwargs={
'lulc_raster_path': file_registry['masked_lulc'],
'lulc_attribute_table': args['lulc_attribute_table'],
'target_raster_path': greenspace_pixels_path,
'target_raster_path': urban_nature_pixels_path,
},
target_path_list=[greenspace_pixels_path],
target_path_list=[urban_nature_pixels_path],
task_name='Identify urban nature areas',
dependent_task_list=[lulc_mask_task]
)
@ -862,18 +862,18 @@ def execute(args):
dependent_task_list=decayed_population_in_group_tasks
)
greenspace_population_ratio_task = graph.add_task(
_calculate_greenspace_population_ratio,
args=(greenspace_pixels_path,
urban_nature_population_ratio_task = graph.add_task(
_calculate_urban_nature_population_ratio,
args=(urban_nature_pixels_path,
sum_of_decayed_population_path,
file_registry['greenspace_population_ratio']),
file_registry['urban_nature_population_ratio']),
task_name=(
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[
file_registry['greenspace_population_ratio']],
file_registry['urban_nature_population_ratio']],
dependent_task_list=[
greenspace_reclassification_task,
urban_nature_reclassification_task,
sum_of_decayed_population_task,
])
@ -883,85 +883,85 @@ def execute(args):
search_radii = dict(
group_radii_table[['pop_group', 'search_radius_m']].itertuples(
index=False, name=None))
greenspace_supply_by_group_paths = {}
greenspace_supply_by_group_tasks = []
greenspace_supply_demand_by_group_paths = {}
greenspace_supply_demand_by_group_tasks = []
urban_nature_supply_by_group_paths = {}
urban_nature_supply_by_group_tasks = []
urban_nature_supply_demand_by_group_paths = {}
urban_nature_supply_demand_by_group_tasks = []
supply_population_paths = {'over': {}, 'under': {}}
supply_population_tasks = {'over': {}, 'under': {}}
for pop_group, proportional_pop_path in (
proportional_population_paths.items()):
search_radius_m = search_radii[pop_group]
greenspace_supply_to_group_path = os.path.join(
urban_nature_supply_to_group_path = os.path.join(
intermediate_dir,
f'greenspace_supply_to_{pop_group}{suffix}.tif')
greenspace_supply_by_group_paths[
pop_group] = greenspace_supply_to_group_path
greenspace_supply_by_group_task = graph.add_task(
f'urban_nature_supply_to_{pop_group}{suffix}.tif')
urban_nature_supply_by_group_paths[
pop_group] = urban_nature_supply_to_group_path
urban_nature_supply_by_group_task = graph.add_task(
pygeoprocessing.convolve_2d,
kwargs={
'signal_path_band': (
file_registry['greenspace_population_ratio'], 1),
file_registry['urban_nature_population_ratio'], 1),
'kernel_path_band': (kernel_paths[search_radius_m], 1),
'target_path': greenspace_supply_to_group_path,
'target_path': urban_nature_supply_to_group_path,
'working_dir': intermediate_dir,
},
task_name=f'2SFCA - urban nature supply for {pop_group}',
target_path_list=[greenspace_supply_to_group_path],
target_path_list=[urban_nature_supply_to_group_path],
dependent_task_list=[
kernel_tasks[search_radius_m],
greenspace_population_ratio_task])
greenspace_supply_by_group_tasks.append(
greenspace_supply_by_group_task)
urban_nature_population_ratio_task])
urban_nature_supply_by_group_tasks.append(
urban_nature_supply_by_group_task)
# Calculate SUP_DEMi_cap for each population group.
per_cap_greenspace_balance_pop_group_path = os.path.join(
per_cap_urban_nature_balance_pop_group_path = os.path.join(
output_dir,
f'greenspace_balance_{pop_group}{suffix}.tif')
per_cap_greenspace_balance_pop_group_task = graph.add_task(
f'urban_nature_balance_{pop_group}{suffix}.tif')
per_cap_urban_nature_balance_pop_group_task = graph.add_task(
pygeoprocessing.raster_calculator,
kwargs={
'base_raster_path_band_const_list': [
(greenspace_supply_to_group_path, 1),
(float(args['greenspace_demand']), 'raw')
(urban_nature_supply_to_group_path, 1),
(float(args['urban_nature_demand']), 'raw')
],
'local_op': _greenspace_balance_op,
'local_op': _urban_nature_balance_op,
'target_raster_path':
per_cap_greenspace_balance_pop_group_path,
per_cap_urban_nature_balance_pop_group_path,
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name=(
f'Calculate per-capita urban nature balance - {pop_group}'),
target_path_list=[
per_cap_greenspace_balance_pop_group_path],
per_cap_urban_nature_balance_pop_group_path],
dependent_task_list=[
greenspace_supply_by_group_task,
urban_nature_supply_by_group_task,
])
greenspace_supply_demand_by_group_path = os.path.join(
urban_nature_supply_demand_by_group_path = os.path.join(
intermediate_dir,
f'greenspace_supply_demand_budget_{pop_group}{suffix}.tif')
greenspace_supply_demand_by_group_paths[
pop_group] = greenspace_supply_demand_by_group_path
greenspace_supply_demand_by_group_tasks.append(graph.add_task(
f'urban_nature_supply_demand_budget_{pop_group}{suffix}.tif')
urban_nature_supply_demand_by_group_paths[
pop_group] = urban_nature_supply_demand_by_group_path
urban_nature_supply_demand_by_group_tasks.append(graph.add_task(
pygeoprocessing.raster_calculator,
kwargs={
'base_raster_path_band_const_list': [
(per_cap_greenspace_balance_pop_group_path, 1),
(per_cap_urban_nature_balance_pop_group_path, 1),
(proportional_pop_path, 1)
],
'local_op': _greenspace_supply_demand_op,
'local_op': _urban_nature_supply_demand_op,
'target_raster_path': (
greenspace_supply_demand_by_group_path),
urban_nature_supply_demand_by_group_path),
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita urban nature supply-demand',
target_path_list=[
greenspace_supply_demand_by_group_path],
urban_nature_supply_demand_by_group_path],
dependent_task_list=[
per_cap_greenspace_balance_pop_group_task,
per_cap_urban_nature_balance_pop_group_task,
proportional_population_tasks[pop_group],
]))
@ -978,7 +978,7 @@ def execute(args):
kwargs={
'base_raster_path_band_const_list': [
(proportional_pop_path, 1),
(per_cap_greenspace_balance_pop_group_path, 1),
(per_cap_urban_nature_balance_pop_group_path, 1),
(op, 'raw'), # numpy element-wise comparator
],
'local_op': _filter_population,
@ -991,41 +991,41 @@ def execute(args):
f'{pop_group}'),
target_path_list=[supply_population_path],
dependent_task_list=[
per_cap_greenspace_balance_pop_group_task,
per_cap_urban_nature_balance_pop_group_task,
proportional_population_tasks[pop_group],
])
greenspace_supply_task = graph.add_task(
urban_nature_supply_task = graph.add_task(
_weighted_sum,
kwargs={
'raster_path_list':
[greenspace_supply_by_group_paths[group] for group in
[urban_nature_supply_by_group_paths[group] for group in
sorted(split_population_fields)],
'weight_raster_list':
[pop_group_proportion_paths[group] for group in
sorted(split_population_fields)],
'target_path': file_registry['greenspace_supply'],
'target_path': file_registry['urban_nature_supply'],
},
task_name='2SFCA - urban nature supply total',
target_path_list=[file_registry['greenspace_supply']],
target_path_list=[file_registry['urban_nature_supply']],
dependent_task_list=[
*greenspace_supply_by_group_tasks,
*urban_nature_supply_by_group_tasks,
*pop_group_proportion_tasks.values(),
])
greenspace_supply_demand_budget_task = graph.add_task(
urban_nature_supply_demand_budget_task = graph.add_task(
ndr._sum_rasters,
kwargs={
'raster_path_list':
list(greenspace_supply_demand_by_group_paths.values()),
list(urban_nature_supply_demand_by_group_paths.values()),
'target_nodata': FLOAT32_NODATA,
'target_result_path':
file_registry['greenspace_supply_demand_budget'],
file_registry['urban_nature_supply_demand_budget'],
},
task_name='2SFCA - greenspace supply-demand budget',
task_name='2SFCA - urban_nature supply-demand budget',
target_path_list=[
file_registry['greenspace_supply_demand_budget']],
dependent_task_list=greenspace_supply_demand_by_group_tasks
file_registry['urban_nature_supply_demand_budget']],
dependent_task_list=urban_nature_supply_demand_by_group_tasks
)
# Summary stats for RADIUS_OPT_POP_GROUP
@ -1034,8 +1034,8 @@ def execute(args):
kwargs={
'source_aoi_vector_path': file_registry['reprojected_admin_boundaries'],
'target_aoi_vector_path': file_registry['admin_boundaries'],
'greenspace_sup_dem_paths_by_pop_group':
greenspace_supply_demand_by_group_paths,
'urban_nature_sup_dem_paths_by_pop_group':
urban_nature_supply_demand_by_group_paths,
'proportional_pop_paths_by_pop_group':
proportional_population_paths,
'undersupply_by_pop_group': supply_population_paths['under'],
@ -1046,54 +1046,54 @@ def execute(args):
target_path_list=[file_registry['admin_boundaries']],
dependent_task_list=[
aoi_reprojection_task,
*greenspace_supply_demand_by_group_tasks,
*urban_nature_supply_demand_by_group_tasks,
*proportional_population_tasks.values(),
*supply_population_tasks['under'].values(),
*supply_population_tasks['over'].values(),
])
# Greenspace budget, supply/demand and over/undersupply rasters are the
# same for uniform radius and for split greenspace modes.
# same for uniform radius and for split urban_nature modes.
if args['search_radius_mode'] in (RADIUS_OPT_UNIFORM,
RADIUS_OPT_URBAN_NATURE):
# This is "SUP_DEMi_cap" from the user's guide
per_capita_greenspace_balance_task = graph.add_task(
per_capita_urban_nature_balance_task = graph.add_task(
pygeoprocessing.raster_calculator,
kwargs={
'base_raster_path_band_const_list': [
(file_registry['greenspace_supply'], 1),
(float(args['greenspace_demand']), 'raw')
(file_registry['urban_nature_supply'], 1),
(float(args['urban_nature_demand']), 'raw')
],
'local_op': _greenspace_balance_op,
'target_raster_path': file_registry['greenspace_balance'],
'local_op': _urban_nature_balance_op,
'target_raster_path': file_registry['urban_nature_balance'],
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita urban nature balance',
target_path_list=[file_registry['greenspace_balance']],
target_path_list=[file_registry['urban_nature_balance']],
dependent_task_list=[
greenspace_supply_task,
urban_nature_supply_task,
])
# This is "SUP_DEMi" from the user's guide
greenspace_supply_demand_task = graph.add_task(
urban_nature_supply_demand_task = graph.add_task(
pygeoprocessing.raster_calculator,
kwargs={
'base_raster_path_band_const_list': [
(file_registry['greenspace_balance'], 1),
(file_registry['urban_nature_balance'], 1),
(file_registry['masked_population'], 1)
],
'local_op': _greenspace_supply_demand_op,
'local_op': _urban_nature_supply_demand_op,
'target_raster_path': (
file_registry['greenspace_supply_demand_budget']),
file_registry['urban_nature_supply_demand_budget']),
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita urban nature supply-demand',
target_path_list=[
file_registry['greenspace_supply_demand_budget']],
file_registry['urban_nature_supply_demand_budget']],
dependent_task_list=[
per_capita_greenspace_balance_task,
per_capita_urban_nature_balance_task,
population_mask_task,
])
@ -1121,7 +1121,7 @@ def execute(args):
kwargs={
'base_raster_path_band_const_list': [
(proportional_pop_path, 1),
(file_registry['greenspace_balance'], 1),
(file_registry['urban_nature_balance'], 1),
(op, 'raw'), # numpy element-wise comparator
],
'local_op': _filter_population,
@ -1132,7 +1132,7 @@ def execute(args):
task_name=f'Determine {supply_type}supplied populations',
target_path_list=[supply_population_path],
dependent_task_list=[
greenspace_supply_demand_task,
urban_nature_supply_demand_task,
population_mask_task,
*list(proportional_population_tasks.values()),
]))
@ -1142,8 +1142,8 @@ def execute(args):
kwargs={
'source_aoi_vector_path': file_registry['reprojected_admin_boundaries'],
'target_aoi_vector_path': file_registry['admin_boundaries'],
'greenspace_budget_path': file_registry[
'greenspace_supply_demand_budget'], # TODO: is this the correct raster?
'urban_nature_budget_path': file_registry[
'urban_nature_supply_demand_budget'], # TODO: is this the correct raster?
'population_path': file_registry['masked_population'],
'undersupplied_populations_path': file_registry[
'undersupplied_population'],
@ -1157,7 +1157,7 @@ def execute(args):
dependent_task_list=[
population_mask_task,
aoi_reprojection_task,
greenspace_supply_demand_task,
urban_nature_supply_demand_task,
*supply_population_tasks
])
@ -1389,9 +1389,9 @@ def _rasterize_aois(base_raster_path, aois_vector_path,
option_list=[f"ATTRIBUTE={id_fieldname}"])
def _reclassify_greenspace_area(
def _reclassify_urban_nature_area(
lulc_raster_path, lulc_attribute_table, target_raster_path,
only_these_greenspace_codes=None):
only_these_urban_nature_codes=None):
"""Reclassify LULC pixels into the urban nature area they represent.
After execution, urban nature pixels will have values representing the
@ -1404,7 +1404,7 @@ def _reclassify_greenspace_area(
LULC attributes. Must have "lucode" and "urban_nature" columns.
target_raster_path (string): Where the reclassified urban nature raster
should be written.
only_these_greenspace_codes=None (iterable or None): If ``None``, all
only_these_urban_nature_codes=None (iterable or None): If ``None``, all
lucodes with a ``urban_nature`` value of 1 will be reclassified to
1. If an iterable, must be an iterable of landuse codes matching
codes in the lulc attribute table. Only these landcover codes will
@ -1419,26 +1419,26 @@ def _reclassify_greenspace_area(
squared_pixel_area = abs(
numpy.multiply(*_square_off_pixels(lulc_raster_path)))
if only_these_greenspace_codes:
valid_greenspace_codes = set(only_these_greenspace_codes)
if only_these_urban_nature_codes:
valid_urban_nature_codes = set(only_these_urban_nature_codes)
else:
valid_greenspace_codes = set(
valid_urban_nature_codes = set(
lucode for lucode, attributes in attribute_table_dict.items()
if (attributes['urban_nature']) == 1)
greenspace_area_map = {}
urban_nature_area_map = {}
for lucode, attributes in attribute_table_dict.items():
greenspace_area = 0
if lucode in valid_greenspace_codes:
greenspace_area = squared_pixel_area
greenspace_area_map[lucode] = greenspace_area
urban_nature_area = 0
if lucode in valid_urban_nature_codes:
urban_nature_area = squared_pixel_area
urban_nature_area_map[lucode] = urban_nature_area
lulc_raster_info = pygeoprocessing.get_raster_info(lulc_raster_path)
greenspace_area_map[lulc_raster_info['nodata'][0]] = FLOAT32_NODATA
urban_nature_area_map[lulc_raster_info['nodata'][0]] = FLOAT32_NODATA
utils.reclassify_raster(
raster_path_band=(lulc_raster_path, 1),
value_map=greenspace_area_map,
value_map=urban_nature_area_map,
target_raster_path=target_raster_path,
target_datatype=gdal.GDT_Float32,
target_nodata=FLOAT32_NODATA,
@ -1450,16 +1450,16 @@ def _reclassify_greenspace_area(
)
def _filter_population(population, greenspace_budget, numpy_filter_op):
def _filter_population(population, urban_nature_budget, numpy_filter_op):
"""Filter the population by a defined op and the urban nature budget.
Note:
The ``population`` and ``greenspace_budget`` inputs must have the same
The ``population`` and ``urban_nature_budget`` inputs must have the same
shape and must both use ``FLOAT32_NODATA`` as their nodata value.
Args:
population (numpy.array): A numpy array with population counts.
greenspace_budget (numpy.array): A numpy array with the urban nature
urban_nature_budget (numpy.array): A numpy array with the urban nature
budget values.
numpy_filter_op (callable): A function that takes a numpy array as
parameter 1 and a scalar value as parameter 2. This function must
@ -1467,16 +1467,16 @@ def _filter_population(population, greenspace_budget, numpy_filter_op):
Returns:
A ``numpy.array`` with the population values where the
``greenspace_budget`` pixels match the ``numpy_filter_op``.
``urban_nature_budget`` pixels match the ``numpy_filter_op``.
"""
population_matching_filter = numpy.full(
population.shape, FLOAT32_NODATA, dtype=numpy.float32)
valid_pixels = (
~numpy.isclose(greenspace_budget, FLOAT32_NODATA) &
~numpy.isclose(urban_nature_budget, FLOAT32_NODATA) &
~numpy.isclose(population, FLOAT32_NODATA))
population_matching_filter[valid_pixels] = numpy.where(
numpy_filter_op(greenspace_budget[valid_pixels], 0),
numpy_filter_op(urban_nature_budget[valid_pixels], 0),
population[valid_pixels], # If condition is true, use population
0 # If condition is false, use 0
)
@ -1486,7 +1486,7 @@ def _filter_population(population, greenspace_budget, numpy_filter_op):
def _supply_demand_vector_for_pop_groups(
source_aoi_vector_path,
target_aoi_vector_path,
greenspace_sup_dem_paths_by_pop_group,
urban_nature_sup_dem_paths_by_pop_group,
proportional_pop_paths_by_pop_group,
undersupply_by_pop_group,
oversupply_by_pop_group):
@ -1495,7 +1495,7 @@ def _supply_demand_vector_for_pop_groups(
Args:
source_aoi_vector_path (str): The source AOI vector path.
target_aoi_vector_path (str): The target AOI vector path.
greenspace_sup_dem_paths_by_pop_group (dict): A dict mapping population
urban_nature_sup_dem_paths_by_pop_group (dict): A dict mapping population
group names to rasters of urban nature supply/demand for the given
group.
proportional_pop_paths_by_pop_group (dict): A dict mapping population
@ -1538,8 +1538,8 @@ def _supply_demand_vector_for_pop_groups(
# trim the leading 'pop_'
groupname = re.sub(POP_FIELD_REGEX, '', pop_group_field)
greenspace_sup_dem_stats = _get_zonal_stats(
greenspace_sup_dem_paths_by_pop_group[pop_group_field])
urban_nature_sup_dem_stats = _get_zonal_stats(
urban_nature_sup_dem_paths_by_pop_group[pop_group_field])
proportional_pop_stats = _get_zonal_stats(
proportional_pop_paths_by_pop_group[pop_group_field])
undersupply_stats = _get_zonal_stats(
@ -1550,7 +1550,7 @@ def _supply_demand_vector_for_pop_groups(
for feature_id in feature_ids:
group_population_in_region = proportional_pop_stats[
feature_id]['sum']
group_sup_dem_in_region = greenspace_sup_dem_stats[
group_sup_dem_in_region = urban_nature_sup_dem_stats[
feature_id]['sum']
stats_by_feature[feature_id][f'SUP_DEMadm_cap_{groupname}'] = (
group_sup_dem_in_region / group_population_in_region)
@ -1576,7 +1576,7 @@ def _supply_demand_vector_for_pop_groups(
def _supply_demand_vector_for_single_raster_modes(
source_aoi_vector_path,
target_aoi_vector_path,
greenspace_budget_path,
urban_nature_budget_path,
population_path,
undersupplied_populations_path,
oversupplied_populations_path,
@ -1587,7 +1587,7 @@ def _supply_demand_vector_for_single_raster_modes(
source_aoi_vector_path (str): Path to the source aois vector.
target_aoi_vector_path (str): Path to where the target aois vector
should be written.
greenspace_budget_path (str): Path to a raster of urban nature
urban_nature_budget_path (str): Path to a raster of urban nature
supply/demand budget.
population_path (str): Path to a population raster.
undersupplied_populations_path (str): Path to a raster of oversupplied
@ -1604,7 +1604,7 @@ def _supply_demand_vector_for_single_raster_modes(
return pygeoprocessing.zonal_statistics(
(raster_path, 1), source_aoi_vector_path)
greenspace_budget_stats = _get_zonal_stats(greenspace_budget_path)
urban_nature_budget_stats = _get_zonal_stats(urban_nature_budget_path)
population_stats = _get_zonal_stats(population_path)
undersupplied_stats = _get_zonal_stats(undersupplied_populations_path)
oversupplied_stats = _get_zonal_stats(oversupplied_populations_path)
@ -1625,10 +1625,10 @@ def _supply_demand_vector_for_single_raster_modes(
pop_proportions_by_fid[id_field][group] = value
stats_by_feature = {}
for fid in greenspace_budget_stats.keys():
for fid in urban_nature_budget_stats.keys():
stats = {
'SUP_DEMadm_cap': (
greenspace_budget_stats[fid]['sum'] /
urban_nature_budget_stats[fid]['sum'] /
population_stats[fid]['sum']),
'Pund_adm': undersupplied_stats[fid]['sum'],
'Povr_adm': oversupplied_stats[fid]['sum'],
@ -1688,42 +1688,42 @@ def _write_supply_demand_vector(source_aoi_vector_path, feature_attrs,
target_vector = None
def _greenspace_balance_op(greenspace_supply, greenspace_demand):
def _urban_nature_balance_op(urban_nature_supply, urban_nature_demand):
"""Calculate the per-capita urban nature balance.
This is the amount of urban nature that each pixel has above (positive
values) or below (negative values) the user-defined ``greenspace_demand``
values) or below (negative values) the user-defined ``urban_nature_demand``
value.
Args:
greenspace_supply (numpy.array): The supply of urban nature available to
urban_nature_supply (numpy.array): The supply of urban nature available to
each person in the population. This is ``Ai`` in the User's Guide.
This matrix must have ``FLOAT32_NODATA`` as its nodata value.
greenspace_demand (float): The policy-defined urban nature requirement,
urban_nature_demand (float): The policy-defined urban nature requirement,
in square meters per person.
Returns:
A ``numpy.array`` of the calculated urban nature budget.
"""
balance = numpy.full(
greenspace_supply.shape, FLOAT32_NODATA, dtype=numpy.float32)
valid_pixels = ~numpy.isclose(greenspace_supply, FLOAT32_NODATA)
balance[valid_pixels] = greenspace_supply[valid_pixels] - greenspace_demand
urban_nature_supply.shape, FLOAT32_NODATA, dtype=numpy.float32)
valid_pixels = ~numpy.isclose(urban_nature_supply, FLOAT32_NODATA)
balance[valid_pixels] = urban_nature_supply[valid_pixels] - urban_nature_demand
return balance
def _greenspace_supply_demand_op(greenspace_balance, population):
def _urban_nature_supply_demand_op(urban_nature_balance, population):
"""Calculate the supply/demand of urban nature per person.
Args:
greenspace_balance (numpy.array): The area of urban nature budgeted to
urban_nature_balance (numpy.array): The area of urban nature budgeted to
each person, relative to a minimum required per-person area of
urban nature. This matrix must have ``FLOAT32_NODATA`` as its nodata
value. This matrix must be the same size and shape as
``population``.
population (numpy.array): Pixel values represent the population count
of the pixel. This matrix must be the same size and shape as
``greenspace_budget``, and must have ``FLOAT32_NODATA`` as its
``urban_nature_budget``, and must have ``FLOAT32_NODATA`` as its
nodata value.
Returns:
@ -1731,22 +1731,22 @@ def _greenspace_supply_demand_op(greenspace_balance, population):
supplied to each individual in each pixel.
"""
supply_demand = numpy.full(
greenspace_balance.shape, FLOAT32_NODATA, dtype=numpy.float32)
urban_nature_balance.shape, FLOAT32_NODATA, dtype=numpy.float32)
valid_pixels = (
~numpy.isclose(greenspace_balance, FLOAT32_NODATA) &
~numpy.isclose(urban_nature_balance, FLOAT32_NODATA) &
~numpy.isclose(population, FLOAT32_NODATA))
supply_demand[valid_pixels] = (
greenspace_balance[valid_pixels] * population[valid_pixels])
urban_nature_balance[valid_pixels] * population[valid_pixels])
return supply_demand
def _calculate_greenspace_population_ratio(
greenspace_area_raster_path, convolved_population_raster_path,
def _calculate_urban_nature_population_ratio(
urban_nature_area_raster_path, convolved_population_raster_path,
target_ratio_raster_path):
"""Calculate the urban nature-population ratio R_j.
Args:
greenspace_area_raster_path (string): The path to a raster representing
urban_nature_area_raster_path (string): The path to a raster representing
the area of the pixel that represents urban nature. Pixel values
will be ``0`` if there is no urban nature.
convolved_population_raster_path (string): The path to a raster
@ -1758,19 +1758,19 @@ def _calculate_greenspace_population_ratio(
Returns:
``None``.
"""
greenspace_nodata = pygeoprocessing.get_raster_info(
greenspace_area_raster_path)['nodata'][0]
urban_nature_nodata = pygeoprocessing.get_raster_info(
urban_nature_area_raster_path)['nodata'][0]
population_nodata = pygeoprocessing.get_raster_info(
convolved_population_raster_path)['nodata'][0]
def _greenspace_population_ratio(greenspace_area, convolved_population):
def _urban_nature_population_ratio(urban_nature_area, convolved_population):
"""Calculate the urban nature-population ratio R_j.
Args:
greenspace_area (numpy.array): A numpy array representing the area
urban_nature_area (numpy.array): A numpy array representing the area
of urban nature in the pixel. Pixel values will be ``0`` if
there is no urban nature. Pixel values may also match
``greenspace_nodata``.
``urban_nature_nodata``.
convolved_population (numpy.array): A numpy array where each pixel
represents the total number of people within a search radius of
each pixel, perhaps weighted by a search kernel.
@ -1788,7 +1788,7 @@ def _calculate_greenspace_population_ratio(
# ASSUMPTION: population nodata value is not close to 0.
# Shouldn't be if we're coming from convolution.
out_array = numpy.full(
greenspace_area.shape, FLOAT32_NODATA, dtype=numpy.float32)
urban_nature_area.shape, FLOAT32_NODATA, dtype=numpy.float32)
# Small negative values should already have been filtered out in
# another function after the convolution.
@ -1796,15 +1796,15 @@ def _calculate_greenspace_population_ratio(
valid_pixels = (convolved_population > 0)
# R_j is a ratio only calculated for the urban nature pixels.
greenspace_pixels = ~numpy.isclose(greenspace_area, 0)
valid_pixels &= greenspace_pixels
urban_nature_pixels = ~numpy.isclose(urban_nature_area, 0)
valid_pixels &= urban_nature_pixels
if population_nodata is not None:
valid_pixels &= ~utils.array_equals_nodata(
convolved_population, population_nodata)
if greenspace_nodata is not None:
if urban_nature_nodata is not None:
valid_pixels &= ~utils.array_equals_nodata(
greenspace_area, greenspace_nodata)
urban_nature_area, urban_nature_nodata)
# The user's guide specifies that if the population in the search
# radius is numerically 0, the urban nature/population ratio should be
@ -1817,13 +1817,13 @@ def _calculate_greenspace_population_ratio(
# nature on that pixel.
population_close_to_zero = (convolved_population <= 1.0)
out_array[population_close_to_zero] = (
greenspace_area[population_close_to_zero])
out_array[~greenspace_pixels] = 0
urban_nature_area[population_close_to_zero])
out_array[~urban_nature_pixels] = 0
valid_pixels_with_population = (
valid_pixels & (~population_close_to_zero))
out_array[valid_pixels_with_population] = (
greenspace_area[valid_pixels_with_population] /
urban_nature_area[valid_pixels_with_population] /
convolved_population[valid_pixels_with_population])
# eliminate pixel values < 0
@ -1832,9 +1832,9 @@ def _calculate_greenspace_population_ratio(
return out_array
pygeoprocessing.raster_calculator(
[(greenspace_area_raster_path, 1),
[(urban_nature_area_raster_path, 1),
(convolved_population_raster_path, 1)],
_greenspace_population_ratio, target_ratio_raster_path,
_urban_nature_population_ratio, target_ratio_raster_path,
gdal.GDT_Float32, FLOAT32_NODATA)

View File

@ -35,7 +35,7 @@ def _build_model_args(workspace):
'lulc_attribute_table': os.path.join(
workspace, 'lulc_attributes.csv'),
'decay_function': 'gaussian',
'greenspace_demand': 100, # square meters
'urban_nature_demand': 100, # square meters
'admin_boundaries_vector_path': os.path.join(
workspace, 'aois.geojson'),
}
@ -337,30 +337,30 @@ class UNATests(unittest.TestCase):
numpy.testing.assert_allclose(
expected_array, kernel)
def test_greenspace_balance(self):
"""UNA: Test the per-capita greenspace balance functions."""
def test_urban_nature_balance(self):
"""UNA: Test the per-capita urban_nature balance functions."""
from natcap.invest import urban_nature_access
nodata = urban_nature_access.FLOAT32_NODATA
greenspace_supply = numpy.array([
urban_nature_supply = numpy.array([
[nodata, 100.5],
[75, 100]], dtype=numpy.float32)
greenspace_demand = 50
urban_nature_demand = 50
population = numpy.array([
[50, 100],
[40.75, nodata]], dtype=numpy.float32)
greenspace_budget = urban_nature_access._greenspace_balance_op(
greenspace_supply, greenspace_demand)
expected_greenspace_budget = numpy.array([
urban_nature_budget = urban_nature_access._urban_nature_balance_op(
urban_nature_supply, urban_nature_demand)
expected_urban_nature_budget = numpy.array([
[nodata, 50.5],
[25, 50]], dtype=numpy.float32)
numpy.testing.assert_allclose(
greenspace_budget, expected_greenspace_budget)
urban_nature_budget, expected_urban_nature_budget)
supply_demand = urban_nature_access._greenspace_supply_demand_op(
greenspace_budget, population)
supply_demand = urban_nature_access._urban_nature_supply_demand_op(
urban_nature_budget, population)
expected_supply_demand = numpy.array([
[nodata, 100 * 50.5],
[25 * 40.75, nodata]], dtype=numpy.float32)
@ -479,13 +479,13 @@ class UNATests(unittest.TestCase):
admin_vector = None
admin_layer = None
def test_split_greenspace(self):
def test_split_urban_nature(self):
from natcap.invest import urban_nature_access
args = _build_model_args(self.workspace_dir)
args['search_radius_mode'] = urban_nature_access.RADIUS_OPT_URBAN_NATURE
# The split greenspace feature requires an extra column in the
# The split urban_nature feature requires an extra column in the
# attribute table.
attribute_table = pandas.read_csv(args['lulc_attribute_table'])
new_search_radius_values = {
@ -667,7 +667,7 @@ class UNATests(unittest.TestCase):
"""UNA: all modes have same results when consistent radii.
Although the different modes have different ways of defining their
search radii, the greenspace_supply raster should be numerically
search radii, the urban_nature_supply raster should be numerically
equivalent if they all use the same search radii.
This is a good gut-check of basic model behavior across modes.
@ -685,20 +685,20 @@ class UNATests(unittest.TestCase):
urban_nature_access.RADIUS_OPT_UNIFORM)
uniform_args['search_radius'] = search_radius
# build args for split greenspace mode
split_greenspace_args = _build_model_args(
os.path.join(self.workspace_dir, 'radius_greenspace'))
split_greenspace_args['results_suffix'] = 'greenspace'
split_greenspace_args['search_radius_mode'] = (
# build args for split urban_nature mode
split_urban_nature_args = _build_model_args(
os.path.join(self.workspace_dir, 'radius_urban_nature'))
split_urban_nature_args['results_suffix'] = 'urban_nature'
split_urban_nature_args['search_radius_mode'] = (
urban_nature_access.RADIUS_OPT_URBAN_NATURE)
attribute_table = pandas.read_csv(
split_greenspace_args['lulc_attribute_table'])
split_urban_nature_args['lulc_attribute_table'])
new_search_radius_values = dict(
(lucode, search_radius) for lucode in attribute_table['lucode'])
attribute_table['search_radius_m'] = attribute_table['lucode'].map(
new_search_radius_values)
attribute_table.to_csv(
split_greenspace_args['lulc_attribute_table'], index=False)
split_urban_nature_args['lulc_attribute_table'], index=False)
# build args for split population group mode
pop_group_args = _build_model_args(
@ -728,21 +728,21 @@ class UNATests(unittest.TestCase):
pop_group_args['population_raster_path'])['projection_wkt'],
'GeoJSON', fields, attributes)
for args in (uniform_args, split_greenspace_args, pop_group_args):
for args in (uniform_args, split_urban_nature_args, pop_group_args):
urban_nature_access.execute(args)
uniform_radius_supply = pygeoprocessing.raster_to_numpy_array(
os.path.join(uniform_args['workspace_dir'], 'output',
'greenspace_supply_uniform.tif'))
split_greenspace_supply = pygeoprocessing.raster_to_numpy_array(
os.path.join(split_greenspace_args['workspace_dir'], 'output',
'greenspace_supply_greenspace.tif'))
'urban_nature_supply_uniform.tif'))
split_urban_nature_supply = pygeoprocessing.raster_to_numpy_array(
os.path.join(split_urban_nature_args['workspace_dir'], 'output',
'urban_nature_supply_urban_nature.tif'))
split_pop_groups_supply = pygeoprocessing.raster_to_numpy_array(
os.path.join(pop_group_args['workspace_dir'], 'output',
'greenspace_supply_popgroup.tif'))
'urban_nature_supply_popgroup.tif'))
numpy.testing.assert_allclose(
uniform_radius_supply, split_greenspace_supply, rtol=1e-6)
uniform_radius_supply, split_urban_nature_supply, rtol=1e-6)
numpy.testing.assert_allclose(
uniform_radius_supply, split_pop_groups_supply, rtol=1e-6)