Beispiel #1
0
def prepare_fc():
    if not arcpy.Exists(pp_c.STATS_FC):
        pp_c.log_debug("Creating '%s'" % pp_c.STATS_FC)
        # Make a copy of the community feature class and reproject it
        communities_fc = arcpy.conversion.FeatureClassToFeatureClass(
            pp_c.MUNI_COMMUNITY_AREA, arcpy.env.scratchGDB, 'communities')[0]
        arcpy.management.Project(
            communities_fc, pp_c.STATS_FC,
            arcpy.Describe(pp_c.TREES_TEMPLATE_FC).spatialReference)

        # Add the stats fields
        for name, type_ in pp_c.COMMUNITY_STATS_SPEC + pp_c.TREE_STATS_SPEC + pp_c.DERIVED_STATS:
            arcpy.AddField_management(pp_c.STATS_FC, name, type_)
        # Fill in the community_id field
        arcpy.management.CalculateField(pp_c.STATS_FC,
                                        pp_c.STATS_COMMUNITY_COL, '!OBJECTID!')
        # Fill in the acres field
        arcpy.management.CalculateField(pp_c.STATS_FC, 'acres',
                                        "!shape.area@acres!")
        # Join in the land cover information for each community
        arcpy.management.JoinField(pp_c.STATS_FC,
                                   pp_c.STATS_COMMUNITY_NAME_COL,
                                   pp_c.COMMUNITY_LAND_COVER_TBL,
                                   pp_c.LAND_COVER_COMMUNITY_NAME_COL,
                                   [s[0] for s in pp_c.LAND_COVER_STATS])
        arcpy.management.CalculateField(pp_c.STATS_FC, 'Canopy',
                                        '!Canopy!*100', "PYTHON3", "", "FLOAT")
        # Map community id to name
        arcpy.management.AssignDomainToField(pp_c.STATS_FC,
                                             pp_c.STATS_COMMUNITY_COL,
                                             pp_c.COMMUNITY_DOMAIN_NAME)

        pp_c.delete([communities_fc])
    return
Beispiel #2
0
def __get_mesh_algorithm(mesh_row_dim, mesh_col_dim, polygon):
    threshold_cells_1 = 10000
    threshold_cells_2 = 100000

    mesh_cells = mesh_row_dim * mesh_col_dim

    if mesh_cells < threshold_cells_1:
        return MESH_ALGORITHM_SMALL
    elif mesh_cells > threshold_cells_2:
        pp_c.log_debug('Big Mesh1 %i cells' % (mesh_cells))
        return MESH_ALGORITHM_BIG
    else:
        polygon_cells = round(
            polygon.getArea('PLANAR', 'SQUAREMETERS') /
            (MIN_DIAMETER * MIN_DIAMETER))
        percent_polygon = (polygon_cells / mesh_cells) * 100
        precent_gap = (mesh_cells - threshold_cells_1) / (
            threshold_cells_2 - threshold_cells_1) * 100
        if percent_polygon > precent_gap:
            return MESH_ALGORITHM_SMALL
        else:
            pp_c.log_debug(
                'Big Mesh2 %i cells, %i percent polygon, %i percent gap' %
                (mesh_cells, int(percent_polygon), int(precent_gap)))
            return MESH_ALGORITHM_BIG
Beispiel #3
0
def combine_spaces_fcs(community_specs):
    pp_c.log_debug('Combining spaces feature classes')
    communities = [c[0] for c in community_specs]
    community_fcs = [
        pp_c.get_community_fc_name(c, pp_c.COMMUNITY_SPACES_FC)
        for c in communities
    ]
    community_ids = [str(c[2]) for c in community_specs]

    out_fc = pp_c.SPACES_FC

    if not pp_c.IS_SCRATCH_OUTPUT_DATA:
        pp_c.log_debug(
            'Deleting existing features in combined spaces feature class')
        where = "%s IN (%s)" % (pp_c.SPACES_COMMUNITY_COL,
                                ','.join(community_ids))
        old_records = arcpy.SelectLayerByAttribute_management(
            out_fc, 'NEW_SELECTION', where)[0]
        arcpy.management.DeleteFeatures(old_records)

    if len(communities) > 10:
        pp_c.remove_indexes(out_fc, pp_c.SPACES_INDEX_SPEC)

    pp_c.log_info('Write to combined spaces feature class')
    arcpy.management.Append(community_fcs, out_fc)

    if len(communities) > 10:
        pp_c.add_indexes(out_fc, pp_c.SPACES_INDEX_SPEC)

    return
Beispiel #4
0
def prepare_fc():
    if not arcpy.Exists(pp_c.SPACES_FC):
        pp_c.log_debug("Creating '%s'" % pp_c.SPACES_FC)
        sr = arcpy.Describe(pp_c.SPACES_TEMPLATE_FC).spatialReference
        arcpy.CreateFeatureclass_management(os.path.dirname(pp_c.SPACES_FC),
                                            os.path.basename(pp_c.SPACES_FC),
                                            'POLYGON', pp_c.SPACES_TEMPLATE_FC,
                                            "DISABLED", "DISABLED", sr)
        arcpy.management.AssignDomainToField(pp_c.SPACES_FC,
                                             pp_c.SPACES_COMMUNITY_COL,
                                             pp_c.COMMUNITY_DOMAIN_NAME)
    return
Beispiel #5
0
def create_canopies(community_spec):
    try:

        # Process the input community
        community_name, acres, community_id = community_spec
        pp_c.log_info('Generating canopies', community_name)

        input_fc = pp_c.get_community_fc_name(community_name,
                                              pp_c.COMMUNITY_TREES_FC)
        output_fc = pp_c.get_community_fc_name(community_name,
                                               pp_c.COMMUNITY_CANOPIES_FC)

        intermediate_output_gdb = pp_c.prepare_intermediate_output_gdb(
            pp_c.USE_IN_MEM)
        trees_buffered = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                    'tbuffered_int',
                                                    community_id,
                                                    pp_c.USE_IN_MEM)

        pp_c.log_debug('Populate the "radius" field', community_name)
        arcpy.management.CalculateField(
            input_fc, 'radius', "get_radius(!code!)", "PYTHON3",
            r"""def get_radius (code):
            if code == 0:
                return %1.2f
            elif code == 1:
                return %1.2f
            else:
                return %1.2f""" %
            (pp_c.TREE_RADIUS[pp_c.SMALL], pp_c.TREE_RADIUS[pp_c.MEDIUM],
             pp_c.TREE_RADIUS[pp_c.BIG]), "FLOAT")

        pp_c.log_debug('Buffer the points', community_name)
        arcpy.analysis.Buffer(input_fc, trees_buffered, "radius", "FULL",
                              "ROUND", "NONE", None, "PLANAR")

        arcpy.management.DeleteField(input_fc, 'radius')

        pp_c.log_debug('Writing community canopies', community_name)
        sr = arcpy.Describe(pp_c.CANOPIES_TEMPLATE_FC).spatialReference
        arcpy.CreateFeatureclass_management(os.path.dirname(output_fc),
                                            os.path.basename(output_fc),
                                            'POLYGON',
                                            pp_c.CANOPIES_TEMPLATE_FC,
                                            "DISABLED", "DISABLED", sr)
        arcpy.management.Append(trees_buffered, output_fc, "NO_TEST")

        pp_c.delete([trees_buffered])

    except Exception as ex:
        pp_c.log_debug('Exception: %s' % (str(ex)))
        raise ex
Beispiel #6
0
def prepare_fc():
    if not arcpy.Exists(pp_c.TREES_FC):
        pp_c.log_debug("Creating '%s'" % pp_c.TREES_FC)
        sr = arcpy.Describe(pp_c.TREES_TEMPLATE_FC).spatialReference
        arcpy.CreateFeatureclass_management(os.path.dirname(pp_c.TREES_FC),
                                            os.path.basename(pp_c.TREES_FC),
                                            'POINT', pp_c.TREES_TEMPLATE_FC,
                                            "DISABLED", "DISABLED", sr)
        arcpy.management.AssignDomainToField(pp_c.TREES_FC,
                                             pp_c.TREES_LANDUSE_COL,
                                             pp_c.LANDUSE_DOMAIN_NAME)
        arcpy.management.AssignDomainToField(pp_c.TREES_FC,
                                             pp_c.TREES_PUBLIC_PRIVATE_COL,
                                             pp_c.PUBLIC_PRIVATE_DOMAIN_NAME)
        arcpy.management.AssignDomainToField(pp_c.TREES_FC,
                                             pp_c.TREES_COMMUNITY_COL,
                                             pp_c.COMMUNITY_DOMAIN_NAME)
        arcpy.management.AssignDomainToField(pp_c.TREES_FC,
                                             pp_c.TREES_SIZE_COL,
                                             pp_c.TREE_SIZE_DOMAIN_NAME)
def create_spaces_and_trees_and_canopies(community_spec):
    try:
        arcpy.env.outputZFlag = "Disabled"
        arcpy.env.outputMFlag = "Disabled"
        arcpy.overwriteOutput = True

        __prepare_community_gdb(community_spec)

        if pp_c.IS_CREATE_SPACES:
            pp.spaces.find_spaces(community_spec)

        if pp_c.IS_CREATE_TREES:
            pp.trees.site_trees(community_spec)

        if pp_c.IS_CREATE_CANOPIES:
            pp.canopies.create_canopies(community_spec)

        pp_c.log_info('Complete', community_spec[0])
        return

    except Exception as ex:
        pp_c.log_debug('Exception: %s' % (str(ex)))
        raise ex
Beispiel #8
0
def combine_stats(community_specs):
    for community, acres, community_id in community_specs:
        pp_c.log_debug('Updating final stats for %s' % community)

        # # Update space stats
        # space_stats = __read_community_stats (community, community_id, pp_c.COMMUNITY_SPACE_STATS_TBL, pp_c.SPACE_STATS_SPEC)
        # update_stats (pp_c.STATS_FC, community_id, space_stats, pp_c.SPACE_STATS_SPEC)

        # Update tree stats
        tree_stats = __read_community_stats(community, community_id,
                                            pp_c.COMMUNITY_TREE_STATS_TBL,
                                            pp_c.TREE_STATS_SPEC)
        update_stats(pp_c.STATS_FC, community_id, tree_stats,
                     pp_c.TREE_STATS_SPEC)

        # Update derived stats
        field_names = [
            'acres', 'small', 'medium', 'large', 'Canopy', 'trees',
            'trees_per_acre', 'canopy_y0', 'canopy_y5', 'canopy_y10',
            'canopy_y15', 'canopy_y20', 'canopy_y25'
        ]
        with arcpy.da.UpdateCursor(
                pp_c.STATS_FC, field_names, '%s = %i' %
            (pp_c.STATS_COMMUNITY_COL, community_id)) as cursor:
            for acres, small, medium, large, percent_canopy, trees, trees_per_acre, cy0, cy5, cy10, cy15, cy20, cy25 in cursor:
                existing_canopy_acres = acres * percent_canopy / 100.0
                trees = small + medium + large
                trees_per_acre = trees / acres
                canopy_growth = compute_canopy_growth(acres, percent_canopy,
                                                      small, medium, large)
                cursor.updateRow([
                    acres, small, medium, large, percent_canopy, trees,
                    trees_per_acre
                ] + [(existing_canopy_acres + canopy_growth[i]) / acres * 100
                     for i in range(0, 26, 5)])
    return
Beispiel #9
0
def __find_overlaps(intermediate_output_gdb, in_fc, community_name,
                    community_id):
    trees_buffered = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                'tbuffered_int', community_id,
                                                pp_c.USE_IN_MEM)
    trees_overlapped = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                  'tolap_int', community_id,
                                                  pp_c.USE_IN_MEM)

    pp_c.log_debug('Populate the "radius" field', community_name)
    arcpy.management.CalculateField(
        in_fc, 'radius', "get_radius(!code!)", "PYTHON3",
        r"""def get_radius (code):
        if code == 0:
            return %1.2f
        elif code == 1:
            return %1.2f
        else:
            return %1.2f""" %
        (TREE_RADIUS[SMALL], TREE_RADIUS[MEDIUM], TREE_RADIUS[BIG]), "FLOAT")

    pp_c.log_debug('Buffer the points', community_name)
    arcpy.analysis.Buffer(in_fc, trees_buffered, "radius", "FULL", "ROUND",
                          "NONE", None, "PLANAR")

    pp_c.log_debug('Find overlapping trees', community_name)
    arcpy.analysis.Intersect(trees_buffered, trees_overlapped, "ONLY_FID",
                             None, "INPUT")
    pp_c.delete([trees_buffered])
    overlap_oids = []
    with arcpy.da.SearchCursor(trees_overlapped,
                               ['FID_%s' %
                                (os.path.basename(trees_buffered))]) as cursor:
        for oid in cursor:
            overlap_oids.append(oid[0])
    pp_c.delete([trees_overlapped])
    return overlap_oids
Beispiel #10
0
def site_trees(community_spec):

    community_name, acres, community_id = community_spec
    pp_c.log_info('Siting trees.', community_name)

    size_stats = [0, 0, 0]
    landuse_stats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    public_private_stats = [0, 0]

    input_fc = pp_c.get_community_fc_name(community_spec[0],
                                          pp_c.COMMUNITY_SPACES_FC)
    output_fc = pp_c.get_community_fc_name(community_spec[0],
                                           pp_c.COMMUNITY_TREES_FC)

    if arcpy.Exists(output_fc):
        arcpy.Delete_management(output_fc)
    arcpy.CreateFeatureclass_management(os.path.dirname(output_fc),
                                        os.path.basename(output_fc), "POINT",
                                        pp_c.TREES_TEMPLATE_FC, "DISABLED",
                                        "DISABLED", pp_c.TREES_TEMPLATE_FC)

    intermediate_output_gdb = pp_c.prepare_intermediate_output_gdb(
        pp_c.USE_IN_MEM)
    intermediate_trees = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                    'trees_int', community_id,
                                                    pp_c.USE_IN_MEM)
    intermediate_trees_lu = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                       'tlu_int', community_id,
                                                       pp_c.USE_IN_MEM)
    intermediate_trees_lu_public = pp_c.get_intermediate_name(
        intermediate_output_gdb, 'tlpub_int', community_id, pp_c.USE_IN_MEM)

    arcpy.CreateFeatureclass_management(os.path.dirname(intermediate_trees),
                                        os.path.basename(intermediate_trees),
                                        "POINT", pp_c.TREES_TEMPLATE_FC,
                                        "DISABLED", "DISABLED",
                                        pp_c.TREES_TEMPLATE_FC)
    arcpy.DeleteField_management(intermediate_trees, ['land_use'])

    community_stats_tbl = pp.stats.prepare_community_stats_tbl(
        community_name, community_id, pp_c.COMMUNITY_TREE_STATS_TBL,
        pp_c.TREE_STATS_SPEC)

    if WRITE_TO_DEBUG_MESH_FC:
        arcpy.management.DeleteFeatures(MESH_FC)

    pp_c.log_info("Calculating points", community_name)
    query = "Shape_Area > 2.5"
    with arcpy.da.SearchCursor(input_fc,
                               ['OBJECTID', 'SHAPE@', 'community_id'],
                               query) as cursor:
        for oid, polygon, community in cursor:
            x_min, y_min, x_max, y_max = polygon.extent.XMin, polygon.extent.YMin, polygon.extent.XMax, polygon.extent.YMax

            center = arcpy.Point((x_min + x_max) / 2, (y_min + y_max) / 2)
            tiers = math.ceil(
                max((x_max - x_min) / 2, (y_max - y_min) / 2) / MIN_DIAMETER)

            # The mesh orgin is the NW corner and indexed row major as  [row][col]
            mesh_row_dim, mesh_col_dim = __get_mesh_dim(polygon, center, tiers)
            nw_corner = arcpy.Point(
                center.X - (mesh_col_dim * MIN_DIAMETER) / 2,
                center.Y + (mesh_row_dim * MIN_DIAMETER) / 2)
            center_row, center_col = __point_to_mesh(center, nw_corner)

            mesh_type = __get_mesh_algorithm(mesh_row_dim, mesh_col_dim,
                                             polygon)
            if mesh_type == MESH_ALGORITHM_SMALL:
                mesh = [m[:] for m in [[VACANT] * mesh_col_dim] * mesh_row_dim]
            elif mesh_type == MESH_ALGORITHM_BIG:
                mesh = __get_mesh(mesh_row_dim, mesh_col_dim, polygon,
                                  nw_corner, input_fc)

            plant_points = dict()

            for tree_category in TREE_CATEGORIES:
                for tier_idx in range(0, tiers + 1):
                    for row, col in __get_tier_vacancies(
                            center_row, center_col, tier_idx, mesh,
                            mesh_row_dim, mesh_col_dim):
                        fp = __get_footprint(row, col,
                                             TREE_FOOTPRINT_DIM[tree_category],
                                             mesh_row_dim, mesh_col_dim)
                        if __is_footprint_clean(mesh, *fp):
                            if is_point_in_polygon(row, col, polygon,
                                                   nw_corner, mesh, mesh_type,
                                                   plant_points):
                                __occupy_footprint(mesh, *fp, row, col,
                                                   tree_category)

            with arcpy.da.InsertCursor(
                    intermediate_trees,
                ['SHAPE@', 'code', 'p_oid', 'community_id']) as cursor:
                for row, col in plant_points.keys():
                    cursor.insertRow([
                        plant_points[(row, col)], mesh[row][col], oid,
                        community
                    ])

            if WRITE_TO_DEBUG_MESH_FC:
                with arcpy.da.InsertCursor(
                        MESH_FC,
                    ['SHAPE@', 'code', 'row', 'col', 'x', 'y', 'dim'
                     ]) as cursor:
                    for r in range(0, mesh_row_dim):
                        for c in range(0, mesh_col_dim):
                            p = __mesh_to_point(r, c, nw_corner)
                            cursor.insertRow(
                                [p, mesh[r][c], r, c, p.X, p.Y, mesh_row_dim])

    pp_c.log_debug('Identify land use', community_name)
    arcpy.Identity_analysis(intermediate_trees, pp_c.LAND_USE_2015,
                            intermediate_trees_lu, "ALL", "",
                            "NO_RELATIONSHIPS")
    pp_c.delete([intermediate_trees])
    arcpy.management.AlterField(intermediate_trees_lu, 'LandUse', 'land_use')

    pp_c.log_debug('Identify public land', community_name)
    arcpy.Identity_analysis(intermediate_trees_lu, pp_c.PUBLIC_LAND,
                            intermediate_trees_lu_public, "ONLY_FID", "",
                            "NO_RELATIONSHIPS")
    pp_c.delete([intermediate_trees_lu])

    pp_c.log_debug('Populate the "is_public" field', community_name)
    arcpy.management.CalculateField(
        intermediate_trees_lu_public, pp_c.SPACES_PUBLIC_PRIVATE_COL,
        "is_public(!FID_%s!)" % (os.path.basename(pp_c.PUBLIC_LAND)),
        "PYTHON3", r"""def is_public (fid):
        if fid == -1:
            return 0
        else:
            return 1""", "SHORT")

    # __downsize (intermediate_output_gdb, intermediate_trees_lu_public, community_name, community_id)

    pp_c.log_debug('Find overlaps', community_name)
    overlap_oids = __find_overlaps(intermediate_output_gdb,
                                   intermediate_trees_lu_public,
                                   community_name, community_id)

    pp_c.log_debug(
        'Collecting tree statistics, fixing bad land uses, and downsizing overlaps',
        community_name)
    big_to_medium, medium_to_small, small = 0, 0, 0
    with arcpy.da.UpdateCursor(intermediate_trees_lu_public, [
            'objectid',
            'code',
            'land_use',
            'is_public',
    ]) as cursor:
        for oid, tree_size, land_use, is_public in cursor:
            if land_use not in pp_c.LANDUSE_DOMAIN.values():
                # Fix up unrecognized land use
                land_use = pp_c.LANDUSE_DOMAIN['Other']
                cursor.updateRow([oid, tree_size, land_use, is_public])
            if oid in overlap_oids:
                if tree_size == BIG:
                    tree_size = MEDIUM
                    big_to_medium = big_to_medium + 1
                    cursor.updateRow([oid, tree_size, land_use, is_public])
                elif tree_size == MEDIUM:
                    tree_size = SMALL
                    medium_to_small = medium_to_small + 1
                    cursor.updateRow([oid, tree_size, land_use, is_public])
                else:
                    tree_size = SMALL
                    small = small + 1
            size_stats[tree_size] = size_stats[tree_size] + 1
            landuse_stats[land_use] = landuse_stats[land_use] + 1
            public_private_stats[
                is_public] = public_private_stats[is_public] + 1
    pp_c.log_debug(
        "Updated feature class with new sizes. L->M=%i, M->S=%i, S=%i" %
        (big_to_medium, medium_to_small, small), community_name)

    pp_c.log_debug("Writing points to '%s'" % output_fc, community_name)
    arcpy.management.Append(intermediate_trees_lu_public, output_fc, "NO_TEST")
    pp_c.delete([intermediate_trees_lu_public])

    pp.stats.update_stats(
        community_stats_tbl, community_id,
        size_stats + landuse_stats[1:] + public_private_stats,
        pp_c.TREE_STATS_SPEC)

    return
Beispiel #11
0
def find_spaces(community_spec):
    try:

        # Process the input community
        community, acres, idx = community_spec
        pp_c.log_info('Finding spaces. %i acres' % (acres), community)

        use_in_mem = pp_c.USE_IN_MEM if community not in PROBLEM_COMMUNITIES else False

        intermediate_output_gdb = pp_c.prepare_intermediate_output_gdb(
            use_in_mem)

        canopy_clipped = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                    'canopy_clipped', idx,
                                                    use_in_mem)
        plantable_region_clipped = pp_c.get_intermediate_name(
            intermediate_output_gdb, 'plantable_region_clipped', idx,
            use_in_mem)
        buildings_clipped = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                       'buildings_clipped',
                                                       idx, use_in_mem)
        minus_trees = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                 'minus_trees', idx,
                                                 use_in_mem)
        minus_trees_buildings = pp_c.get_intermediate_name(
            intermediate_output_gdb, 'minus_trees_buildings', idx, use_in_mem)
        plantable_poly = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                    'plantable_poly', idx,
                                                    use_in_mem)
        plantable_single_poly = pp_c.get_intermediate_name(
            intermediate_output_gdb, 'plantable_single_poly', idx, use_in_mem)
        plantable_muni = pp_c.get_intermediate_name(intermediate_output_gdb,
                                                    'plantable_muni', idx,
                                                    use_in_mem)

        community_fc = pp_c.get_community_fc_name(community,
                                                  pp_c.COMMUNITY_SPACES_FC)
        pp_c.delete([community_fc])

        pp_c.log_debug('Getting community boundary', community)
        community_boundary = arcpy.SelectLayerByAttribute_management(
            pp_c.MUNI_COMMUNITY_AREA, 'NEW_SELECTION',
            "COMMUNITY = '%s'" % (community))[0]

        pp_c.log_debug(
            'Clipping %s' % (os.path.basename(pp_c.CANOPY_EXPAND_TIF)),
            community)
        arcpy.management.Clip(pp_c.CANOPY_EXPAND_TIF,
                              '#',
                              canopy_clipped,
                              community_boundary,
                              nodata_value='',
                              clipping_geometry="ClippingGeometry",
                              maintain_clipping_extent="MAINTAIN_EXTENT")

        pp_c.log_debug(
            'Clipping %s' % (os.path.basename(pp_c.PLANTABLE_REGION_TIF)),
            community)
        arcpy.management.Clip(pp_c.PLANTABLE_REGION_TIF,
                              '#',
                              plantable_region_clipped,
                              community_boundary,
                              clipping_geometry="ClippingGeometry",
                              maintain_clipping_extent="MAINTAIN_EXTENT")

        pp_c.log_debug('Removing trees', community)
        arcpy.gp.RasterCalculator_sa(
            'Con(IsNull("%s"), "%s")' %
            (canopy_clipped, plantable_region_clipped), minus_trees)
        pp_c.delete([canopy_clipped, plantable_region_clipped])

        pp_c.log_debug(
            'Clipping %s' % (os.path.basename(pp_c.BUILDINGS_EXPAND_TIF)),
            community)
        arcpy.management.Clip(pp_c.BUILDINGS_EXPAND_TIF,
                              '#',
                              buildings_clipped,
                              community_boundary,
                              clipping_geometry="ClippingGeometry",
                              maintain_clipping_extent="MAINTAIN_EXTENT")

        pp_c.log_debug('Removing buildings', community)
        arcpy.gp.RasterCalculator_sa(
            'Con(IsNull("%s"), "%s")' % (buildings_clipped, minus_trees),
            minus_trees_buildings)
        pp_c.delete([buildings_clipped, minus_trees, community_boundary])

        pp_c.log_debug('Converting raster to polygon', community)
        arcpy.RasterToPolygon_conversion(minus_trees_buildings, plantable_poly,
                                         "SIMPLIFY", "", "SINGLE_OUTER_PART",
                                         "")
        pp_c.delete([minus_trees_buildings])

        pp_c.log_debug('Repair invalid features', community)
        arcpy.management.RepairGeometry(plantable_poly, "DELETE_NULL", "ESRI")

        pp_c.log_debug('Converting multipart polygons to singlepart',
                       community)
        arcpy.MultipartToSinglepart_management(plantable_poly,
                                               plantable_single_poly)
        pp_c.delete([plantable_poly])

        pp_c.log_debug('Spatial join', community)
        arcpy.SpatialJoin_analysis(plantable_single_poly,
                                   pp_c.MUNI_COMMUNITY_AREA, plantable_muni,
                                   "JOIN_ONE_TO_ONE", "KEEP_ALL", "",
                                   "INTERSECT", "", "")
        pp_c.delete([plantable_single_poly])

        pp_c.log_debug('Add and populate the "CommunityID" field', community)
        arcpy.management.CalculateField(plantable_muni,
                                        pp_c.SPACES_COMMUNITY_COL,
                                        '%i' % (idx), "PYTHON3", "", "SHORT")

        __save_community_spaces(plantable_muni, community_fc)
        pp_c.delete([plantable_muni])

    except Exception as ex:
        pp_c.log_debug('Exception: %s' % (str(ex)))
        raise ex

    return community_fc