Renaming attribute in LULC table, updating text.

RE:#1179
This commit is contained in:
James Douglass 2023-02-11 15:54:22 -08:00
parent 892e7e3599
commit 1e2959378f
2 changed files with 89 additions and 88 deletions

View File

@ -67,11 +67,11 @@ ARGS_SPEC = {
'type': 'csv',
'columns': {
'lucode': spec_utils.LULC_TABLE_COLUMN,
'greenspace': {
'urban_nature': {
'type': 'number',
'units': u.none,
'about': (
"1 if this landcover code represents greenspace, 0 "
"1 if this LULC code represents urban nature, 0 "
"if not."
),
},
@ -83,14 +83,14 @@ ARGS_SPEC = {
'expression': 'value >= 0',
'about': (
'The distance in meters to use as the search radius '
'for this type of greenspace. Values must be >= 0. '
'for this type of urban nature. Values must be >= 0. '
'Required when running the model with search radii '
'defined per greenspace class.'
'defined per urban nature class.'
),
}
},
'about': (
"A table identifying which LULC codes represent greenspace."
"A table identifying which LULC codes represent urban nature."
),
},
'population_raster_path': {
@ -133,11 +133,11 @@ ARGS_SPEC = {
},
'greenspace_demand': {
'type': 'number',
'name': 'greenspace demand per capita',
'name': 'urban nature demand per capita',
'units': u.m**2, # defined as m² per capita
'expression': "value > 0",
'about': gettext(
"The amount of greenspace that each resident should have "
"The amount of urban nature that each resident should have "
"access to. This is often defined by local urban planning "
"documents."
)
@ -150,26 +150,26 @@ ARGS_SPEC = {
'display_name': 'Dichotomy',
'description': gettext(
'All pixels within the search radius contribute '
'equally to a greenspace pixel.'),
'equally to an urban nature pixel.'),
},
KERNEL_LABEL_EXPONENTIAL: {
'display_name': 'Exponential',
'description': gettext(
'Contributions to a greenspace pixel decrease '
'Contributions to an urban nature pixel decrease '
'exponentially, where '
'"weight = e^(-pixel_dist / search_radius)"'),
},
KERNEL_LABEL_GAUSSIAN: {
'display_name': 'Gaussian',
'description': gettext(
'Contributions to a greenspace pixel decrease '
'Contributions to an urban nature pixel decrease '
'according to a normal ("gaussian") distribution '
'with a sigma of 3.'),
},
KERNEL_LABEL_DENSITY: {
'display_name': 'Density',
'description': gettext(
'Contributions to a greenspace pixel decrease '
'Contributions to an urban nature pixel decrease '
'faster as distances approach the search radius. '
'Weights are calculated by '
'"weight = 0.75 * (1-(pixel_dist / search_radius)^2)"'
@ -178,7 +178,7 @@ ARGS_SPEC = {
# KERNEL_LABEL_POWER: {
# 'display_name': 'Power',
# 'description': gettext(
# 'Contributions to a greenspace pixel decrease '
# 'Contributions to an urban nature pixel decrease '
# 'according to a user-defined negative power function '
# 'of the form "weight = pixel_dist^beta", where beta '
# 'is expected to be negative and defined by the user.'
@ -186,8 +186,8 @@ ARGS_SPEC = {
# },
},
'about': (
'Pixels within the search radius of a greenspace pixel '
'have a distance-weighted contribution to a greenspace '
'Pixels within the search radius of an urban nature pixel '
'have a distance-weighted contribution to an urban nature '
'pixel according to the selected decay function.'),
},
'search_radius_mode': {
@ -201,14 +201,14 @@ ARGS_SPEC = {
RADIUS_OPT_UNIFORM: {
'display_name': 'Uniform radius',
'description': gettext(
'The search radius is the same for all greenspace '
'types.'),
'The search radius is the same for all types of '
'urban nature.'),
},
RADIUS_OPT_URBAN_NATURE: {
'display_name': 'Radius defined per greenspace class',
'display_name': 'Radius defined per urban nature class',
'description': gettext(
'The search radius is defined for each distinct '
'greenspace LULC classification.'),
'urban nature LULC classification.'),
},
RADIUS_OPT_POP_GROUP: {
'display_name': 'Radius defined per population group',
@ -272,8 +272,9 @@ ARGS_SPEC = {
'about': gettext(
'A table associating population groups with the distance '
'in meters that members of the population group will, on '
'average, travel to find greenspace. Required when running '
'the model with search radii defined per population group.'
'average, travel to find urban nature. Required when '
'running the model with search radii defined per population '
'group.'
),
},
# 'decay_function_power_beta': {
@ -332,12 +333,12 @@ def execute(args):
CSV with the following columns:
* ``lucode``: (required) the integer landcover code represented.
* ``greenspace``: (required) ``0`` or ``1`` indicating whether
this landcover code is (``1``) or is not (``0``) a greenspace
* ``urban_nature``: (required) ``0`` or ``1`` indicating whether
this landcover code is (``1``) or is not (``0``) an urban nature
pixel.
* ``search_radius_m``: (conditionally required) the search radius
for this greenspace landcover in meters. Required for all
greenspace lucodes if ``args['search_radius_mode'] ==
for this urban nature LULC class in meters. Required for all
urban nature LULC codes if ``args['search_radius_mode'] ==
RADIUS_OPT_URBAN_NATURE``
args['population_raster_path'] (string): (required) A string path to a
@ -600,7 +601,7 @@ 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['greenspace'] == 1]
greenspace_attrs = attr_table[attr_table['urban_nature'] == 1]
try:
search_radii = set(greenspace_attrs['search_radius_m'].unique())
except KeyError as missing_key:
@ -676,7 +677,7 @@ def execute(args):
'target_raster_path': greenspace_pixels_path,
},
target_path_list=[greenspace_pixels_path],
task_name='Identify greenspace areas',
task_name='Identify urban nature areas',
dependent_task_list=[lulc_mask_task]
)
@ -689,7 +690,7 @@ def execute(args):
decayed_population_path,
greenspace_population_ratio_path),
task_name=(
'2SFCA: Calculate R_j greenspace/population ratio - '
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[greenspace_population_ratio_path],
dependent_task_list=[
@ -705,7 +706,7 @@ def execute(args):
'target_path': file_registry['greenspace_supply'],
'working_dir': intermediate_dir,
},
task_name='2SFCA - greenspace supply',
task_name='2SFCA - urban nature supply',
target_path_list=[file_registry['greenspace_supply']],
dependent_task_list=[
kernel_tasks[search_radius_m],
@ -750,7 +751,7 @@ def execute(args):
'only_these_greenspace_codes': set([lucode]),
},
target_path_list=[greenspace_pixels_path],
task_name=f'Identify greenspace areas with lucode {lucode}',
task_name=f'Identify urban nature areas with lucode {lucode}',
dependent_task_list=[lulc_mask_task]
)
@ -763,7 +764,7 @@ def execute(args):
decayed_population_paths[search_radius_m],
greenspace_population_ratio_path),
task_name=(
'2SFCA: Calculate R_j greenspace/population ratio - '
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[greenspace_population_ratio_path],
dependent_task_list=[
@ -797,7 +798,7 @@ def execute(args):
'target_nodata': FLOAT32_NODATA,
'target_result_path': file_registry['greenspace_supply'],
},
task_name='2SFCA - greenspace supply total',
task_name='2SFCA - urban nature supply total',
target_path_list=[file_registry['greenspace_supply']],
dependent_task_list=partial_greenspace_supply_tasks
)
@ -816,7 +817,7 @@ def execute(args):
'target_raster_path': greenspace_pixels_path,
},
target_path_list=[greenspace_pixels_path],
task_name='Identify greenspace areas',
task_name='Identify urban nature areas',
dependent_task_list=[lulc_mask_task]
)
@ -856,7 +857,7 @@ def execute(args):
'target_nodata': FLOAT32_NODATA,
'target_result_path': sum_of_decayed_population_path,
},
task_name='2SFCA - greenspace supply total',
task_name='2SFCA - urban nature supply total',
target_path_list=[sum_of_decayed_population_path],
dependent_task_list=decayed_population_in_group_tasks
)
@ -867,7 +868,7 @@ def execute(args):
sum_of_decayed_population_path,
file_registry['greenspace_population_ratio']),
task_name=(
'2SFCA: Calculate R_j greenspace/population ratio - '
'2SFCA: Calculate R_j urban nature/population ratio - '
f'{search_radius_m}'),
target_path_list=[
file_registry['greenspace_population_ratio']],
@ -905,7 +906,7 @@ def execute(args):
'target_path': greenspace_supply_to_group_path,
'working_dir': intermediate_dir,
},
task_name=f'2SFCA - greenspace supply for {pop_group}',
task_name=f'2SFCA - urban nature supply for {pop_group}',
target_path_list=[greenspace_supply_to_group_path],
dependent_task_list=[
kernel_tasks[search_radius_m],
@ -931,7 +932,7 @@ def execute(args):
'nodata_target': FLOAT32_NODATA
},
task_name=(
f'Calculate per-capita greenspace balance - {pop_group}'),
f'Calculate per-capita urban nature balance - {pop_group}'),
target_path_list=[
per_cap_greenspace_balance_pop_group_path],
dependent_task_list=[
@ -956,7 +957,7 @@ def execute(args):
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita greenspace supply-demand',
task_name='Calculate per-capita urban nature supply-demand',
target_path_list=[
greenspace_supply_demand_by_group_path],
dependent_task_list=[
@ -1005,7 +1006,7 @@ def execute(args):
sorted(split_population_fields)],
'target_path': file_registry['greenspace_supply'],
},
task_name='2SFCA - greenspace supply total',
task_name='2SFCA - urban nature supply total',
target_path_list=[file_registry['greenspace_supply']],
dependent_task_list=[
*greenspace_supply_by_group_tasks,
@ -1068,7 +1069,7 @@ def execute(args):
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita greenspace balance',
task_name='Calculate per-capita urban nature balance',
target_path_list=[file_registry['greenspace_balance']],
dependent_task_list=[
greenspace_supply_task,
@ -1088,7 +1089,7 @@ def execute(args):
'datatype_target': gdal.GDT_Float32,
'nodata_target': FLOAT32_NODATA
},
task_name='Calculate per-capita greenspace supply-demand',
task_name='Calculate per-capita urban nature supply-demand',
target_path_list=[
file_registry['greenspace_supply_demand_budget']],
dependent_task_list=[
@ -1278,12 +1279,12 @@ def _weighted_sum(raster_path_list, weight_raster_list, target_path):
def _reclassify_and_multiply(
aois_raster_path, reclassification_map, supply_raster_path,
target_raster_path):
"""Create a raster of greenspace supply given areas of interest.
"""Create a raster of urban nature supply given areas of interest.
This is done by:
1. Reclassifying AOI IDs to population group ratios and then
2. Multiplying the population group ratios by the greenspace supply.
2. Multiplying the population group ratios by the urban nature supply.
Args:
aois_raster_path (string): The path to a raster of integers
@ -1291,7 +1292,7 @@ def _reclassify_and_multiply(
reclassification_map (dict): A dict mapping integer admin unit IDs to
float population proportions (values 0-1) for a given population
group.
supply_raster_path (string): A string path to a raster of greenspace
supply_raster_path (string): A string path to a raster of urban nature
supply values for the total population.
target_raster_path (string): The string path to where the resulting
supply-to-group raster should be written.
@ -1391,23 +1392,23 @@ def _rasterize_aois(base_raster_path, aois_vector_path,
def _reclassify_greenspace_area(
lulc_raster_path, lulc_attribute_table, target_raster_path,
only_these_greenspace_codes=None):
"""Reclassify LULC pixels into the greenspace area they represent.
"""Reclassify LULC pixels into the urban nature area they represent.
After execution, greenspace pixels will have values representing the
pixel's area, while non-greenspace pixels will have a pixel value of 0.
Nodata values will propagate to the output raster.
After execution, urban nature pixels will have values representing the
pixel's area, while pixels that are not urban nature will have a pixel
value of 0. Nodata values will propagate to the output raster.
Args:
lulc_raster_path (string): The path to a land-use/land-cover raster.
lulc_attribute_table (string): The path to a CSV table representing
LULC attributes. Must have "lucode" and "greenspace" columns.
target_raster_path (string): Where the reclassified greenspace raster
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
lucodes with a ``greenspace`` 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 have
greenspace area classified in the target raster path.
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
have urban nature area classified in the target raster path.
Returns:
``None``
@ -1423,7 +1424,7 @@ def _reclassify_greenspace_area(
else:
valid_greenspace_codes = set(
lucode for lucode, attributes in attribute_table_dict.items()
if (attributes['greenspace']) == 1)
if (attributes['urban_nature']) == 1)
greenspace_area_map = {}
for lucode, attributes in attribute_table_dict.items():
@ -1443,14 +1444,14 @@ def _reclassify_greenspace_area(
target_nodata=FLOAT32_NODATA,
error_details={
'raster_name': ARGS_SPEC['args']['lulc_raster_path']['name'],
'column_name': 'greenspace',
'column_name': 'urban_nature',
'table_name': ARGS_SPEC['args']['lulc_attribute_table']['name'],
}
)
def _filter_population(population, greenspace_budget, numpy_filter_op):
"""Filter the population by a defined op and the greenspace budget.
"""Filter the population by a defined op and the urban nature budget.
Note:
The ``population`` and ``greenspace_budget`` inputs must have the same
@ -1458,7 +1459,7 @@ def _filter_population(population, greenspace_budget, numpy_filter_op):
Args:
population (numpy.array): A numpy array with population counts.
greenspace_budget (numpy.array): A numpy array with the greenspace
greenspace_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
@ -1495,7 +1496,7 @@ def _supply_demand_vector_for_pop_groups(
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
group names to rasters of greenspace supply/demand for the given
group names to rasters of urban nature supply/demand for the given
group.
proportional_pop_paths_by_pop_group (dict): A dict mapping population
group names to rasters of the population of that group.
@ -1586,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 greenspace
greenspace_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
@ -1688,21 +1689,21 @@ def _write_supply_demand_vector(source_aoi_vector_path, feature_attrs,
def _greenspace_balance_op(greenspace_supply, greenspace_demand):
"""Calculate the per-capita greenspace balance.
"""Calculate the per-capita urban nature balance.
This is the amount of greenspace that each pixel has above (positive
This is the amount of urban nature that each pixel has above (positive
values) or below (negative values) the user-defined ``greenspace_demand``
value.
Args:
greenspace_supply (numpy.array): The supply of greenspace available to
greenspace_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 greenspace requirement,
greenspace_demand (float): The policy-defined urban nature requirement,
in square meters per person.
Returns:
A ``numpy.array`` of the calculated greenspace budget.
A ``numpy.array`` of the calculated urban nature budget.
"""
balance = numpy.full(
greenspace_supply.shape, FLOAT32_NODATA, dtype=numpy.float32)
@ -1712,12 +1713,12 @@ def _greenspace_balance_op(greenspace_supply, greenspace_demand):
def _greenspace_supply_demand_op(greenspace_balance, population):
"""Calculate the supply/demand of greenspace per person.
"""Calculate the supply/demand of urban nature per person.
Args:
greenspace_balance (numpy.array): The area of greenspace budgeted to
greenspace_balance (numpy.array): The area of urban nature budgeted to
each person, relative to a minimum required per-person area of
greenspace. This matrix must have ``FLOAT32_NODATA`` as its nodata
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
@ -1726,8 +1727,8 @@ def _greenspace_supply_demand_op(greenspace_balance, population):
nodata value.
Returns:
A ``numpy.array`` of the area (in square meters) of greenspace supplied
to each individual in each pixel.
A ``numpy.array`` of the area (in square meters) of urban nature
supplied to each individual in each pixel.
"""
supply_demand = numpy.full(
greenspace_balance.shape, FLOAT32_NODATA, dtype=numpy.float32)
@ -1742,17 +1743,17 @@ def _greenspace_supply_demand_op(greenspace_balance, population):
def _calculate_greenspace_population_ratio(
greenspace_area_raster_path, convolved_population_raster_path,
target_ratio_raster_path):
"""Calculate the greenspace-population ratio R_j.
"""Calculate the urban nature-population ratio R_j.
Args:
greenspace_area_raster_path (string): The path to a raster representing
the area of the pixel that represents greenspace. Pixel values
will be ``0`` if there is no greenspace.
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
representing population counts that have been convolved over some
search kernel and perhaps weighted.
target_ratio_raster_path (string): The path to where the target
greenspace-population raster should be written.
urban nature-population raster should be written.
Returns:
``None``.
@ -1763,12 +1764,12 @@ def _calculate_greenspace_population_ratio(
convolved_population_raster_path)['nodata'][0]
def _greenspace_population_ratio(greenspace_area, convolved_population):
"""Calculate the greenspace-population ratio R_j.
"""Calculate the urban nature-population ratio R_j.
Args:
greenspace_area (numpy.array): A numpy array representing the area
of greenspace in the pixel. Pixel values will be ``0`` if
there is no greenspace. Pixel values may also match
of urban nature in the pixel. Pixel values will be ``0`` if
there is no urban nature. Pixel values may also match
``greenspace_nodata``.
convolved_population (numpy.array): A numpy array where each pixel
represents the total number of people within a search radius of
@ -1776,12 +1777,12 @@ def _calculate_greenspace_population_ratio(
Returns:
A numpy array with the ratio ``R_j`` representing the
greenspace-population ratio with the following constraints:
urban nature-population ratio with the following constraints:
* ``convolved_population`` pixels that are numerically close to
``0`` are snapped to ``0`` to avoid unrealistically small
denominators in the final ratio.
* Any non-greenspace pixels will have a value of ``0`` in the
* Any non-urban nature pixels will have a value of ``0`` in the
output matrix.
"""
# ASSUMPTION: population nodata value is not close to 0.
@ -1794,7 +1795,7 @@ def _calculate_greenspace_population_ratio(
# This avoids divide-by-zero errors when taking the ratio.
valid_pixels = (convolved_population > 0)
# R_j is a ratio only calculated for the greenspace pixels.
# R_j is a ratio only calculated for the urban nature pixels.
greenspace_pixels = ~numpy.isclose(greenspace_area, 0)
valid_pixels &= greenspace_pixels
if population_nodata is not None:
@ -1806,14 +1807,14 @@ def _calculate_greenspace_population_ratio(
greenspace_area, greenspace_nodata)
# The user's guide specifies that if the population in the search
# radius is numerically 0, the greenspace/population ratio should be
# set to the greenspace area.
# radius is numerically 0, the urban nature/population ratio should be
# set to the urban nature area.
# A consequence of this is that as the population approaches 0 from the
# positive side, the ratio will approach infinity.
# After checking with the science team, we decided that where the
# population is less than or equal to 1, the calculated
# greenspace/population ratio would be set to the available greenspace
# on that pixel.
# urban nature/population ratio would be set to the available urban
# nature on that pixel.
population_close_to_zero = (convolved_population <= 1.0)
out_array[population_close_to_zero] = (
greenspace_area[population_close_to_zero])

View File

@ -75,7 +75,7 @@ def _build_model_args(workspace):
with open(args['lulc_attribute_table'], 'w') as attr_table:
attr_table.write(textwrap.dedent(
"""\
lucode,greenspace,search_radius_m
lucode,urban_nature,search_radius_m
0,0,100
1,1,100
2,0,100
@ -483,7 +483,7 @@ class UNATests(unittest.TestCase):
from natcap.invest import urban_nature_access
args = _build_model_args(self.workspace_dir)
args['search_radius_mode'] = urban_nature_access.RADIUS_OPT_GREENSPACE
args['search_radius_mode'] = urban_nature_access.RADIUS_OPT_URBAN_NATURE
# The split greenspace feature requires an extra column in the
# attribute table.
@ -690,7 +690,7 @@ class UNATests(unittest.TestCase):
os.path.join(self.workspace_dir, 'radius_greenspace'))
split_greenspace_args['results_suffix'] = 'greenspace'
split_greenspace_args['search_radius_mode'] = (
urban_nature_access.RADIUS_OPT_GREENSPACE)
urban_nature_access.RADIUS_OPT_URBAN_NATURE)
attribute_table = pandas.read_csv(
split_greenspace_args['lulc_attribute_table'])
new_search_radius_values = dict(
@ -779,7 +779,7 @@ class UNATests(unittest.TestCase):
urban_nature_access.execute(args)
self.assertIn('Invalid search radius mode provided', str(cm.exception))
for mode_suffix in ('UNIFORM', 'GREENSPACE', 'POP_GROUP'):
for mode_suffix in ('UNIFORM', 'URBAN_NATURE', 'POP_GROUP'):
valid_mode_string = getattr(urban_nature_access,
f'RADIUS_OPT_{mode_suffix}')
self.assertIn(valid_mode_string, str(cm.exception))
@ -806,5 +806,5 @@ class UNATests(unittest.TestCase):
"""UNA: Basic test for validation."""
from natcap.invest import urban_nature_access
args = _build_model_args(self.workspace_dir)
args['search_radius_mode'] = urban_nature_access.RADIUS_OPT_GREENSPACE
args['search_radius_mode'] = urban_nature_access.RADIUS_OPT_URBAN_NATURE
self.assertEqual(urban_nature_access.validate(args), [])