def compute_adjacency_list(input_points, input_network, id_attribute, impedance_attribute, accumulator_attributes, search_radius, output_location, adj_dbf_name): """ |input_points|: point shape file marking entity (e.g. building) locations |input_network|: street network in which |input_points| is located |id_attribute|: the name of attribute that distinguishes between input points |impedance_attribute|: distance between neighboring nodes will be based on this attribute |accumulator_attributes|: distance between neighboring nodes will also be recorded for these attributes |search_radius|: the maximum extent for centrality computation |output_location|: adjacency list dbf will be saved here |adj_dbf_name|: the name of the adjacency list dbf """ # Number of points in |input_points| input_point_count = int(GetCount_management(input_points).getOutput(0)) # Make a directory to store all auxiliary files auxiliary_dir = join(output_location, AUXILIARY_DIR_NAME) if not Exists(auxiliary_dir): mkdir(auxiliary_dir) # Record the edge and junction source names of |input_network| junction_feature, edge_feature = network_features(input_network) # Calculate network locations if not already calculated test_input_point = UpdateCursor(input_points).next() locations_calculated = all( row_has_field(test_input_point, field) for field in NETWORK_LOCATION_FIELDS) if not locations_calculated: calculate_network_locations(input_points, input_network) # Calculate barrier cost per input point if not already calculated barrier_costs_calculated = row_has_field(test_input_point, trim(BARRIER_COST_FIELD)) if not barrier_costs_calculated: AddMessage(BARRIER_COST_COMPUTATION_STARTED) # Add |BARRIER_COST_FIELD| column in |input_points| AddField_management(in_table=input_points, field_name=trim(BARRIER_COST_FIELD), field_type="DOUBLE", field_is_nullable="NON_NULLABLE") # Initialize a dictionary to store the frequencies of (SnapX, SnapY) values xy_count = {} # A method to retrieve a (SnapX, SnapY) pair for a row in |input_points| get_xy = lambda row: (row.getValue(trim("SnapX")), row.getValue(trim("SnapY"))) barrier_pre_progress = Progress_Bar(input_point_count, 1, BARRIER_COST_PRE_PROCESSING) rows = UpdateCursor(input_points) for row in rows: snap_xy = get_xy(row) if snap_xy in xy_count: xy_count[snap_xy] += 1 else: xy_count[snap_xy] = 1 barrier_pre_progress.step() # Populate |BARRIER_COST_FIELD|, this will be used in OD matrix computation barrier_progress = Progress_Bar(input_point_count, 1, BARRIER_COST_COMPUTATION) rows = UpdateCursor(input_points) for row in rows: barrier_cost = BARRIER_COST / xy_count[get_xy(row)] row.setValue(trim(BARRIER_COST_FIELD), barrier_cost) rows.updateRow(row) barrier_progress.step() AddMessage(BARRIER_COST_COMPUTATION_FINISHED) # Necessary files od_cost_matrix_layer = join(auxiliary_dir, OD_COST_MATRIX_LAYER_NAME) od_cost_matrix_lines = join(od_cost_matrix_layer, OD_COST_MATRIX_LINES) temp_adj_dbf_name = TEMP_ADJACENCY_DBF_NAME(adj_dbf_name) temp_adj_dbf = join(output_location, temp_adj_dbf_name) adj_dbf = join(output_location, adj_dbf_name) partial_adj_dbf = join(auxiliary_dir, PARTIAL_ADJACENCY_LIST_NAME) polygons = join(auxiliary_dir, POLYGONS_SHAPEFILE_NAME) raster = join(auxiliary_dir, RASTER_NAME) polygons_layer = join(auxiliary_dir, POLYGONS_LAYER_NAME) input_points_layer = join(auxiliary_dir, INPUT_POINTS_LAYER_NAME) # Make sure none of these files already exists for path in [ od_cost_matrix_layer, temp_adj_dbf, adj_dbf, partial_adj_dbf, polygons, raster, polygons_layer, input_points_layer, od_cost_matrix_lines ]: delete(path) # Cutoff radius for OD matrix computation cutoff_radius = 2 * BARRIER_COST + min(search_radius, BARRIER_COST / 2) # Compute OD matrix MakeODCostMatrixLayer_na(in_network_dataset=input_network, out_network_analysis_layer=od_cost_matrix_layer, impedance_attribute=impedance_attribute, default_cutoff=str(cutoff_radius), accumulate_attribute_name=accumulator_attributes, UTurn_policy="ALLOW_UTURNS", hierarchy="NO_HIERARCHY", output_path_shape="NO_LINES") # Determine raster cell size points_per_raster_cell = OD_MATRIX_ENTRIES / input_point_count raster_cell_count = max(1, input_point_count / points_per_raster_cell) input_points_extent = Describe(input_points).Extent raster_cell_area = (input_points_extent.width * input_points_extent.height / raster_cell_count) raster_cell_size = int(sqrt(raster_cell_area)) # Construct |raster| from |input_points| PointToRaster_conversion(in_features=input_points, value_field=id_attribute, out_rasterdataset=raster, cell_assignment="MOST_FREQUENT", priority_field="NONE", cellsize=str(raster_cell_size)) # Construct |polygons| from |raster| RasterToPolygon_conversion(in_raster=raster, out_polygon_features=polygons, simplify="NO_SIMPLIFY", raster_field="VALUE") # Export empty |od_cost_matrix_lines| to |temp_dbf| to start adjacency list TableToTable_conversion(in_rows=od_cost_matrix_lines, out_path=output_location, out_name=temp_adj_dbf_name) # Construct |polygons_layer| and |input_points_layer| for (feature, layer) in [(polygons, polygons_layer), (input_points, input_points_layer)]: MakeFeatureLayer_management(in_features=feature, out_layer=layer) def add_locations(sub_layer, field_mappings=""): """ |sub_layer|: one of "Origins", "Destinations", "Barrier Points" |field_mappings|: field mappings in addition to those for "Name" and "CurbApproach" """ AddLocations_na(in_network_analysis_layer=od_cost_matrix_layer, sub_layer=sub_layer, in_table=input_points_layer, field_mappings=("Name %s #; CurbApproach # 0; %s" % (id_attribute, field_mappings)), search_tolerance=SEARCH_TOLERANCE, search_criteria=("%s SHAPE; %s SHAPE;" % (junction_feature, edge_feature)), append="CLEAR", snap_to_position_along_network="SNAP", snap_offset=SNAP_OFFSET) # OD cost matrix destinations AddMessage(ADDING_DESTINATIONS_STARTED) SelectLayerByLocation_management(in_layer=input_points_layer) add_locations("Destinations") AddMessage(ADDING_DESTINATIONS_FINISHED) # OD cost matrix point barriers AddMessage(ADDING_BARRIERS_STARTED) add_locations("Point Barriers", ("FullEdge # 0; BarrierType # 2;" "Attr_%s %s #;" % (impedance_attribute, trim(BARRIER_COST_FIELD)))) AddMessage(ADDING_BARRIERS_FINISHED) # Compute adjacency list, one raster cell at a time progress = Progress_Bar(raster_cell_count, 1, STEP_1) rows = UpdateCursor(polygons) for row in rows: # Select the current polygon SelectLayerByAttribute_management(in_layer_or_view=polygons_layer, selection_type="NEW_SELECTION", where_clause="FID = %s" % str(row.FID)) # Origins SelectLayerByLocation_management(in_layer=input_points_layer, select_features=polygons_layer) add_locations("Origins") # Solve OD Cost matrix Solve_na(in_network_analysis_layer=od_cost_matrix_layer, ignore_invalids="SKIP") # Add origin and destination fields to the adjacency list dbf for (index, field) in [(0, ORIGIN_ID_FIELD_NAME), (1, DESTINATION_ID_FIELD_NAME)]: CalculateField_management(in_table=od_cost_matrix_lines, field=field, expression="!Name!.split(' - ')[%d]" % index, expression_type="PYTHON") # Record actual distance between neighboring nodes distance_field = "Total_%s" % impedance_attribute CalculateField_management(in_table=od_cost_matrix_lines, field=distance_field, expression="!%s! - 2 * %d" % (distance_field, BARRIER_COST), expression_type="PYTHON") # Append result to |temp_adj_dbf| TableToTable_conversion(in_rows=od_cost_matrix_lines, out_path=auxiliary_dir, out_name=PARTIAL_ADJACENCY_LIST_NAME) Append_management(inputs=partial_adj_dbf, target=temp_adj_dbf, schema_type="TEST") progress.step() # Copy data from |temp_adj_dbf| to |adj_dbf| Rename_management(in_data=temp_adj_dbf, out_data=adj_dbf) # Clean up for path in [ od_cost_matrix_layer, partial_adj_dbf, polygons, raster, polygons_layer, input_points_layer, auxiliary_dir ]: delete(path)
def compute_centrality(nodes, origins, compute_r, compute_g, compute_b, compute_c, compute_s, radius, network_radius, beta, measures_to_normalize, accumulator_fields): """ Computes reach, gravity, betweenness, closeness, and straightness on a graph. |nodes|: graph representation; dictionary mapping node id's to |Node| objects |origins|: subset of nodes that will be used as sources of shortest path trees |compute_r|: compute reach? |compute_g|: compute gravity type index? |compute_b|: compute betweenness? |compute_c|: compute closeness? |compute_s|: compute straightness? |radius|: for each node, only consider other nodes that can be reached within this distance |network_radius|: use network radius or birds-eye radius? |beta|: parameter for gravity type index |measures_to_normalize|: a list of measures to normalize |accumulator_fields|: a list of cost attributes to accumulate """ # Number of nodes in the graph N = len(nodes) O = len(origins) if O > N: raise Invalid_Parameters_Exception("size of origins exceeds size of nodes") elif O == 0: return # Preprocessing have_accumulations = len(accumulator_fields) > 0 if have_accumulations: empty_accumulations = lambda: dict((field, 0.0) for field in accumulator_fields) have_locations = hasattr(nodes.values()[0], LOCATION) if compute_s and not have_locations: # We cannot compute straightness without node locations compute_s = False if compute_b: # Initialize betweenness values for id in nodes: setattr(nodes[id], BETWEENNESS, 0.0) # Initialize the sum of all node weights (normalization) sum_weights = 0.0 # Computation progress = Progress_Bar(O, 1, STEP_4) for s in origins: if s not in nodes: continue weight_s = getattr(nodes[s], WEIGHT) if have_locations: location_s = getattr(nodes[s], LOCATION) sum_weights += weight_s # Initialize reach (weighted and unweighted) computation for |s| # (normalization) reach_s = -1 weighted_reach_s = -weight_s # Initialize measures if compute_g: gravity_s = 0.0 if compute_b: P = {s: []} # Predecessors S = [] # Stack containing nodes in the order they are extended sigma = {s: 1.0} # Number of shortest paths from |s| to other nodes delta = {} # Dependency of |s| on other nodes if compute_c: d_sum_s = 0.0 if compute_s: straightness_s = 0.0 if have_accumulations: accumulations_s = {s: empty_accumulations()} d = {s: 0.0} # Shortest distance from |s| to other nodes # Queue for Dijkstra Q = [(0.0, s)] if network_radius else [(0.0, s, 0.0)] # If we use euclidean radius, make a list of all reachable nodes if not network_radius: reachable_s = set() for t in nodes: location_t = getattr(nodes[t], LOCATION) if dist(location_s, location_t) <= radius: reachable_s.add(t) # Dijkstra while Q and (True if network_radius else reachable_s): # Pop the closest node to |s| from |Q| if network_radius: d_sv, v = heappop(Q) else: d_sv, v, dist_sv = heappop(Q) if v in reachable_s: reachable_s.remove(v) weight_v = getattr(nodes[v], WEIGHT) if have_locations: location_v = getattr(nodes[v], LOCATION) compute = network_radius or dist_sv <= radius if compute: reach_s += 1 weighted_reach_s += weight_v if d_sv > 0: if compute_g: gravity_s += weight_v * exp(-d_sv * beta) if compute_c: d_sum_s += weight_v * d_sv if compute_s: straightness_s += (weight_v * dist(location_s, location_v) / d_sv) if compute_b: S.append(v) for w, d_vw, accumulations_vw in getattr(nodes[v], NEIGHBORS): # s ~ ... ~ v ~ w d_sw = d_sv + d_vw if not network_radius: # Use Euclidean distance location_w = getattr(nodes[w], LOCATION) dist_sw = dist(location_s, location_w) if compute_b: b_refresh = False add_w_to_Q = False if not w in d: # Found a path from |s| to |w| for the first time if d_sw <= radius or not network_radius: add_w_to_Q = True d[w] = d_sw if compute_b: b_refresh = True elif lt_tol(d_sw, d[w]): # Found a better path from |s| to |w| if d_sw <= radius or not network_radius: if d[w] <= radius or not network_radius: longer_path_node = (d[w], w) if network_radius else (d[w], w, dist_sw) Q.remove(longer_path_node) heapify(Q) add_w_to_Q = True d[w] = d_sw if compute_b: b_refresh = True if add_w_to_Q: new_node = (d_sw, w) if network_radius else (d_sw, w, dist_sw) heappush(Q, new_node) if have_accumulations: accumulations_s[w] = merge_maps(accumulations_s[v], dict(accumulations_vw), add) if compute_b: if b_refresh: sigma[w] = 0.0 P[w] = [] if eq_tol(d_sw, d[w]): # Count all shortest paths from |s| to |w| sigma[w] += sigma[v] # Update the number of shortest paths P[w].append(v) # |v| is a predecessor of |w| delta[v] = 0.0 # Recognize |v| as a predecessor if compute_r: setattr(nodes[s], REACH, weighted_reach_s) if compute_g: setattr(nodes[s], GRAVITY, gravity_s) if compute_b: while S: # Revisit nodes in reverse order of distance from |s| w = S.pop() delta_w = delta[w] if w in delta else 0.0 # Dependency of |s| on |w| for v in P[w]: weight_w = getattr(nodes[w], WEIGHT) delta[v] += sigma[v] / sigma[w] * (weight_w + delta_w) if w != s: between_w = getattr(nodes[w], BETWEENNESS) setattr(nodes[w], BETWEENNESS, between_w + delta_w) if compute_c: setattr(nodes[s], CLOSENESS, (1.0 / d_sum_s if d_sum_s > 0 else 0.0)) if compute_s: setattr(nodes[s], STRAIGHTNESS, straightness_s) nodes[s].reach = reach_s nodes[s].weighted_reach = weighted_reach_s if have_accumulations: total_accumulations_s = empty_accumulations() for v in accumulations_s: total_accumulations_s = merge_maps(total_accumulations_s, accumulations_s[v], add) for field in accumulator_fields: setattr(nodes[s], field, total_accumulations_s[field]) progress.step() # Normalization if BETWEENNESS in measures_to_normalize and O < N: measures_to_normalize.remove(BETWEENNESS) AddWarning(WARNING_NO_BETWEENNESS_NORMALIZATION) if measures_to_normalize: norm_progress = Progress_Bar(O, 1, PROGRESS_NORMALIZATION) for s in origins: if s not in nodes: continue reach_s = nodes[s].reach weighted_reach_s = nodes[s].weighted_reach # Normalize reach if compute_r and REACH in measures_to_normalize: weight_s = getattr(nodes[s], WEIGHT) try: setattr(nodes[s], NORM_REACH, reach_s / (sum_weights - weight_s)) except: setattr(nodes[s], NORM_REACH, 0.0) # Normalize gravity if compute_g and GRAVITY in measures_to_normalize: gravity_s = getattr(nodes[s], GRAVITY) try: setattr(nodes[s], NORM_GRAVITY, (exp(beta) * gravity_s / weighted_reach_s)) except: setattr(nodes[s], NORM_GRAVITY, 0.0) # Normalize betweenness if compute_b and BETWEENNESS in measures_to_normalize: betweenness_s = getattr(nodes[s], BETWEENNESS) try: setattr(nodes[s], NORM_BETWEENNESS, (betweenness_s / (weighted_reach_s * (reach_s - 1)))) except: setattr(nodes[s], NORM_BETWEENNESS, 0.0) # Normalize closeness if compute_c and CLOSENESS in measures_to_normalize: closeness_s = getattr(nodes[s], CLOSENESS) try: setattr(nodes[s], NORM_CLOSENESS, closeness_s * weighted_reach_s) except: setattr(nodes[s], NORM_CLOSENESS, 0.0) # Normalize straightness if compute_s and STRAIGHTNESS in measures_to_normalize: straightness_s = getattr(nodes[s], STRAIGHTNESS) try: setattr(nodes[s], NORM_STRAIGHTNESS, (straightness_s / weighted_reach_s)) except: setattr(nodes[s], NORM_STRAIGHTNESS, 0.0) norm_progress.step()
def compute_centrality(nodes, origins, compute_r, compute_g, compute_b, compute_c, compute_s, radius, beta, measures_to_normalize, accumulator_fields): """ Computes reach, gravity, betweenness, closeness, and straightness on a graph. |nodes|: graph representation; dictionary mapping node id's to |Node| objects |origins|: subset of nodes that will be used as sources of shortest path trees |compute_r|: compute reach? |compute_g|: compute gravity type index? |compute_b|: compute betweenness? |compute_c|: compute closeness? |compute_s|: compute straightness? |radius|: for each node, only consider other nodes that can be reached within this distance |beta|: parameter for gravity type index |measures_to_normalize|: a list of measures to normalize |accumulator_fields|: a list of cost attributes to accumulate """ # Number of nodes in the graph N = len(nodes) O = len(origins) if N == 0 or O == 0: return # Preprocessing have_accumulations = len(accumulator_fields) > 0 have_locations = hasattr(nodes.values()[0], LOCATION) if compute_s and not have_locations: # We cannot compute straightness without node locations compute_s = False if compute_b: # Initialize betweenness values for id in nodes: setattr(nodes[id], BETWEENNESS, 0.0) # Initialize the sum of all node weights (normalization) sum_weights = 0.0 # Computation progress = Progress_Bar(O, 1, STEP_4) for s in origins: weight_s = getattr(nodes[s], WEIGHT) if have_locations: location_s = getattr(nodes[s], LOCATION) sum_weights += weight_s # Initialize reach (weighted and unweighted) computation for |s| # (normalization) reach_s = -1 weighted_reach_s = -weight_s # Initialize measures if compute_g: gravity_s = 0.0 if compute_b: P = {s: []} # Predecessors S = [] # Stack containing nodes in the order they are extended sigma = {s: 1.0} # Number of shortest paths from |s| to other nodes delta = {} # Dependency of |s| on other nodes if compute_c: d_sum_s = 0.0 if compute_s: straightness_s = 0.0 if have_accumulations: empty_accumulations = lambda: dict((field, 0.0) for field in accumulator_fields) accumulations_s = {s: empty_accumulations()} d = {s: 0.0} # Shortest distance from |s| to other nodes Q = [(0.0, s)] # Queue for Dijkstra # Dijkstra while Q: # Pop the closest node to |s| from |Q| d_sv, v = heappop(Q) weight_v = getattr(nodes[v], WEIGHT) if have_locations: location_v = getattr(nodes[v], LOCATION) reach_s += 1 weighted_reach_s += weight_v if d_sv > 0: if compute_g: gravity_s += weight_v * exp(-d_sv * beta) if compute_c: d_sum_s += weight_v * d_sv if compute_s: straightness_s += (weight_v * dist(location_s, location_v) / d_sv) if compute_b: S.append(v) for w, d_vw, accumulations_vw in getattr(nodes[v], NEIGHBORS): # s ~ ... ~ v ~ w d_sw = d_sv + d_vw if compute_b: b_refresh = False if not w in d: # Found a path from |s| to |w| for the first time if d_sw <= radius: heappush(Q, (d_sw, w)) # Add |w| to |Q| if have_accumulations: accumulations_s[w] = merge_maps(accumulations_s[v], dict(accumulations_vw), add) d[w] = d_sw if compute_b: b_refresh = True elif lt_tol(d_sw, d[w]): # Found a better path from |s| to |w| if d_sw <= radius: if d[w] <= radius: Q.remove((d[w], w)) heapify(Q) heappush(Q, (d_sw, w)) # Add |w| to |Q| if have_accumulations: accumulations_s[w] = merge_maps(accumulations_s[v], dict(accumulations_vw), add) d[w] = d_sw if compute_b: b_refresh = True if compute_b: if b_refresh: sigma[w] = 0.0 P[w] = [] if eq_tol(d_sw, d[w]): # Count all shortest paths from |s| to |w| sigma[w] += sigma[v] # Update the number of shortest paths P[w].append(v) # |v| is a predecessor of |w| delta[v] = 0.0 # Recognize |v| as a predecessor if compute_r: setattr(nodes[s], REACH, weighted_reach_s) if compute_g: setattr(nodes[s], GRAVITY, gravity_s) if compute_b: while S: # Revisit nodes in reverse order of distance from |s| w = S.pop() delta_w = delta[w] if w in delta else 0.0 # Dependency of |s| on |w| for v in P[w]: weight_w = getattr(nodes[w], WEIGHT) delta[v] += sigma[v] / sigma[w] * (weight_w + delta_w) if w != s: between_w = getattr(nodes[w], BETWEENNESS) setattr(nodes[w], BETWEENNESS, between_w + delta_w) if compute_c: setattr(nodes[s], CLOSENESS, (1.0 / d_sum_s if d_sum_s > 0 else 0.0)) if compute_s: setattr(nodes[s], STRAIGHTNESS, straightness_s) nodes[s].reach = reach_s nodes[s].weighted_reach = weighted_reach_s if have_accumulations: total_accumulations_s = empty_accumulations() for v in accumulations_s: total_accumulations_s = merge_maps(total_accumulations_s, accumulations_s[v], add) for field in accumulator_fields: setattr(nodes[s], field, total_accumulations_s[field]) progress.step() # Normalization if BETWEENNESS in measures_to_normalize and O < N: measures_to_normalize.remove(BETWEENNESS) AddWarning(WARNING_NO_BETWEENNESS_NORMALIZATION) if measures_to_normalize: norm_progress = Progress_Bar(O, 1, PROGRESS_NORMALIZATION) for s in origins: reach_s = nodes[s].reach weighted_reach_s = nodes[s].weighted_reach # Normalize reach if compute_r and REACH in measures_to_normalize: weight_s = getattr(nodes[s], WEIGHT) try: setattr(nodes[s], NORM_REACH, reach_s / (sum_weights - weight_s)) except: setattr(nodes[s], NORM_REACH, 0.0) # Normalize gravity if compute_g and GRAVITY in measures_to_normalize: gravity_s = getattr(nodes[s], GRAVITY) try: setattr(nodes[s], NORM_GRAVITY, (exp(beta) * gravity_s / weighted_reach_s)) except: setattr(nodes[s], NORM_GRAVITY, 0.0) # Normalize betweenness if compute_b and BETWEENNESS in measures_to_normalize: betweenness_s = getattr(nodes[s], BETWEENNESS) try: setattr(nodes[s], NORM_BETWEENNESS, (betweenness_s / (weighted_reach_s * (reach_s - 1)))) except: setattr(nodes[s], NORM_BETWEENNESS, 0.0) # Normalize closeness if compute_c and CLOSENESS in measures_to_normalize: closeness_s = getattr(nodes[s], CLOSENESS) try: setattr(nodes[s], NORM_CLOSENESS, closeness_s * weighted_reach_s) except: setattr(nodes[s], NORM_CLOSENESS, 0.0) # Normalize straightness if compute_s and STRAIGHTNESS in measures_to_normalize: straightness_s = getattr(nodes[s], STRAIGHTNESS) try: setattr(nodes[s], NORM_STRAIGHTNESS, (straightness_s / weighted_reach_s)) except: setattr(nodes[s], NORM_STRAIGHTNESS, 0.0) norm_progress.step()
AddMessage(STEP_1_FAILED) success = False # Step 2 if success: AddMessage(STEP_2_STARTED) try: distance_field = trim("Total_%s" % inputs[IMPEDANCE_ATTRIBUTE]) accumulator_fields = set([trim("Total_%s" % accumulator_attribute) for accumulator_attribute in inputs[ACCUMULATOR_ATTRIBUTES].split(";") if accumulator_attribute != "#"]) # Graph representation: dictionary mapping node id's to Node objects nodes = {} # The number of rows in |adj_dbf| directed_edge_count = int(GetCount_management(adj_dbf).getOutput(0)) graph_progress = Progress_Bar(directed_edge_count, 1, STEP_2) rows = UpdateCursor(adj_dbf) for row in rows: # Get neighboring nodes, and the distance between them origin_id = row.getValue(trim(ORIGIN_ID_FIELD_NAME)) destination_id = row.getValue(trim(DESTINATION_ID_FIELD_NAME)) distance = float(row.getValue(distance_field)) # Make sure the nodes are recorded in the graph for id in [origin_id, destination_id]: if not id in nodes: nodes[id] = Node() # Make sure that the nodes are neighbors in the graph if origin_id != destination_id and distance >= 0: accumulations = {} for field in accumulator_fields: accumulations[field] = float(row.getValue(field))
def compute_adjacency_list(input_points, input_network, id_attribute, impedance_attribute, accumulator_attributes, search_radius, output_location, adj_dbf_name): """ |input_points|: point shape file marking entity (e.g. building) locations |input_network|: street network in which |input_points| is located |id_attribute|: the name of attribute that distinguishes between input points |impedance_attribute|: distance between neighboring nodes will be based on this attribute |accumulator_attributes|: distance between neighboring nodes will also be recorded for these attributes |search_radius|: the maximum extent for centrality computation |output_location|: adjacency list dbf will be saved here |adj_dbf_name|: the name of the adjacency list dbf """ # Number of points in |input_points| input_point_count = int(GetCount_management(input_points).getOutput(0)) # Make a directory to store all auxiliary files auxiliary_dir = join(output_location, AUXILIARY_DIR_NAME) if not Exists(auxiliary_dir): mkdir(auxiliary_dir) # Record the edge and junction source names of |input_network| edge_feature = None junction_feature = None for source in Describe(input_network).sources: if source.sourceType == EDGE_FEATURE: edge_feature = source.name elif source.sourceType in JUNCTION_FEATURE: junction_feature = source.name if edge_feature == None: AddWarning(WARNING_NO_EDGE_FEATURE(input_network)) raise Invalid_Input_Exception("Input Network") if junction_feature == None: AddWarning(WARNING_NO_JUNCTION_FEATURE(input_network)) raise Invalid_Input_Exception("Input Network") # Calculate network locations if not already calculated test_input_point = UpdateCursor(input_points).next() locations_calculated = all(row_has_field(test_input_point, field) for field in NETWORK_LOCATION_FIELDS) if not locations_calculated: AddMessage(CALCULATE_LOCATIONS_STARTED) CalculateLocations_na(in_point_features=input_points, in_network_dataset=input_network, search_tolerance=SEARCH_TOLERANCE, search_criteria=("%s SHAPE; %s SHAPE;" % (junction_feature, edge_feature)), exclude_restricted_elements="INCLUDE") AddMessage(CALCULATE_LOCATIONS_FINISHED) # Calculate barrier cost per input point if not already calculated barrier_costs_calculated = row_has_field(test_input_point, trim(BARRIER_COST_FIELD)) if not barrier_costs_calculated: AddMessage(BARRIER_COST_COMPUTATION_STARTED) # Add |BARRIER_COST_FIELD| column in |input_points| AddField_management(in_table=input_points, field_name=trim(BARRIER_COST_FIELD), field_type="DOUBLE", field_is_nullable="NON_NULLABLE") # Initialize a dictionary to store the frequencies of (SnapX, SnapY) values xy_count = {} # A method to retrieve a (SnapX, SnapY) pair for a row in |input_points| get_xy = lambda row: (row.getValue(trim("SnapX")), row.getValue(trim("SnapY"))) barrier_pre_progress = Progress_Bar(input_point_count, 1, BARRIER_COST_PRE_PROCESSING) rows = UpdateCursor(input_points) for row in rows: snap_xy = get_xy(row) if snap_xy in xy_count: xy_count[snap_xy] += 1 else: xy_count[snap_xy] = 1 barrier_pre_progress.step() # Populate |BARRIER_COST_FIELD|, this will be used in OD matrix computation barrier_progress = Progress_Bar(input_point_count, 1, BARRIER_COST_COMPUTATION) rows = UpdateCursor(input_points) for row in rows: barrier_cost = BARRIER_COST / xy_count[get_xy(row)] row.setValue(trim(BARRIER_COST_FIELD), barrier_cost) rows.updateRow(row) barrier_progress.step() AddMessage(BARRIER_COST_COMPUTATION_FINISHED) # Necessary files od_cost_matrix_layer = join(auxiliary_dir, OD_COST_MATRIX_LAYER_NAME) od_cost_matrix_lines = join(od_cost_matrix_layer, OD_COST_MATRIX_LINES) temp_adj_dbf_name = "%s~.dbf" % adj_dbf_name[:-4] temp_adj_dbf = join(output_location, temp_adj_dbf_name) adj_dbf = join(output_location, adj_dbf_name) partial_adj_dbf = join(auxiliary_dir, PARTIAL_ADJACENCY_LIST_NAME) polygons = join(auxiliary_dir, POLYGONS_SHAPEFILE_NAME) raster = join(auxiliary_dir, RASTER_NAME) polygons_layer = join(auxiliary_dir, POLYGONS_LAYER_NAME) input_points_layer = join(auxiliary_dir, INPUT_POINTS_LAYER_NAME) # Make sure none of these files already exists for path in [od_cost_matrix_layer, temp_adj_dbf, adj_dbf, partial_adj_dbf, polygons, raster, polygons_layer, input_points_layer, od_cost_matrix_lines]: delete(path) # Cutoff radius for OD matrix computation cutoff_radius = 2 * BARRIER_COST + min(search_radius, BARRIER_COST / 2) # Compute OD matrix MakeODCostMatrixLayer_na(in_network_dataset=input_network, out_network_analysis_layer=od_cost_matrix_layer, impedance_attribute=impedance_attribute, default_cutoff=str(cutoff_radius), accumulate_attribute_name=accumulator_attributes, UTurn_policy="ALLOW_UTURNS", hierarchy="NO_HIERARCHY", output_path_shape="NO_LINES") # Determine raster cell size points_per_raster_cell = OD_MATRIX_ENTRIES / input_point_count raster_cell_count = max(1, input_point_count / points_per_raster_cell) input_points_extent = Describe(input_points).Extent raster_cell_area = (input_points_extent.width * input_points_extent.height / raster_cell_count) raster_cell_size = int(sqrt(raster_cell_area)) # Construct |raster| from |input_points| PointToRaster_conversion(in_features=input_points, value_field=id_attribute, out_rasterdataset=raster, cell_assignment="MOST_FREQUENT", priority_field="NONE", cellsize=str(raster_cell_size)) # Construct |polygons| from |raster| RasterToPolygon_conversion(in_raster=raster, out_polygon_features=polygons, simplify="NO_SIMPLIFY", raster_field="VALUE") # Export empty |od_cost_matrix_lines| to |temp_dbf| to start adjacency list TableToTable_conversion(in_rows=od_cost_matrix_lines, out_path=output_location, out_name=temp_adj_dbf_name) # Construct |polygons_layer| and |input_points_layer| for (feature, layer) in [(polygons, polygons_layer), (input_points, input_points_layer)]: MakeFeatureLayer_management(in_features=feature, out_layer=layer) def add_locations(sub_layer, field_mappings=""): """ |sub_layer|: one of "Origins", "Destinations", "Barrier Points" |field_mappings|: field mappings in addition to those for "Name" and "CurbApproach" """ AddLocations_na(in_network_analysis_layer=od_cost_matrix_layer, sub_layer=sub_layer, in_table=input_points_layer, field_mappings=("Name %s #; CurbApproach # 0; %s" % (id_attribute, field_mappings)), search_tolerance=SEARCH_TOLERANCE, search_criteria=("%s SHAPE; %s SHAPE;" % (junction_feature, edge_feature)), append="CLEAR", snap_to_position_along_network="SNAP", snap_offset=SNAP_OFFSET) # OD cost matrix destinations AddMessage(ADDING_DESTINATIONS_STARTED) SelectLayerByLocation_management(in_layer=input_points_layer) add_locations("Destinations") AddMessage(ADDING_DESTINATIONS_FINISHED) # OD cost matrix point barriers AddMessage(ADDING_BARRIERS_STARTED) add_locations("Point Barriers", ("FullEdge # 0; BarrierType # 2;" "Attr_%s %s #;" % (impedance_attribute, trim(BARRIER_COST_FIELD)))) AddMessage(ADDING_BARRIERS_FINISHED) # Compute adjacency list, one raster cell at a time progress = Progress_Bar(raster_cell_count, 1, STEP_1) rows = UpdateCursor(polygons) for row in rows: # Select the current polygon SelectLayerByAttribute_management(in_layer_or_view=polygons_layer, selection_type="NEW_SELECTION", where_clause="FID = %s" % str(row.FID)) # Origins SelectLayerByLocation_management(in_layer=input_points_layer, select_features=polygons_layer) add_locations("Origins") # Solve OD Cost matrix Solve_na(in_network_analysis_layer=od_cost_matrix_layer, ignore_invalids="SKIP") # Add origin and destination fields to the adjacency list dbf for (index, field) in [(0, ORIGIN_ID_FIELD_NAME), (1, DESTINATION_ID_FIELD_NAME)]: CalculateField_management(in_table=od_cost_matrix_lines, field=field, expression="!Name!.split(' - ')[%d]" % index, expression_type="PYTHON") # Record actual distance between neighboring nodes distance_field = "Total_%s" % impedance_attribute CalculateField_management(in_table=od_cost_matrix_lines, field=distance_field, expression="!%s! - 2 * %d" % (distance_field, BARRIER_COST), expression_type="PYTHON") # Append result to |temp_adj_dbf| TableToTable_conversion(in_rows=od_cost_matrix_lines, out_path=auxiliary_dir, out_name=PARTIAL_ADJACENCY_LIST_NAME) Append_management(inputs=partial_adj_dbf, target=temp_adj_dbf, schema_type="TEST") progress.step() # Copy data from |temp_adj_dbf| to |adj_dbf| Rename_management(in_data=temp_adj_dbf, out_data=adj_dbf) # Clean up for path in [od_cost_matrix_layer, partial_adj_dbf, polygons, raster, polygons_layer, input_points_layer, auxiliary_dir]: delete(path)