compare algorithms - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: compare algorithms (/thread-8319.html) |
compare algorithms - tygaf - Feb-14-2018 Hello , I want to write a python script that would compare the three algorithms with different parameters. For the script I don't have to modify the existing clustering, make a separate instance call the clustering functions from there. Any help would be appreciated. import arcpy, os, math import datetime, time, json from arcpy import env class Toolbox(object): def __init__(self): """Define the toolbox (the name of the toolbox is the name of the .pyt file).""" self.label = "Clustering" self.alias = "clr" print("clr") # List of tool classes associated with this toolbox self.tools = [CMPM, UpdateCMPM, LocationAllocation] ######################################################################################################################## ######################################################################################################################## # Clustering (Arslan) Cost Matrix Penalty Matrix Threshold (without threshold) ######################################################################################################################## ######################################################################################################################## class CMPM(object): def __init__(self): """Define the tool (tool name is the name of the class).""" self.label = "CMPM" self.description = "Cost Matrix Penalty Matrix (CMPM) Clustering" self.canRunInBackground = False def getParameterInfo(self): """Define parameter definitions""" # Network dataset to work with network = arcpy.Parameter( displayName="Network dataset", name="network", datatype="DENetworkDataset", parameterType="Required", direction="Input") # Nodes to be clustered nodes = arcpy.Parameter( displayName="Nodes", name="nodes", datatype="DEFeatureClass", parameterType="Required", direction="Input") nodes.filter.list = ["Point"] # Facilities facilities_in = arcpy.Parameter( displayName = "Facilities", name="fac", datatype="String", parameterType="Required", direction="Input") facilities_in.filter.type = "ValueList" facilities_in.filter.list = ['Nodes', 'Intersections'] # Splitting ratio sr = arcpy.Parameter( displayName="Splitting ratio", name="SR", datatype="Double", parameterType="Required", direction="Input") # Geography mode geo_mode = arcpy.Parameter( displayName="Geography mode", name="gm", datatype="String", parameterType="Required", direction="Input") geo_mode.filter.type = "ValueList" geo_mode.filter.list = ['OD Cost Matrix', 'Closest Facility'] # Cost metric cost_met = arcpy.Parameter( displayName="Penalty metric", name="cm", datatype="String", parameterType="Required", direction="Input") cost_met.filter.type = "ValueList" cost_met.filter.list = ['Accumulated length', 'Maximum length'] # Cost direction cost_dir = arcpy.Parameter( displayName = "Penalty matrix sorting direction", name="cd", datatype="String", parameterType="Required", direction="Input") cost_dir.filter.type = "ValueList" cost_dir.filter.list = ['Ascending', 'Descending'] # Intersection points int_points = arcpy.Parameter( displayName = "Intersection points", name="int", datatype="DEFeatureClass", parameterType="Required", direction="Input") int_points.filter.list = ["Point"] # Run Identifier rid = arcpy.Parameter( displayName = "Run Identifier", name="rid", datatype="String", parameterType="Optional", direction="Input") # Output penalty matrix, metrics depends on the input: OD - euclidian, Closest facility - network clusters = arcpy.Parameter( displayName = "Output Features", name = "out_features", datatype = "Table", parameterType="Derived", direction="Output") params = [network, nodes, facilities_in, geo_mode, sr, cost_met, cost_dir, int_points, rid, clusters] return params def isLicensed(self): """Set whether tool is licensed to execute.""" return True def updateParameters(self, parameters): """Modify the values and properties of parameters before internal validation is performed. This method is called whenever a parameter has been changed.""" return def updateMessages(self, parameters): """Modify the messages created by internal validation for each tool parameter. This method is called after internal validation.""" return def execute(self, parameters, messages): # Acknowledgements msg = 'This toolbox was developed by the Chair of Communication Networks, Technical University of Munich, Germany. ' \ 'More information under https://www.lkn.ei.tum.de/en/home.html ' \ 'This work has been funded by the German Research Foundation (DFG) ' \ 'under grant numbers MA6529/2-1 and KE1863/4-1.' arcpy.AddMessage(msg) # Check out the Network Analyst extension license arcpy.CheckOutExtension("Network") arcpy.management.Delete('in_memory/') # Set overwriting out the files to TRUE arcpy.overwriteOutput = 1 # Get input network dataset network = parameters[0].value # Network dataset # Set environment # Check the input database descript = arcpy.Describe(network) path_test = descript.catalogPath index = path_test.find('gdb') # gives the index of the first symbol in 'gdb' gdb_path = path_test[:(index+3)] arcpy.env.workspace = os.path.join(gdb_path) # Setting the database, where the output table will be created output_dir = str(gdb_path) # Get all the input parameters nodes_in = parameters[1].value # BS locations nodes = arcpy.CopyFeatures_management(nodes_in, 'in_memory/nodes') arcpy.AddXY_management(nodes) # Add coordinates to the nodes facilities_in = parameters[2].value # Which nodes to use as prospective facility, i.e., PS, location geo_mode = parameters[3].value # Which optimization to use sr = parameters[4].value # Splitting ratio # Penalty matrix sorting parameters cost_met = parameters[5].value # max or accumulated length cost_dir = parameters[6].value # ascending or descending int_points_in = parameters[7].value # Intersection points int_points = arcpy.CopyFeatures_management(int_points_in, 'in_memory/int_points') arcpy.AddXY_management(int_points) # Add coordinates to the intersection points # Optional rid = parameters[8].value # Run Identifier: used only for differentiating the runs, ieĀ“.e., # naming of the files numb = int(arcpy.GetCount_management(nodes).getOutput(0)) # number of RSUs(BSs) arcpy.AddMessage('Your output location is '+ output_dir) arcpy.AddMessage('Calculating cost matrix.') if geo_mode == 'OD Cost Matrix': ########################################################################################################### # Get the cost matrix: OD ########################################################################################################### # Set local variables layer_name = "ODcostMatrix" impedance = "Length" output_layer_file = os.path.join(output_dir, layer_name + ".lyrx") result_object = arcpy.na.MakeODCostMatrixLayer(network, layer_name, impedance) # Get the layer object from the result object. The OD cost matrix layer can # now be referenced using the layer object. layer_object = result_object.getOutput(0) # Get the names of all the sublayers within the OD cost matrix layer. sublayer_names = arcpy.na.GetNAClassNames(layer_object) # Stores the layer names that we will use later origins_layer_name = sublayer_names["Origins"] destinations_layer_name = sublayer_names["Destinations"] lines_layer_name = sublayer_names["ODLines"] # Load the BS locations (nodes) as both origins and destinations. if facilities_in == 'Nodes': arcpy.na.AddLocations(layer_object, origins_layer_name, nodes) else: arcpy.na.AddLocations(layer_object, origins_layer_name, int_points) arcpy.na.AddLocations(layer_object, destinations_layer_name, nodes) # Solve the OD cost matrix layer arcpy.na.Solve(layer_object) # Save the solved OD cost matrix layer as a layer file on disk layer_object.saveACopy(output_layer_file) # Get the Lines Sublayer (all the distances) lines_sublayer = arcpy.mapping.ListLayers(layer_object, lines_layer_name)[0] layer_out_path = os.path.join('in_memory', lines_layer_name) if facilities_in != 'Nodes': # Add xy coordinates to facilities. facilities = arcpy.mapping.ListLayers(layer_object, origins_layer_name)[0] facilities_out_path = os.path.join('in_memory', origins_layer_name) # arcpy.management.CopyFeatures(facilities, facilities_out_path) facilities_point = arcpy.FeatureToPoint_management(facilities, facilities_out_path, "CENTROID") facilities_in = 'in_memory/' + origins_layer_name facilities_xy = arcpy.AddXY_management(facilities_in) arcpy.management.CopyFeatures(lines_sublayer, layer_out_path) lines = 'in_memory/Lines' else: ########################################################################################################### # Get the cost matrix: Closest facility ########################################################################################################### # Set local variables layer_name = "ClosestFacility" impedance = "Length" output_layer_file = os.path.join(output_dir, layer_name + ".lyrx") # MakeClosestFacilityLayer_na (in_network_dataset, out_network_analysis_layer, impedance_attribute, # {travel_from_to}, {default_cutoff}, {default_number_facilities_to_find}, {accumulate_attribute_name}, # {UTurn_policy}, {restriction_attribute_name}, {hierarchy}, {hierarchy_settings}, {output_path_shape}, # {time_of_day}, {time_of_day_usage}) # # http://desktop.arcgis.com/en/arcmap/10.3/tools/network-analyst-toolbox/make-closest-facility-layer.htm result_object = arcpy.na.MakeClosestFacilityLayer(network, layer_name, impedance,'TRAVEL_TO', None, numb, '', '', '', '', '', 'TRUE_LINES_WITH_MEASURES') # Get the layer object from the result object. The Closest facility layer can # now be referenced using the layer object. layer_object = result_object.getOutput(0) # Get the names of all the sublayers within the Closest facility layer. sublayer_names = arcpy.na.GetNAClassNames(layer_object) # Stores the layer names that we will use later origins_layer_name = sublayer_names["Incidents"] # as origins in OD cost matrix destinations_layer_name = sublayer_names["Facilities"] # as destinations in OD cost matrix lines_layer_name = sublayer_names["CFRoutes"] # as lines in OD cost matrix # Load the BS locations (nodes) as both origins and destinations. if facilities_in == 'Nodes': arcpy.na.AddLocations(layer_object, origins_layer_name, nodes) else: arcpy.na.AddLocations(layer_object, origins_layer_name, int_points) arcpy.na.AddLocations(layer_object, destinations_layer_name, nodes) # Solve the Closest facility layer arcpy.na.Solve(layer_object) # Save the solved Closest facility layer as a layer file on disk layer_object.saveACopy(output_layer_file) if facilities_in != 'Nodes': # Add xy coordinates to facilities. facilities = arcpy.mapping.ListLayers(layer_object, destinations_layer_name)[0] facilities_out_path = os.path.join('in_memory', destinations_layer_name) # arcpy.management.CopyFeatures(facilities, facilities_out_path) facilities_point = arcpy.FeatureToPoint_management(facilities, facilities_out_path, "CENTROID") facilities_in = 'in_memory/' + destinations_layer_name facilities_xy = arcpy.AddXY_management(facilities_in) # Get the Lines Sublayer (all the distances) lines_sublayer = arcpy.mapping.ListLayers(layer_object, lines_layer_name)[0] layer_out_path = os.path.join('in_memory', lines_layer_name) lines = 'in_memory/'+ lines_layer_name arcpy.management.CopyFeatures(lines_sublayer, layer_out_path) ################################################################################################################ # Gathering the data for the penalty matrix ################################################################################################################ leng = int(arcpy.GetCount_management(lines_sublayer).getOutput(0)) # Number of paths from every BS to every BS if sr < numb: thr = int(sr) else: thr = int(numb) # Convert an attribute table into python dictionary # http://gis.stackexchange.com/questions/54804/fastest-methods-for-modifying-attribute-tables-with-python def make_attribute_dict(fc, key_field, attr_list=['*']): attdict = {} # define an empty dictionary fc_field_objects = arcpy.ListFields(fc) fc_fields = [field.name for field in fc_field_objects if field.type != 'Geometry'] # checking input parameters if attr_list == ['*']: valid_fields = fc_fields else: valid_fields = [field for field in attr_list if field in fc_fields] # Ensure that key_field is always the first field in the field list cursor_fields = [key_field] + list(set(valid_fields) - set([key_field])) # step through the table with arcpy.da.SearchCursor(fc, cursor_fields) as cursor: for row in cursor: attdict[row[0]] = dict(zip(cursor.fields, row)) return attdict # Convert attribute table of lines from optimization to python nested dict if geo_mode == 'OD Cost Matrix': cost = make_attribute_dict(lines, "ObjectID", ["OriginID", 'DestinationID', 'DestinationRank', 'Total_Length']) cost_keys = ["ObjectID", "OriginID", 'DestinationID', 'DestinationRank', 'Total_Length'] else: cost = make_attribute_dict(lines, "ObjectID", ["IncidentID", 'FacilityID', 'FacilityRank', 'Total_Length']) cost_keys = ["ObjectID", "IncidentID", 'FacilityID', 'FacilityRank', 'Total_Length'] # Convert nodes to python nested dictionary to get the coordinates for the clustering fields = arcpy.ListFields(nodes) for field in fields: if field.name == "ObjectID": nodes_d = make_attribute_dict(nodes, "ObjectID", ['POINT_X', 'POINT_Y']) elif field.name == 'OID': nodes_d = make_attribute_dict(nodes, 'OID', ['POINT_X', 'POINT_Y']) elif field.name == "OBJECTID": nodes_d = make_attribute_dict(nodes, 'OBJECTID', ['POINT_X', 'POINT_Y']) if facilities_in != 'Nodes': fields = arcpy.ListFields(facilities_xy) for field in fields: if field.name == "ObjectID": facilities_d = make_attribute_dict(facilities_xy, "ObjectID", ['POINT_X', 'POINT_Y']) elif field.name == 'OID': facilities_d = make_attribute_dict(facilities_xy, "OID", ['POINT_X', 'POINT_Y']) elif field.name == "OBJECTID": facilities_d = make_attribute_dict(facilities_xy, "OBJECTID", ['POINT_X', 'POINT_Y']) def penalty_update(numb_in, thr_in, cost_matrix, keys, count_in=0): arcpy.AddMessage('Calculating Penalty Matrix.') penalty = {} # empty penalty dictionary length = len(cost_matrix.keys()) # Number of entries in cost matrix if count_in != 0: co = count_in+1 else: co = numb_in+1 # Construct penalty dictionary for h in range(1, co): if count_in == 0: # index to iterate through cost, as we need not all the entries, we make a custom iteration g = numb_in*(h-1) + 1 if g < length: penalty[h] = {} penalty[h]['max_cost'] = 0 penalty[h]['accum_cost'] = 0 # Calculate accumulated cost = sum of indiv. total lengths n = 0 while n < thr_in: if g <= length: penalty[h]['accum_cost'] += cost_matrix[g][keys[4]] if penalty[h]['max_cost'] < cost_matrix[g][keys[4]]: penalty[h]['max_cost'] = cost_matrix[g][keys[4]] n += 1 if g < length: g += 1 if g > numb_in*(h-1)+numb_in: break elif count_in != 0: # index to iterate through cost, as we need not all the entries, we make a custom iteration g = numb_in*(h-1) + 1 if g < length: penalty[h] = {} penalty[h]['max_cost'] = 0 penalty[h]['accum_cost'] = 0 # Calculate accumulated cost = sum of indiv. total lengths n = 0 while n < thr_in: if g <= length: penalty[h]['accum_cost'] += cost_matrix[g][keys[4]] if penalty[h]['max_cost'] < cost_matrix[g][keys[4]]: penalty[h]['max_cost'] = cost_matrix[g][keys[4]] n += 1 if g < length: g += 1 if g > numb_in*(h-1)+numb_in: break else: break # Sort penalty matrix acc_list = [] # List for total (accumulated cost) max_list = [] # Maximum cost (length) # Make a sortable structure out of a penalty dict. for key_i in penalty.keys(): # for all the base stations acc_list.append((key_i, penalty[key_i]['accum_cost'])) max_list.append((key_i, penalty[key_i]['max_cost'])) # Sort the values by length; sorting depends on user input if cost_met == 'Accumulated length': if cost_dir == 'Ascending': sort_in = sorted(acc_list, key=lambda x: x[1]) else: sort_in = sorted(acc_list, key=lambda x: x[1], reverse=True) else: if cost_dir == 'Ascending': sort_in = sorted(max_list, key=lambda x: x[1]) else: sort_in = sorted(max_list, key=lambda x: x[1], reverse=True) return sort_in ################################################################################################################ # Clustering ################################################################################################################ arcpy.AddMessage('Performing clustering.') # Define a list of non-available nodes for clustering flags = set() # Define clustering dictionary clustering = {} if facilities_in == 'Nodes': count = numb index = 0 else: count = len(facilities_d) index = len(facilities_d) j = 1 # iterator through the cost matrix cl = 1 # counter fr the cluster sort = penalty_update(numb, thr, cost, cost_keys, index) # calculate penalty matrix for i in range(0, count): # for all the nodes k = 0 # counter for number of cluster members if j < leng: if len(flags) != numb: node = sort[i][0] #Nodes if facilities_in == 'Nodes': if node not in flags: clustering[cl] = {} clustering[cl]['tot_cost'] = 0 # We need to traverse the cost matrix. However we start not from the first element. As the matrix # structure is regular we can calculate the starting index for our array to assign the (X,Y) # to the seed master. E.g., we have three elements [1, 2, 3] then we have 111222333 structure, where # 3 starts from position 7 number of nodes is 3, thus 3*(3-1)+1 = 7. for 1 it is 3*(1-1)+1 = 1 and # for 2 is 3*(2-1)+1 = 4 j = len(nodes_d)*(node-1) + 1 while k < thr: # now we need to populate the cluster dict with non-clustered yet members if cost[j][cost_keys[2]] not in flags: member_id = cost[j][cost_keys[2]] clustering[cl][member_id] = {} clustering[cl][member_id]['ind'] = j clustering[cl][member_id]['XY'] = (nodes_d[member_id]['POINT_X'], nodes_d[member_id]['POINT_Y']) clustering[cl][member_id]['cost'] = cost[j][cost_keys[4]] clustering[cl]['tot_cost'] += cost[j][cost_keys[4]] flags.add(member_id) k += 1 if j < leng: j += 1 else: break else: if j < leng: j += 1 if len(flags) == numb: break cl += 1 # Intersections else: clustering[cl] = {} clustering[cl]['tot_cost'] = 0 # We need to traverse the cost matrix. However we start not from the first element. As the matrix # structure is regular we can calculate the starting index for our array to assign the (X,Y) # to the seed master. E.g., we have three elements [1, 2, 3] then we have 111222333 structure, where # 3 starts from position 7 number of nodes is 3, thus 3*(3-1)+1 = 7. for 1 it is 3*(1-1)+1 = 1 and # for 2 is 3*(2-1)+1 = 4 clustering[cl]['Cluster_head'] = (facilities_d[sort[0][0]]['POINT_X'], facilities_d[sort[0][0]]['POINT_Y']) j = len(nodes_d)*(node-1) + 1 while k < thr: # now we need to populate the cluster dict with non-clustered yet members if cost[j][cost_keys[2]] not in flags: # Only if the cost threshold was provided by the user member_id = cost[j][cost_keys[2]] clustering[cl][member_id] = {} clustering[cl][member_id]['ind'] = j clustering[cl][member_id]['XY'] = (nodes_d[member_id]['POINT_X'], nodes_d[member_id]['POINT_Y']) clustering[cl][member_id]['cost'] = cost[j][cost_keys[4]] clustering[cl]['tot_cost'] += cost[j][cost_keys[4]] flags.add(member_id) k += 1 if j < leng: j += 1 if k == thr: cl+=1 else: break else: if j < leng: j += 1 if len(flags) == numb: break else: break arcpy.AddMessage('Writing results.') if facilities_in != 'Nodes': point = arcpy.Point() # Get the spatial reference from the input so that the results can be mapped to the original data desc = arcpy.Describe(nodes) spatial_ref = desc.spatialReference # Write clusters to a point file for key in clustering.keys(): # vary cluster name if len(clustering[key]) > 2: if rid and rid != "#": if cost_met == 'Accumulated length': if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + str(int(sr))\ + '_acc_asc_' + str(rid) cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_' + 'sr' \ + str(int(sr))+'_acc_asc_' + str(rid) else: cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + str(int(sr))\ + '_acc_des'+str(rid) cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_acc_des_' + str(rid) else: if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_asc_' + str(rid) cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_asc_' + str(rid) else: cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_des_' + str(rid) cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_des_' + str(rid) else: if cost_met == 'Accumulated length': if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + str(int(sr))\ + '_acc_asc' cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_' + 'sr' \ + str(int(sr))+'_acc_asc' else: cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + str(int(sr))\ + '_acc_des' cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_acc_des' else: if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_asc' cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_asc' else: cluster = 'CMPM_Cluster_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_des' cluster_head = 'CMPM_Cluster_head_' + str(key) + '_Intersections_'+'sr' + \ str(int(sr)) + '_max_des' out_cluster = os.path.join(output_dir, cluster) out_cluster_head = os.path.join(output_dir, cluster_head) point.X = clustering[key]['Cluster_head'][0] point.Y = clustering[key]['Cluster_head'][1] point_geometry = arcpy.PointGeometry(point, spatial_ref) arcpy.CopyFeatures_management(point_geometry, out_cluster_head) # Do it for the cluster members # It has to be greater than two as we always have total cost and coordinates if len(clustering[key].keys()) > 2: # A list to hold the PointGeometry objects point_list = [] for k in clustering[key].keys(): if k != 'tot_cost' and k != 'Cluster_head': point.X = clustering[key][k]['XY'][0] point.Y = clustering[key][k]['XY'][1] point_geometry = arcpy.PointGeometry(point, spatial_ref) # Aggregate cluster members point_list.append(point_geometry) # Write cluster members to a file and define coordinate system arcpy.CopyFeatures_management(point_list, out_cluster) else: # Create an empty Point object point = arcpy.Point() # Get the spatial reference from the input so that the results can be mapped to the original data desc = arcpy.Describe(nodes) spatial_ref = desc.spatialReference # Write clusters to a point file for key in clustering.keys(): # vary cluster name if rid and rid != "#": if cost_met == 'Accumulated length': if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_asc_' + str(rid) else: cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_des_'+str(rid) else: if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_asc_' + str(rid) else: cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_des_' + str(rid) else: if cost_met == 'Accumulated length': if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_asc' else: cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_des' else: if cost_dir == 'Ascending': cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_asc' else: cluster = 'CMPM_Cluster_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_des' out_cluster = os.path.join(output_dir, cluster) # It has to be greater than one as we always have total cost if len(clustering[key].keys()) > 1: # A list to hold the PointGeometry objects point_list = [] for k in clustering[key].keys(): if k != 'tot_cost': point.X = clustering[key][k]['XY'][0] point.Y = clustering[key][k]['XY'][1] point_geometry = arcpy.PointGeometry(point, spatial_ref) # Aggregate cluster members point_list.append(point_geometry) # Write cluster members to a file and define coordinate system arcpy.CopyFeatures_management(point_list, out_cluster) # Find centroid of the cluster # First project the data into meters name_proj = 'Proj_'+cluster output_features_class = os.path.join(output_dir, name_proj) #out_coordinate_system = arcpy.SpatialReference(spatial_ref) arcpy.Project_management(out_cluster, output_features_class, spatial_ref) # Use mean center tool to find the centroid TODO median center cluster_head = 'Proj_Cluster_head_'+ str(key) mean_output = os.path.join(output_dir, cluster_head) arcpy.MeanCenter_stats(output_features_class, mean_output, "#", "#", "#") arcpy.management.Delete(name_proj) # Project cluster head to original projection name_proj = 'Prel_Cluster_head_'+ str(key) output_features_class = os.path.join(output_dir, name_proj) arcpy.Project_management(mean_output, output_features_class, spatial_ref) # Create near table to closest Intersection node in_features = output_features_class near_features = int_points out_table = 'Cluster_head_Near_Table'+str(key) location = 'LOCATION' angle = 'ANGLE' arcpy.GenerateNearTable_analysis(in_features, near_features, out_table, '', location, angle, '', '') # Plot near_X and near_Y out_name = 'Cluster_head' path_output = os.path.join(output_dir, out_name) head = arcpy.MakeXYEventLayer_management(out_table, "NEAR_X", "NEAR_Y", path_output, spatial_ref, '') if rid and rid != "#": if cost_met == 'Accumulated length': if cost_dir == 'Ascending': out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_asc_' + str(rid) else: out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_des_'+str(rid) else: if cost_dir == 'Ascending': out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_asc_' + str(rid) else: out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_des_' + str(rid) else: if cost_met == 'Accumulated length': if cost_dir == 'Ascending': out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_asc' else: out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + str(int(sr)) \ + '_acc_des' else: if cost_dir == 'Ascending': out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_asc' else: out_name = 'CMPM_Cluster_head_' + str(key) + '_Nodes_'+'sr' + \ str(int(sr)) + '_max_des' path_output = os.path.join(output_dir, out_name) arcpy.CopyFeatures_management(head, path_output) arcpy.management.Delete(out_table) arcpy.management.Delete(name_proj) arcpy.management.Delete(cluster_head) return ######################################################################################################################## ######################################################################################################################## # CMPM Clustering with penalty matrix update (Elena) ######################################################################################################################## ######################################################################################################################## class UpdateCMPM(object): def __init__(self): """Define the tool (tool name is the name of the class).""" self.label = "UpdateCMPM" self.description = "CMPM Clustering with penalty matrix update" self.canRunInBackground = False def getParameterInfo(self): """Define parameter definitions""" # Network dataset to work with network = arcpy.Parameter( displayName="Network dataset", name="network", datatype="DENetworkDataset", parameterType="Required", direction="Input") # Nodes to be clustered nodes = arcpy.Parameter( displayName="Nodes", name="nodes", datatype="DEFeatureClass", parameterType="Required", direction="Input") # Facilities facilities_in = arcpy.Parameter( displayName = "Facilities", name="fac", datatype="String", parameterType="Required", direction="Input") facilities_in.filter.type = "ValueList" facilities_in.filter.list = ['Nodes', 'Intersections'] # Splitting ratio sr = arcpy.Parameter( displayName="Splitting ratio", name="SR", datatype="Double", parameterType="Required", direction="Input") # Geography mode geo_mode = arcpy.Parameter( displayName="Geography mode", name="gm", datatype="String", parameterType="Required", direction="Input") geo_mode.filter.type = "ValueList" geo_mode.filter.list = ['OD Cost Matrix', 'Closest Facility'] # Cost metric cost_met = arcpy.Parameter( displayName="Penalty metric", name="cm", datatype="String", parameterType="Required", direction="Input") cost_met.filter.type = "ValueList" cost_met.filter.list = ['Accumulated length', 'Maximum length'] # Cost direction cost_dir = arcpy.Parameter( displayName = "Penalty matrix sorting direction", name="cd", datatype="String", parameterType="Required", direction="Input") cost_dir.filter.type = "ValueList" cost_dir.filter.list = ['Ascending', 'Descending'] # Intersection points int_points = arcpy.Parameter( displayName = "Intersection points", name="int", datatype="DEFeatureClass", parameterType="Required", direction="Input") # Run Identifier rid = arcpy.Parameter( displayName = "Run Identifier", name="rid", datatype="String", parameterType="Optional", direction="Input") # Output penalty matrix, metrics depends on the input: OD - euclidian, Closest facility - network clusters = arcpy.Parameter( displayName = "Output Features", name = "out_features", datatype = "Table", parameterType="Derived", direction="Output") params = [network, nodes, facilities_in, geo_mode, sr, cost_met, cost_dir, int_points, rid, clusters] return params def isLicensed(self): """Set whether tool is licensed to execute.""" return True def updateParameters(self, parameters): """Modify the values and properties of parameters before internal validation is performed. This method is called whenever a parameter has been changed.""" return def updateMessages(self, parameters): """Modify the messages created by internal validation for each tool parameter. This method is called after internal validation.""" return def execute(self, parameters, messages): # Acknowledgements msg = 'This toolbox was developed by the Chair of Communication Networks, Technical University of Munich, Germany. ' \ 'More information under https://www.lkn.ei.tum.de/en/home.html ' \ 'This work has been funded by the German Research Foundation (DFG) ' \ 'under grant numbers MA6529/2-1 and KE1863/4-1.' arcpy.AddMessage(msg) # Check out the Network Analyst extension license arcpy.CheckOutExtension("Network") arcpy.management.Delete('in_memory/') # Set overwriting out the files to TRUE arcpy.overwriteOutput = 1 # Get input network dataset network = parameters[0].value # Network dataset # Set environment # Check the input database descript = arcpy.Describe(network) path_test = descript.catalogPath index = path_test.find('gdb') # gives the index of the first symbol in 'gdb' gdb_path = path_test[:(index+3)] arcpy.env.workspace = os.path.join(gdb_path) # Setting the database, where the output table will be created # out_path = arcpy.env.workspace output_dir = str(gdb_path) # Ge all the input parameters nodes_in = parameters[1].value # BS locations nodes = arcpy.CopyFeatures_management(nodes_in, 'in_memory/nodes') arcpy.AddXY_management(nodes) facilities_in = parameters[2].value # Facility locations choice: nodes or intersections geo_mode = parameters[3].value # Which optimization to use sr = parameters[4].value # Splitting ratio # Penalty matrix sorting parameters cost_met = parameters[5].value # max or accumulated length cost_dir = parameters[6].value # ascending or descending int_points_in = parameters[7].value # Intersection points int_points = arcpy.CopyFeatures_management(int_points_in, 'in_memory/int_points') arcpy.AddXY_management(int_points) # Optional rid = parameters[8].value # Run Identifier arcpy.AddMessage('Your output location is '+ output_dir) arcpy.AddMessage('Calculating cost matrix.') numb = int(arcpy.GetCount_management(nodes).getOutput(0)) # number of RSUs(BSs) if geo_mode == 'OD Cost Matrix': ########################################################################################################### # Get the cost matrix: OD ########################################################################################################### # Set local variables layer_name = "ODcostMatrix" impedance = "Length" output_layer_file = os.path.join(output_dir, layer_name + ".lyrx") result_object = arcpy.na.MakeODCostMatrixLayer(network, layer_name, impedance) # Get the layer object from the result object. The OD cost matrix layer can # now be referenced using the layer object. layer_object = result_object.getOutput(0) # Get the names of all the sublayers within the OD cost matrix layer. sublayer_names = arcpy.na.GetNAClassNames(layer_object) # Stores the layer names that we will use later origins_layer_name = sublayer_names["Origins"] destinations_layer_name = sublayer_names["Destinations"] lines_layer_name = sublayer_names["ODLines"] # Load the BS locations (nodes) as both origins and destinations. if facilities_in == 'Nodes': arcpy.na.AddLocations(layer_object, origins_layer_name, nodes) else: arcpy.na.AddLocations(layer_object, origins_layer_name, int_points) arcpy.na.AddLocations(layer_object, destinations_layer_name, nodes) # Solve the OD cost matrix layer arcpy.na.Solve(layer_object) # Save the solved OD cost matrix layer as a layer file on disk layer_object.saveACopy(output_layer_file) # Get the Lines Sublayer (all the distances) lines_sublayer = arcpy.mapping.ListLayers(layer_object, lines_layer_name)[0] layer_out_path = os.path.join('in_memory',lines_layer_name) if facilities_in != 'Nodes': # Add xy coordinates to facilities. facilities = arcpy.mapping.ListLayers(layer_object, origins_layer_name)[0] facilities_out_path = os.path.join('in_memory', origins_layer_name) # arcpy.management.CopyFeatures(facilities, facilities_out_path) facilities_point = arcpy.FeatureToPoint_management(facilities, facilities_out_path, "CENTROID") facilities_m = 'in_memory/' + origins_layer_name facilities_xy = arcpy.AddXY_management(facilities_m) arcpy.management.CopyFeatures(lines_sublayer, layer_out_path) lines = 'in_memory/' + lines_layer_name else: ########################################################################################################### # Get the cost matrix: Closest facility ########################################################################################################### # Set local variables layer_name = "ClosestFacility" impedance = "Length" output_layer_file = os.path.join(output_dir, layer_name + ".lyrx") # MakeClosestFacilityLayer_na (in_network_dataset, out_network_analysis_layer, impedance_attribute, # {travel_from_to}, {default_cutoff}, {default_number_facilities_to_find}, {accumulate_attribute_name}, # {UTurn_policy}, {restriction_attribute_name}, {hierarchy}, {hierarchy_settings}, {output_path_shape}, # {time_of_day}, {time_of_day_usage}) # # http://desktop.arcgis.com/en/arcmap/10.3/tools/network-analyst-toolbox/make-closest-facility-layer.htm result_object = arcpy.na.MakeClosestFacilityLayer(network, layer_name, impedance,'TRAVEL_TO', None, numb, '', '', '', '', '', 'TRUE_LINES_WITH_MEASURES') # Get the layer object from the result object. The Closest facility layer can # now be referenced using the layer object. layer_object = result_object.getOutput(0) # Get the names of all the sublayers within the Closest facility layer. sublayer_names = arcpy.na.GetNAClassNames(layer_object) # Stores the layer names that we will use later origins_layer_name = sublayer_names["Incidents"] # as origins in OD cost matrix destinations_layer_name = sublayer_names["Facilities"] # as destinations in OD cost matrix lines_layer_name = sublayer_names["CFRoutes"] # as lines in OD cost matrix # Load the BS locations (nodes) as both origins and destinations. if facilities_in == 'Nodes': arcpy.na.AddLocations(layer_object, origins_layer_name, nodes) else: arcpy.na.AddLocations(layer_object, origins_layer_name, int_points) arcpy.na.AddLocations(layer_object, destinations_layer_name, nodes) # Solve the OD cost matrix layer arcpy.na.Solve(layer_object) # Save the solved OD cost matrix layer as a layer file on disk layer_object.saveACopy(output_layer_file) # Get the Lines Sublayer (all the distances) lines_sublayer = arcpy.mapping.ListLayers(layer_object, lines_layer_name)[0] layer_out_path = os.path.join('in_memory', lines_layer_name) if facilities_in != 'Nodes': # Add xy coordinates to facilities. facilities = arcpy.mapping.ListLayers(layer_object, origins_layer_name)[0] facilities_out_path = os.path.join('in_memory', origins_layer_name) # arcpy.management.CopyFeatures(facilities, facilities_out_path) facilities_point = arcpy.FeatureToPoint_management(facilities, facilities_out_path, "CENTROID") facilities_m = 'in_memory/' + origins_layer_name facilities_xy = arcpy.AddXY_management(facilities_m) arcpy.management.CopyFeatures(lines_sublayer, layer_out_path) lines = 'in_memory/' + lines_layer_name ################################################################################################################ # Gathering the data for the penalty matrix ################################################################################################################ leng = int(arcpy.GetCount_management(lines).getOutput(0)) # Number of paths from every BS to every BS if sr < numb: thr = int(sr) else: thr = int(numb) # Convert an attribute table into python dictionary # http://gis.stackexchange.com/questions/54804/fastest-methods-for-modifying-attribute-tables-with-python def make_attribute_dict(fc, key_field, attr_list=['*']): attdict = {} # define an empty dictionary fc_field_objects = arcpy.ListFields(fc) fc_fields = [field.name for field in fc_field_objects if field.type != 'Geometry'] # checking input parameters if attr_list == ['*']: valid_fields = fc_fields else: valid_fields = [field for field in attr_list if field in fc_fields] # Ensure that key_field is always the first field in the field list cursor_fields = [key_field] + list(set(valid_fields) - set([key_field])) # step through the table with arcpy.da.SearchCursor(fc, cursor_fields) as cursor: for row in cursor: attdict[row[0]] = dict(zip(cursor.fields, row)) return attdict # Convert attribute table of lines from optimization to python nested dict if geo_mode == 'OD Cost Matrix': cost = make_attribute_dict(lines, "ObjectID", ["OriginID", 'DestinationID', 'DestinationRank', 'Total_Length']) cost_keys = ["ObjectID", "OriginID", 'DestinationID', 'DestinationRank', 'Total_Length'] else: cost = make_attribute_dict(lines, "ObjectID", ["IncidentID", 'FacilityID', 'FacilityRank', 'Total_Length']) cost_keys = ["ObjectID", "IncidentID", 'FacilityID', 'FacilityRank', 'Total_Length'] # Convert nodes to python nested dictionary to get the coordinates for the clustering fields = arcpy.ListFields(nodes) for field in fields: if field.name == "ObjectID": nodes_d = make_attribute_dict(nodes, "ObjectID", ['POINT_X', 'POINT_Y']) elif field.name == 'OID': nodes_d = make_attribute_dict(nodes, 'OID', ['POINT_X', 'POINT_Y']) elif field.name == "OBJECTID": nodes_d = make_attribute_dict(nodes, 'OBJECTID', ['POINT_X', 'POINT_Y']) if facilities_in != 'Nodes': fields = arcpy.ListFields(facilities_xy) for field in fields: if field.name == "ObjectID": facilities_d = make_attribute_dict(facilities_xy, "ObjectID", ['POINT_X', 'POINT_Y']) elif field.name == 'OID': facilities_d = make_attribute_dict(facilities_xy, "OID", ['POINT_X', 'POINT_Y']) elif field.name == "OBJECTID": facilities_d = make_attribute_dict(facilities_xy, "OBJECTID", ['POINT_X', 'POINT_Y']) ################################################################################################################ # Function for penalty matrix calculation/recalculation ################################################################################################################ def penalty_update(numb_in, thr_in, cost_matrix, keys, count_in=0, clustered=set()): arcpy.AddMessage('Calculating Penalty Matrix.') penalty = {} # empty penalty dictionary length = len(cost_matrix.keys()) # Number of entries in cost matrix if count_in != 0: co = count_in+1 else: co = numb_in+1 # Construct penalty dictionary for h in range(1, co): if count_in == 0 and h not in clustered: # index to iterate through cost, as we need not all the entries, we make a custom iteration g = numb_in*(h-1) + 1 if g < length: penalty[h] = {} penalty[h]['max_cost'] = 0 penalty[h]['accum_cost'] = 0 # Calculate accumulated cost = sum of indiv. total lengths n = 0 while n < thr_in: if g <= length: if cost_matrix[g][keys[2]] not in clustered: penalty[h]['accum_cost'] += cost_matrix[g][keys[4]] if penalty[h]['max_cost'] < cost_matrix[g][keys[4]]: penalty[h]['max_cost'] = cost_matrix[g][keys[4]] n += 1 if g < length: g += 1 if g > numb_in*(h-1)+numb_in: break else: break elif count_in != 0: # index to iterate through cost, as we need not all the entries, we make a custom iteration g = numb_in*(h-1) + 1 if g < length: if cost_matrix[g][keys[2]] not in clustered: penalty[h] = {} penalty[h]['max_cost'] = 0 penalty[h]['accum_cost'] = 0 # Calculate accumulated cost = sum of indiv. total lengths n = 0 while n < thr_in: if g <= length: if cost_matrix[g][keys[2]] not in clustered: penalty[h]['accum_cost'] += cost_matrix[g][keys[4]] if penalty[h]['max_cost'] < cost_matrix[g][keys[4]]: penalty[h]['max_cost'] = cost_matrix[g][keys[4]] n += 1 if g < length: g += 1 if g > numb_in*(h-1)+numb_in: break else: break # Sort penalty matrix acc_list = [] # List for total (accumulated cost) max_list = [] # Maximum cost (length) # Make a sortable structure out of a penalty dict. for key_i in penalty.keys(): # for all the base stations acc_list.append((key_i, penalty[key_i]['accum_cost'])) max_list.append((key_i, penalty[key_i]['max_cost'])) # Sort the values by length; sorting depends on user input if cost_met == 'Accumulated length': if cost_dir == 'Ascending': sort_in = sorted(acc_list, key=lambda x: x[1]) else: sort_in = sorted(acc_list, key=lambda x: x[1], reverse=True) else: if cost_dir == 'Ascending': sort_in = sorted(max_list, key=lambda x: x[1]) else: sort_in = sorted(max_list, key=lambda x: x[1], reverse=True) return sort_in ################################################################################################################ # Clustering ################################################################################################################ # Define a list of non-available nodes for clustering flags = set() # Define clustering dictionary clustering = {} if facilities_in == 'Nodes': count = numb index = 0 else: count = len(facilities_d) index = len(facilities_d) j = 1 # iterator through the cost matrix cl = 1 # counter fr the cluster arcpy.AddMessage('Performing clustering.') for i in range(0, count): # for all the nodes k = 0 # counter for number of cluster members if j < leng: if len(flags) != numb: sort = penalty_update(numb, thr, cost, cost_keys, index,flags) # update penalty matrix node = sort[0][0] #Nodes if facilities_in == 'Nodes': if node not in flags: clustering[cl] = {} clustering[cl]['tot_cost'] = 0 # We need to traverse the cost matrix. However we start not from the first element. As the matrix # structure is regular we can calculate the starting index for our array to assign the (X,Y) # to the seed master. E.g., we have three elements [1, 2, 3] then we have 111222333 structure, where # 3 starts from position 7 number of nodes is 3, thus 3*(3-1)+1 = 7. for 1 it is 3*(1-1)+1 = 1 and # for 2 is 3*(2-1)+1 = 4 j = len(nodes_d)*(node-1) + 1 while k < thr: # now we need to populate the cluster dict with non-clustered yet members if cost[j][cost_keys[2]] not in flags: member_id = cost[j][cost_keys[2]] clustering[cl][member_id] = {} clustering[cl][member_id]['ind'] = j clustering[cl][member_id]['XY'] = (nodes_d[member_id]['POINT_X'], nodes_d[member_id]['POINT_Y']) clustering[cl][member_id]['cost'] = cost[j][cost_keys[4]] clustering[cl]['tot_cost'] += cost[j][cost_keys[4]] flags.add(member_id) k += 1 if j < leng: j += 1 else: break else: if j < leng: j += 1 if len(flags) == numb: break cl += 1 # Intersections else: clustering[cl] = {} clustering[cl]['tot_cost'] = 0 # We need to traverse the cost matrix. However we start not from the first element. As the matrix # structure is regular we can calculate the starting index for our array to assign the (X,Y) # to the seed master. E.g., we have three elements [1, 2, 3] then we have 111222333 structure, where # 3 starts from position 7 number of nodes is 3, thus 3*(3-1)+1 = 7. for 1 it is 3*(1-1)+1 = 1 RE: compare algorithms - buran - Feb-14-2018 Please, use proper tags when post code, traceback, output, etc. See BBcode help for more info. However I doubt someone will bother to look at 1285 lines of code. You were about to exceed maximum number of chars per post. |