import collections,math,sys # Scan the postmile file to preprocess it to identify N/S pairs # that are so close they will overlap in the display. # Compute the perpendicular vector that will be used to adjust their position. # Input filename is a Output to stdout. # This program adds one extra column to the output, a color, to allow the # results to be easily converted to json and displayed for visual verification. # (See the convert csv to json bash/awk script) # Before being used in the simulation, remove the last column: # cut -f1-6 -d"," output.txt # jdalbey Feb 2019 postmileFile = "d12_vds_uniq_sorted.csv" # Helper function to find the perpendicular unit vector to the line # between two postmiles. def findPerpX(ax,ay,bx,by): dx = float(bx) - float(ax); dy = float(by) - float(ay); dist = math.sqrt(dx * dx + dy * dy); try: normX = dy / dist; # calc a unit vector normY = -dx / dist; return round(normX, 6) except ZeroDivisionError: print "Oops, (",ay,",",ax,") appeared twice," print "causing findPerp() to divide by zero. Probable cause: duplicates in input" print "Please correct the input file." sys.exit(-1) # And same for Y ... I know it's redundant code. def findPerpY(ax,ay,bx,by): dx = float(bx) - float(ax); dy = float(by) - float(ay); dist = math.sqrt(dx * dx + dy * dy); normX = dy / dist; # calc a unit vector normY = -dx / dist; return round(normY, 6) orientationLookup = {'N':0,'S':1,'E':0,'W':1} def loadHighways(): f = open(postmileFile,'r') lines = [line.split(',') for line in f.readlines()] # Create a set containing just the route numbers routeNums = set() for item in lines: routeNums.add(int(item[0])) # put the route numbers in order sortedRoutes = sorted (routeNums) # Create the empty postmile collections for route in sortedRoutes: #print route, highways[str(route)]=[collections.OrderedDict(),collections.OrderedDict()] #print # Process all the data, placing it in proper route and collection for item in lines: route = item[0] orientation = orientationLookup[item[1]] postmileItem = item[2] highways[route][orientation][postmileItem]=item def dumpHighways(): # Dump the highways data we've organized for item in highways: for cnt in [0,1]: list1 = highways[item][cnt] print "highway",item,list1 # show fields for one entry for pm_entry in list1: print pm_entry,list1[pm_entry] def calcPerpendicularVectors(theList,thePerps,dirSign): size = len(theList) idx = 1 while (idx < size-1): # see which is closer, previous or next a = abs(float(theList[idx][2]) - float(theList[idx+1][2])) b = abs(float(theList[idx][2]) - float(theList[idx-1][2])) if ( aclosest to ",theList[idx][2]," is ", theList[idx-1][2], ax=theList[idx][4] ay=theList[idx][3] bx=theList[idx-1][4] by=theList[idx-1][3] px = findPerpX(bx,by,ax,ay) * dirSign # reverse order so normal stays NB py = findPerpY(bx,by,ax,ay) * dirSign # TODO: ADd check to see if this pm already assigned px,py thePerps[theList[idx][2]] = [px,py] thePerps[theList[idx-1][2]] = [px,py] #print px,py idx += 1 # Did first and last spots get filled? if theList[0][2] in thePerps: #print "good, the first item ",theList[0][2]," is present" pass else: #print "oops, first item ",theList[0][2]," missing" # I'm too lazy to calc this value, so providing zero meaning no perp vector # Which means if by chance this spot has a "mate" then it won't get adjusted # as it should. TODO: fix this by using the neighbor as adjacent thePerps[theList[0][2]]=[0,0] if theList[idx][2] in thePerps: #print "good, the last item ",theList[idx][2]," is present" pass else: #print "oops, last item ",theList[idx][2]," is missing" thePerps[theList[idx][2]]=[0,0] # ------------------------------------------------------------------------------------------ highways = collections.OrderedDict() loadHighways() # Iterate over all the highway routes for route in highways: #print "Starting route: ",route # --------------------------------------------------------------------------------- # ## First, compute the perpendicular vectors for each item # Compute nearest adjacent for each item (in SAME direction) # We create separate north/south lists so it's easier to locate an adjacent spot northlist = list (highways[route][0].values()) southlist = list (highways[route][1].values()) northPerps = {} # a dictionary addressed by postmile that yields perp vectors southPerps = {} #print "northsize is ",northSize, "southsize is ",southSize calcPerpendicularVectors(northlist,northPerps, +1) calcPerpendicularVectors(southlist,southPerps, -1) # ------------------------------------------------------------------------------- #print "*****" #print "Perps computed for these up pm's:",sorted(northPerps) #print "Perps computed for these down pm's:",sorted (southPerps) # Try to find matching pairs # Create a match list and add matching postmiles to it. north = highways[route][0] south = highways[route][1] matches = [] for item in north: # if south ALSO has item add i to matches list if item in south: #print "match for: " + item matches.append(item) #print "found ",len(matches)," matches." #outFile = open("pairedDots.json","w") # put results here #[0xFF0000,0xFF4000,0xFF8000,0xFFBF00,0xFFFF00,0x00FF00,0x00FFFF,0x0080FF,0x0000FF,0x8000FF,0xFF00FF] colorcode = ["lime","red","salmon","deeppink","coral","orangered","yellow","khaki","purple", "slateblue","lightgreen","cyan","blue","slategray"] colorindex=0 for match in matches: # We want to output these as json with matching color id = south[match][0] + " " + south[match][1] + " " + south[match][2] print ("%s,%s,%s,%s,%s,%s,%s" % (id,south[match][3],south[match][4],south[match][5].rstrip(),southPerps[south[match][2]][0],southPerps[south[match][2]][1],colorcode[colorindex])) # lookup perpvector for postmile = south[match][2] #perpx = southPerps[south[match][2]][0] #perpy = southPerps[south[match][2]][1] #print perpx, perpy id = north[match][0] + " " + north[match][1] + " " + north[match][2] print ("%s,%s,%s,%s,%s,%s,%s" % (id,north[match][3],north[match][4],north[match][5].rstrip(),northPerps[north[match][2]][0],northPerps[north[match][2]][1],colorcode[colorindex])) # colorindex = (colorindex+1) % 14 # advance to next color # remove them from future consideration south.pop(match) north.pop(match) # ----------------------------------------------------------------------- leftover_count = len(north)+len(south) #print "Leftover count:",leftover_count # After we've handled all the "matching" pairs of N/S dots # There will be "leftover" single dots if (leftover_count > 0): #print "Leftovers ... single dots" #print len(north),"North keys", north.keys() #print len(south),"South keys", south.keys() # Assert: there are no matching keys in the two dictionaries, # they should have been removed by the previous step. # Merge the two sets of keys into one list, # each entry is the postmile and direction mergedKeys = [] upLetter = '' downLetter = '' if len(north) > 0: upLetter = north.items()[0][1][1] for item in north.keys(): mergedKeys.append(item + upLetter) #print "north keys after merging and letter assigned:",mergedKeys if len(south) > 0: downLetter = south.items()[0][1][1] for item in south.keys(): mergedKeys.append(item + downLetter) #Sort the list of keys in ascending order by postmile leftovers = sorted(mergedKeys) # Create a dictionary of postmiles and assigned color, and init to white pm_colors=collections.OrderedDict() for pm in leftovers: pm_colors[pm] = "white" # Look for adjacent items close together in opposite directions # Give them same color dots and assign perpendicular vectors close_count = 0 prev = leftovers.pop(0) prev_dir = prev[-1:] prev_pm = prev[:-1] for curr in leftovers: curr_dir = curr[-1:] curr_pm = curr[:-1] # Only consider adjacent items in OPPOSITE directions if curr_dir != prev_dir: curr_color = "white" # See if they are close enough to be considered a pair if (abs(float(curr_pm) - float(prev_pm)) <= 0.111): #print "FOUND CLOSE: ",prev, curr close_count += 1 # tag the previous item with a colored dot pm_colors[curr_pm+curr_dir]="lime" pm_colors[prev_pm+prev_dir]="lime" prev = curr prev_dir = curr_dir prev_pm = curr_pm #print "Counted ",close_count," close pairs" # print all the tagged items as csv with their tagged color for spot in pm_colors: curr_dir = spot[-1:] curr_pm = spot[:-1] if (curr_dir == upLetter): curr_info = north[curr_pm] curr_perpx = northPerps[north[curr_pm][2]][0] curr_perpy = northPerps[north[curr_pm][2]][1] else: curr_info = south[curr_pm] curr_perpx = southPerps[south[curr_pm][2]][0] curr_perpy = southPerps[south[curr_pm][2]][1] id = curr_info[0] + " " + curr_info[1] + " " + curr_info[2] # white dots have no mate so don't need to be adjusted if (pm_colors[spot]=="white"): px=0 py=0 else: px=curr_perpx py=curr_perpy # output this spot print ("%s,%s,%s,%s,%s,%s,%s" % (id,curr_info[3],curr_info[4],curr_info[5].rstrip(),px,py,pm_colors[curr_pm+curr_info[1]]))