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 run(): pp_c.log_info("Logging to %s" % pp.logger.LOG_FILE) arcpy.env.overwriteOutput = True if os.path.isdir(pp_c.TEMP_DIR): shutil.rmtree(pp_c.TEMP_DIR) os.makedirs(pp_c.TEMP_DIR, exist_ok=True) os.makedirs(pp_c.COMMUNITIES_DIR, exist_ok=True) # Prepare the output databases for workspace in [ pp_c.SPACES_GDB, pp_c.TREES_AND_STATS_GDB, pp_c.CANOPIES_GDB ]: if not arcpy.Exists(workspace): raise Exception("%s geodatabase does not exist" % (workspace)) if pp_c.IS_SCRATCH_OUTPUT_DATA: arcpy.env.workspace = workspace pp_c.delete(arcpy.ListFeatureClasses()) pp_c.create_domains(workspace, pp_c.DOMAIN_ASSIGNMENTS[workspace]) # Prepare the output feature classes pp.spaces.prepare_fc() pp.trees.prepare_fc() pp.canopies.prepare_fc() pp.stats.prepare_fc() community_specs = __get_communities(pp_c.SUBSET_START_POINT, pp_c.SUBSET_COUNT, pp_c.SUBSET_LIST) if pp_c.PROCESSORS > 1: p = multiprocessing.Pool(pp_c.PROCESSORS) p.map(create_spaces_and_trees_and_canopies, community_specs, 1) p.close() else: # Process each community past the alphabetical starting point for community_spec in community_specs: create_spaces_and_trees_and_canopies(community_spec) if pp_c.IS_COMBINE_SPACES: pp.spaces.combine_spaces_fcs(community_specs) if pp_c.IS_COMBINE_TREES: pp.trees.combine_trees_fcs(community_specs) if pp_c.IS_COMBINE_CANOPIES: pp.canopies.combine_canopies_fcs(community_specs) pp.stats.combine_stats(community_specs) pp_c.log_info('Complete: %s' % ([c[0] for c in community_specs])) 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 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 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