Exemple #1
0
def is_near(input_path, radius, distance_path, out_path):
    """Make binary raster of which pixels are within a radius of a '1' pixel.

    Args:
        input_path (str): path to a binary raster where '1' pixels are what
            we're measuring distance to, in this case roads/connected areas
        radius (float): distance in pixels which is considered "near".
            pixels this distance or less from a '1' pixel are marked '1' in
            the output. Distances are measured centerpoint to centerpoint.
        distance_path (str): path to write out the raster of distances
        out_path (str): path to write out the final thresholded raster.
            Pixels marked '1' are near to a '1' pixel in the input, pixels
            marked '0' are not.

    Returns:
        None
    """
    # Calculate the distance from each pixel to the nearest '1' pixel
    pygeoprocessing.distance_transform_edt(
        (input_path, 1),
        distance_path)

    def lte_threshold_op(array, threshold):
        """Binary array of elements less than or equal to the threshold."""
        # no need to mask nodata because distance_transform_edt doesn't
        # output any nodata pixels
        return array <= threshold

    # Threshold that to a binary array so '1' means it's within the radius
    pygeoprocessing.raster_calculator(
        [(distance_path, 1), (radius, 'raw')],
        lte_threshold_op,
        out_path,
        gdal.GDT_Byte,
        UINT8_NODATA)
Exemple #2
0
def _map_distance_from_forest_edge(lulc_uri, biophysical_table_uri,
                                   edge_distance_uri):
    """Generates a raster of forest edge distances where each pixel is the
    distance to the edge of the forest in meters.

    Parameters:
        lulc_uri (string): path to the landcover raster that contains integer
            landcover codes
        biophysical_table_uri (string): a path to a csv table that indexes
            landcover codes to forest type, contains at least the fields
            'lucode' (landcover integer code) and 'is_forest' (0 or 1 depending
            on landcover code type)
        edge_distance_uri (string): path to output raster where each pixel
            contains the euclidian pixel distance to nearest forest edges on
            all non-nodata values of lulc_uri

    Returns:
        None"""

    # Build a list of forest lucodes
    biophysical_table = pygeoprocessing.get_lookup_from_table(
        biophysical_table_uri, 'lucode')
    forest_codes = [
        lucode for (lucode, ludata) in biophysical_table.iteritems()
        if int(ludata['is_forest']) == 1
    ]

    # Make a raster where 1 is non-forest landcover types and 0 is forest
    forest_mask_nodata = 255
    lulc_nodata = pygeoprocessing.get_nodata_from_uri(lulc_uri)

    def mask_non_forest_op(lulc_array):
        """converts forest lulc codes to 1"""
        non_forest_mask = ~numpy.in1d(lulc_array.flatten(),
                                      forest_codes).reshape(lulc_array.shape)
        nodata_mask = lulc_array == lulc_nodata
        return numpy.where(nodata_mask, forest_mask_nodata, non_forest_mask)

    non_forest_mask_uri = pygeoprocessing.temporary_filename()
    out_pixel_size = pygeoprocessing.get_cell_size_from_uri(lulc_uri)
    pygeoprocessing.vectorize_datasets([lulc_uri],
                                       mask_non_forest_op,
                                       non_forest_mask_uri,
                                       gdal.GDT_Byte,
                                       forest_mask_nodata,
                                       out_pixel_size,
                                       "intersection",
                                       vectorize_op=False)

    # Do the distance transform on non-forest pixels
    pygeoprocessing.distance_transform_edt(non_forest_mask_uri,
                                           edge_distance_uri)

    # good practice to delete temporary files when we're done with them
    os.remove(non_forest_mask_uri)
Exemple #3
0
def _map_distance_from_tropical_forest_edge(base_lulc_raster_path,
                                            biophysical_table_path,
                                            edge_distance_path,
                                            target_non_forest_mask_path):
    """Generates a raster of forest edge distances where each pixel is the
    distance to the edge of the forest in meters.

    Parameters:
        base_lulc_raster_path (string): path to the landcover raster that
            contains integer landcover codes
        biophysical_table_path (string): path to a csv table that indexes
            landcover codes to forest type, contains at least the fields
            'lucode' (landcover integer code) and 'is_tropical_forest' (0 or 1
            depending on landcover code type)
        edge_distance_path (string): path to output raster where each pixel
            contains the euclidean pixel distance to nearest forest edges on
            all non-nodata values of base_lulc_raster_path
        target_non_forest_mask_path (string): path to the output non forest
            mask raster

    Returns:
        None

    """

    # Build a list of forest lucodes
    biophysical_table = utils.build_lookup_from_csv(biophysical_table_path,
                                                    'lucode',
                                                    to_lower=False)
    forest_codes = [
        lucode for (lucode, ludata) in biophysical_table.items()
        if int(ludata['is_tropical_forest']) == 1
    ]

    # Make a raster where 1 is non-forest landcover types and 0 is forest
    forest_mask_nodata = 255
    lulc_nodata = pygeoprocessing.get_raster_info(
        base_lulc_raster_path)['nodata']

    def mask_non_forest_op(lulc_array):
        """converts forest lulc codes to 1"""
        non_forest_mask = ~numpy.in1d(lulc_array.flatten(),
                                      forest_codes).reshape(lulc_array.shape)
        nodata_mask = lulc_array == lulc_nodata
        return numpy.where(nodata_mask, forest_mask_nodata, non_forest_mask)

    pygeoprocessing.raster_calculator([(base_lulc_raster_path, 1)],
                                      mask_non_forest_op,
                                      target_non_forest_mask_path,
                                      gdal.GDT_Byte, forest_mask_nodata)

    # Do the distance transform on non-forest pixels
    pygeoprocessing.distance_transform_edt((target_non_forest_mask_path, 1),
                                           edge_distance_path)
def _convert_landscape(
        base_lulc_uri, replacement_lucode, area_to_convert,
        focal_landcover_codes, convertible_type_list, score_weight, n_steps,
        smooth_distance_from_edge_uri, output_landscape_raster_uri,
        stats_uri):
    """Expand replacement lucodes in relation to the focal lucodes.

    If the sign on `score_weight` is positive, expansion occurs marches
    away from the focal types, while if `score_weight` is negative conversion
    marches toward the focal types.

    Parameters:
        base_lulc_uri (string): path to landcover raster that will be used as
            the base landcover map to agriculture pixels
        replacement_lucode (int): agriculture landcover code type found in the
            raster at `base_lulc_uri`
        area_to_convert (float): area (Ha) to convert to agriculture
        focal_landcover_codes (list of int): landcover codes that are used to
            calculate proximity
        convertible_type_list (list of int): landcover codes that are allowable
            to be converted to agriculture
        score_weight (float): this value is used to multiply the distance from
            the focal landcover types when prioritizing which pixels in
            `convertable_type_list` are to be converted.  If negative,
            conversion occurs toward the focal types, if positive occurs away
            from the focal types.
        n_steps (int): number of steps to convert the landscape.  On each step
            the distance transform will be applied on the
            current value of the `focal_landcover_codes` pixels in
            `output_landscape_raster_uri`.  On the first step the distance
            is calculated from `base_lulc_uri`.
        smooth_distance_from_edge_uri (string): an intermediate output showing
            the pixel distance from the edge of the base landcover types
        output_landscape_raster_uri (string): an output raster that will
            contain the final fragmented forest layer.
        stats_uri (string): a path to an output csv that records the number
            type, and area of pixels converted in `output_landscape_raster_uri`

    Returns:
        None.
    """
    tmp_file_registry = {
        'non_base_mask': pygeoprocessing.temporary_filename(),
        'base_mask': pygeoprocessing.temporary_filename(),
        'gaussian_kernel': pygeoprocessing.temporary_filename(),
        'distance_from_base_mask_edge': pygeoprocessing.temporary_filename(),
        'distance_from_non_base_mask_edge':
            pygeoprocessing.temporary_filename(),
        'convertible_distances': pygeoprocessing.temporary_filename(),
        'smooth_distance_from_edge': pygeoprocessing.temporary_filename(),
        'distance_from_edge': pygeoprocessing.temporary_filename(),
    }
    # a sigma of 1.0 gives nice visual results to smooth pixel level artifacts
    # since a pixel is the 1.0 unit
    _make_gaussian_kernel_uri(1.0, tmp_file_registry['gaussian_kernel'])

    # create the output raster first as a copy of the base landcover so it can
    # be looped on for each step
    lulc_nodata = pygeoprocessing.get_nodata_from_uri(base_lulc_uri)
    pixel_size_out = pygeoprocessing.get_cell_size_from_uri(base_lulc_uri)
    mask_nodata = 2
    pygeoprocessing.vectorize_datasets(
        [base_lulc_uri], lambda x: x, output_landscape_raster_uri,
        gdal.GDT_Int32, lulc_nodata, pixel_size_out, "intersection",
        vectorize_op=False, datasets_are_pre_aligned=True)

    # convert everything furthest from edge for each of n_steps
    pixel_area_ha = (
            pygeoprocessing.get_cell_size_from_uri(base_lulc_uri)**2 / 10000.0)
    max_pixels_to_convert = int(math.ceil(area_to_convert / pixel_area_ha))
    convertible_type_nodata = -1
    pixels_left_to_convert = max_pixels_to_convert
    pixels_to_convert = max_pixels_to_convert / n_steps
    stats_cache = collections.defaultdict(int)

    # pylint complains when these are defined inside the loop
    invert_mask = None
    distance_nodata = None

    for step_index in xrange(n_steps):
        LOGGER.info('step %d of %d', step_index+1, n_steps)
        pixels_left_to_convert -= pixels_to_convert

        # Often the last segement of the steps will overstep the  number of
        # pixels to convert, this check converts the exact amount
        if pixels_left_to_convert < 0:
            pixels_to_convert += pixels_left_to_convert

        # create distance transforms for inside and outside the base lulc codes
        LOGGER.info('create distance transform for current landcover')
        for invert_mask, mask_id, distance_id in [
            (False, 'non_base_mask', 'distance_from_non_base_mask_edge'),
            (True, 'base_mask', 'distance_from_base_mask_edge')]:

            def _mask_base_op(lulc_array):
                """Create a mask of valid non-base pixels only."""
                base_mask = numpy.in1d(
                    lulc_array.flatten(), focal_landcover_codes).reshape(
                    lulc_array.shape)
                if invert_mask:
                    base_mask = ~base_mask
                return numpy.where(
                    lulc_array == lulc_nodata, mask_nodata, base_mask)
            pygeoprocessing.vectorize_datasets(
                [output_landscape_raster_uri], _mask_base_op,
                tmp_file_registry[mask_id], gdal.GDT_Byte,
                mask_nodata, pixel_size_out, "intersection",
                vectorize_op=False, datasets_are_pre_aligned=True)

            # create distance transform for the current mask
            pygeoprocessing.distance_transform_edt(
                tmp_file_registry[mask_id], tmp_file_registry[distance_id])

        # combine inner and outer distance transforms into one
        distance_nodata = pygeoprocessing.get_nodata_from_uri(
            tmp_file_registry['distance_from_base_mask_edge'])

        def _combine_masks(base_distance_array, non_base_distance_array):
            """create a mask of valid non-base pixels only."""
            result = non_base_distance_array
            valid_base_mask = base_distance_array > 0.0
            result[valid_base_mask] = base_distance_array[valid_base_mask]
            return result
        pygeoprocessing.vectorize_datasets(
            [tmp_file_registry['distance_from_base_mask_edge'],
             tmp_file_registry['distance_from_non_base_mask_edge']],
            _combine_masks, tmp_file_registry['distance_from_edge'],
            gdal.GDT_Float32, distance_nodata, pixel_size_out, "intersection",
            vectorize_op=False, datasets_are_pre_aligned=True)

        # smooth the distance transform to avoid scanline artifacts
        pygeoprocessing.convolve_2d_uri(
            tmp_file_registry['distance_from_edge'],
            tmp_file_registry['gaussian_kernel'],
            smooth_distance_from_edge_uri)

        # turn inside and outside masks into a single mask
        def _mask_to_convertible_codes(distance_from_base_edge, lulc):
            """Mask out the distance transform to a set of lucodes."""
            convertible_mask = numpy.in1d(
                lulc.flatten(), convertible_type_list).reshape(lulc.shape)
            return numpy.where(
                convertible_mask, distance_from_base_edge,
                convertible_type_nodata)
        pygeoprocessing.vectorize_datasets(
            [smooth_distance_from_edge_uri, output_landscape_raster_uri],
            _mask_to_convertible_codes,
            tmp_file_registry['convertible_distances'], gdal.GDT_Float32,
            convertible_type_nodata, pixel_size_out, "intersection",
            vectorize_op=False, datasets_are_pre_aligned=True)

        LOGGER.info(
            'convert %d pixels to lucode %d', pixels_to_convert,
            replacement_lucode)
        _convert_by_score(
            tmp_file_registry['convertible_distances'], pixels_to_convert,
            output_landscape_raster_uri, replacement_lucode, stats_cache,
            score_weight)

    _log_stats(stats_cache, pixel_area_ha, stats_uri)
    for filename in tmp_file_registry.values():
        os.remove(filename)
Exemple #5
0
def _map_distance_from_tropical_forest_edge(base_lulc_raster_path,
                                            biophysical_table_path,
                                            edge_distance_path,
                                            target_non_forest_mask_path):
    """Generates a raster of forest edge distances.

    Generates a raster of forest edge distances where each pixel is the
    distance to the edge of the forest in meters.

    Args:
        base_lulc_raster_path (string): path to the landcover raster that
            contains integer landcover codes
        biophysical_table_path (string): path to a csv table that indexes
            landcover codes to forest type, contains at least the fields
            'lucode' (landcover integer code) and 'is_tropical_forest' (0 or 1
            depending on landcover code type)
        edge_distance_path (string): path to output raster where each pixel
            contains the euclidean pixel distance to nearest forest edges on
            all non-nodata values of base_lulc_raster_path
        target_non_forest_mask_path (string): path to the output non forest
            mask raster

    Returns:
        None

    """
    # Build a list of forest lucodes
    biophysical_table = utils.build_lookup_from_csv(biophysical_table_path,
                                                    'lucode',
                                                    to_lower=False)
    forest_codes = [
        lucode for (lucode, ludata) in biophysical_table.items()
        if int(ludata['is_tropical_forest']) == 1
    ]

    # Make a raster where 1 is non-forest landcover types and 0 is forest
    lulc_nodata = pygeoprocessing.get_raster_info(
        base_lulc_raster_path)['nodata']

    forest_mask_nodata = 255

    def mask_non_forest_op(lulc_array):
        """Convert forest lulc codes to 0.
        Args:
            lulc_array (numpy.ndarray): array representing a LULC raster where
                each forest LULC code is in `forest_codes`.
        Returns:
            numpy.ndarray with the same shape as lulc_array. All pixels are
                0 (forest), 1 (non-forest), or 255 (nodata).
        """
        non_forest_mask = ~numpy.isin(lulc_array, forest_codes)
        nodata_mask = lulc_array == lulc_nodata
        # where LULC has nodata, set value to nodata value (255)
        # where LULC has data, set to 0 if LULC is a forest type, 1 if it's not
        return numpy.where(nodata_mask, forest_mask_nodata, non_forest_mask)

    pygeoprocessing.raster_calculator([(base_lulc_raster_path, 1)],
                                      mask_non_forest_op,
                                      target_non_forest_mask_path,
                                      gdal.GDT_Byte, forest_mask_nodata)

    # Do the distance transform on non-forest pixels
    # This is the distance from each pixel to the nearest pixel with value 1.
    #   - for forest pixels, this is the distance to the forest edge
    #   - for non-forest pixels, this is 0
    #   - for nodata pixels, distance is calculated but is meaningless
    pygeoprocessing.distance_transform_edt((target_non_forest_mask_path, 1),
                                           edge_distance_path)

    # mask out the meaningless distance pixels so they don't affect the output
    lulc_raster = gdal.OpenEx(base_lulc_raster_path)
    lulc_band = lulc_raster.GetRasterBand(1)
    edge_distance_raster = gdal.OpenEx(edge_distance_path, gdal.GA_Update)
    edge_distance_band = edge_distance_raster.GetRasterBand(1)

    for offset_dict in pygeoprocessing.iterblocks((base_lulc_raster_path, 1),
                                                  offset_only=True):
        # where LULC has nodata, overwrite edge distance with nodata value
        lulc_block = lulc_band.ReadAsArray(**offset_dict)
        distance_block = edge_distance_band.ReadAsArray(**offset_dict)
        masked_distance_block = numpy.where(lulc_block == lulc_nodata,
                                            NODATA_VALUE, distance_block)
        edge_distance_band.WriteArray(masked_distance_block,
                                      xoff=offset_dict['xoff'],
                                      yoff=offset_dict['yoff'])
Exemple #6
0
def _convert_landscape(
        base_lulc_path, replacement_lucode, area_to_convert,
        focal_landcover_codes, convertible_type_list, score_weight, n_steps,
        smooth_distance_from_edge_path, output_landscape_raster_path,
        stats_path, workspace_dir):
    """Expand replacement lucodes in relation to the focal lucodes.

    If the sign on `score_weight` is positive, expansion occurs marches
    away from the focal types, while if `score_weight` is negative conversion
    marches toward the focal types.

    Parameters:
        base_lulc_path (string): path to landcover raster that will be used as
            the base landcover map to agriculture pixels
        replacement_lucode (int): agriculture landcover code type found in the
            raster at `base_lulc_path`
        area_to_convert (float): area (Ha) to convert to agriculture
        focal_landcover_codes (list of int): landcover codes that are used to
            calculate proximity
        convertible_type_list (list of int): landcover codes that are allowable
            to be converted to agriculture
        score_weight (float): this value is used to multiply the distance from
            the focal landcover types when prioritizing which pixels in
            `convertable_type_list` are to be converted.  If negative,
            conversion occurs toward the focal types, if positive occurs away
            from the focal types.
        n_steps (int): number of steps to convert the landscape.  On each step
            the distance transform will be applied on the
            current value of the `focal_landcover_codes` pixels in
            `output_landscape_raster_path`.  On the first step the distance
            is calculated from `base_lulc_path`.
        smooth_distance_from_edge_path (string): an intermediate output showing
            the pixel distance from the edge of the base landcover types
        output_landscape_raster_path (string): an output raster that will
            contain the final fragmented forest layer.
        stats_path (string): a path to an output csv that records the number
            type, and area of pixels converted in `output_landscape_raster_path`
        workspace_dir (string): workspace directory that will be used to
            hold temporary files. On a successful run of this function,
            the temporary directory will be removed.

    Returns:
        None.

    """
    temp_dir = tempfile.mkdtemp(prefix='temp_dir', dir=workspace_dir)
    tmp_file_registry = {
        'non_base_mask': os.path.join(temp_dir, 'non_base_mask.tif'),
        'base_mask': os.path.join(temp_dir, 'base_mask.tif'),
        'gaussian_kernel': os.path.join(temp_dir, 'gaussian_kernel.tif'),
        'distance_from_base_mask_edge': os.path.join(
            temp_dir, 'distance_from_base_mask_edge.tif'),
        'distance_from_non_base_mask_edge': os.path.join(
            temp_dir, 'distance_from_non_base_mask_edge.tif'),
        'convertible_distances': os.path.join(
            temp_dir, 'convertible_distances.tif'),
        'distance_from_edge': os.path.join(
            temp_dir, 'distance_from_edge.tif'),
    }
    # a sigma of 1.0 gives nice visual results to smooth pixel level artifacts
    # since a pixel is the 1.0 unit
    _make_gaussian_kernel_path(1.0, tmp_file_registry['gaussian_kernel'])

    # create the output raster first as a copy of the base landcover so it can
    # be looped on for each step
    lulc_raster_info = pygeoprocessing.get_raster_info(base_lulc_path)
    lulc_nodata = lulc_raster_info['nodata'][0]
    mask_nodata = 2
    pygeoprocessing.raster_calculator(
        [(base_lulc_path, 1)], lambda x: x, output_landscape_raster_path,
        gdal.GDT_Int32, lulc_nodata)

    # convert everything furthest from edge for each of n_steps
    pixel_area_ha = (
        abs(lulc_raster_info['pixel_size'][0]) *
        abs(lulc_raster_info['pixel_size'][1])) / 10000.0
    max_pixels_to_convert = int(math.ceil(area_to_convert / pixel_area_ha))
    convertible_type_nodata = -1
    pixels_left_to_convert = max_pixels_to_convert
    pixels_to_convert = max_pixels_to_convert / n_steps
    stats_cache = collections.defaultdict(int)

    # pylint complains when these are defined inside the loop
    invert_mask = None
    distance_nodata = None

    for step_index in range(n_steps):
        LOGGER.info('step %d of %d', step_index+1, n_steps)
        pixels_left_to_convert -= pixels_to_convert

        # Often the last segement of the steps will overstep the  number of
        # pixels to convert, this check converts the exact amount
        if pixels_left_to_convert < 0:
            pixels_to_convert += pixels_left_to_convert

        # create distance transforms for inside and outside the base lulc codes
        LOGGER.info('create distance transform for current landcover')
        for invert_mask, mask_id, distance_id in [
                (False, 'non_base_mask', 'distance_from_non_base_mask_edge'),
                (True, 'base_mask', 'distance_from_base_mask_edge')]:

            def _mask_base_op(lulc_array):
                """Create a mask of valid non-base pixels only."""
                base_mask = numpy.in1d(
                    lulc_array.flatten(), focal_landcover_codes).reshape(
                        lulc_array.shape)
                if invert_mask:
                    base_mask = ~base_mask
                return numpy.where(
                    lulc_array == lulc_nodata,
                    mask_nodata, base_mask)
            pygeoprocessing.raster_calculator(
                [(output_landscape_raster_path, 1)], _mask_base_op,
                tmp_file_registry[mask_id], gdal.GDT_Byte,
                mask_nodata)

            # create distance transform for the current mask
            pygeoprocessing.distance_transform_edt(
                (tmp_file_registry[mask_id], 1),
                tmp_file_registry[distance_id],
                working_dir=temp_dir)

        # combine inner and outer distance transforms into one
        distance_nodata = pygeoprocessing.get_raster_info(
            tmp_file_registry['distance_from_base_mask_edge'])['nodata'][0]

        def _combine_masks(base_distance_array, non_base_distance_array):
            """Create a mask of valid non-base pixels only."""
            result = non_base_distance_array
            valid_base_mask = base_distance_array > 0.0
            result[valid_base_mask] = base_distance_array[valid_base_mask]
            return result
        pygeoprocessing.raster_calculator(
            [(tmp_file_registry['distance_from_base_mask_edge'], 1),
             (tmp_file_registry['distance_from_non_base_mask_edge'], 1)],
            _combine_masks, tmp_file_registry['distance_from_edge'],
            gdal.GDT_Float32, distance_nodata)

        # smooth the distance transform to avoid scanline artifacts
        pygeoprocessing.convolve_2d(
            (tmp_file_registry['distance_from_edge'], 1),
            (tmp_file_registry['gaussian_kernel'], 1),
            smooth_distance_from_edge_path)

        # turn inside and outside masks into a single mask
        def _mask_to_convertible_codes(distance_from_base_edge, lulc):
            """Mask out the distance transform to a set of lucodes."""
            convertible_mask = numpy.in1d(
                lulc.flatten(), convertible_type_list).reshape(lulc.shape)
            return numpy.where(
                convertible_mask, distance_from_base_edge,
                convertible_type_nodata)
        pygeoprocessing.raster_calculator(
            [(smooth_distance_from_edge_path, 1),
             (output_landscape_raster_path, 1)],
            _mask_to_convertible_codes,
            tmp_file_registry['convertible_distances'], gdal.GDT_Float32,
            convertible_type_nodata)

        LOGGER.info(
            'convert %d pixels to lucode %d', pixels_to_convert,
            replacement_lucode)
        _convert_by_score(
            tmp_file_registry['convertible_distances'], pixels_to_convert,
            output_landscape_raster_path, replacement_lucode, stats_cache,
            score_weight)

    _log_stats(stats_cache, pixel_area_ha, stats_path)
    try:
        shutil.rmtree(temp_dir)
    except OSError:
        LOGGER.warn(
            "Could not delete temporary working directory '%s'", temp_dir)