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