Add the RouteFinding Part 0 assignment files.

This commit is contained in:
Charles Descamps 2025-02-14 16:21:01 -07:00
parent d6d0b03547
commit f23ca49f82
6 changed files with 797 additions and 0 deletions

34
RouteFinding/05_tests.py Normal file
View file

@ -0,0 +1,34 @@
# Define Searcher, hSLD, SearchNode.
# (b) Show your program loading in the 30-node sample file.
s=Searcher("30node.txt")
# (c) Show you program setting start node=U and end node=T.
s.setStartGoal('U','T')
# myViz should be a DRDViz instance -> save map to file on disk.
s.myViz.save("30node.png")
# (d) Show the one open node.
[n.showBasic() for n in s.open]
# (e) Show successors of only open node.
initial_children = s.successors(s.open.pop(0))
[n.showBasic() for n in initial_children]
# (f) Show three inserts: at the front, and the end, and "in order"
def reset_insert(where):
s.reset()
initial_children = s.successors(s.open.pop(0))
insert_method = getattr(s, "insert_"+where)
insert_method(initial_children)
return [n.showBasic() for n in s.open]
reset_insert("front")
reset_insert("end")
reset_insert("ordered")
# (g) INSERT (K,500), (C,91) and (J,10) and show no duplicates.
newdata = (("K",500), ("C",91), ("J",10))
newlist = [SearchNode(label=label, pathcost=pathcost) for label, pathcost in newdata]
ignored = s.insert_end(newlist)
[n.showBasic() for n in s.open]
# 3. hSLD heuritic function being called on three nodes.
[hSLD(x, s) for x in ("V", "AC", "J")]

245
RouteFinding/DRDViz.py Normal file
View file

@ -0,0 +1,245 @@
# DRDViz is a powerful graph visualizer graph specifically for
# visualizing searches in a given search space. It it designed to be
# used an an "attached helper" to some sort of graph searching class.
# The overall idea is that you you create an instance of your graph
# searcher object, loading some map file. You then create yourself an
# instance of the DRDViz class, and load it with the same map file as
# your search class; the new DRDViz is stored in an instance variable
# of your graph searcher object. Each time your graph searcher does
# something significant e.g. setting start/edge node, exploring a new
# node in the search, or adding new siblings to the OPEN list of the
# search, it simply calls a method on the attached DRDViz to visually
# show the evolving search on the graphical map.
## Usage examples:
# x=DRDViz() # make yourself a new visualizer
# x.loadGraphFromFile('testfile.txt') # load it up from a map file
# x.plot() # make the plot of the road map appear
# x.markStart('A') # mark starting node A
# x.markGoal('D') # mark 'D' as a goal node
# x.exploreNode('J',['A','J']) # mark node 'J' as being currently explored, along with the path to it from A
# x.exploreEdges('G',['C','B']) # mark the edges coming from parent 'G' and going to children [C, B].
#
# x.reset() # reset the visualizer, e.g., in preparation for a new search.
#
# ##
__author__ = "Eck Doerry"
__copyright__ = "Copyright 2018, Northern Arizona University, Flagstaff AZ"
import numpy as np
import scipy.spatial as graph
import matplotlib.pyplot as plt
from node import Node
from edge import Edge
### GRAPHVIZ
# This class implements a graph visualizer. It can plot a graph, then provides methods to mark start/end nodes, and to
# color/re-color nodes and edges in various ways to visualize exploration of the space defined by the graph.
class DRDViz:
# to create a GraphVisualizer, you first create the blank visualizer (no args taken by INIT).
# You could then use either loadGraphFromFile() or loadGraphFromObjects() to suck in a map to visualize
# and create internal data structures for use in all other operations.
def __init__(self):
self.nodes=[]
self.edges=[]
self.nodeIndex={}
self.markedNodes=[]
self.markedEdges=[]
###### EXTERNAL INTERFACE METHODS ############
# The primary methods you call to use this visualizer
# Just resets the visualizer. Easiest to just close the graph and redraw it
def reset(self):
self.markedEdges = []
self.markedNodes = []
plt.close()
self.plot()
def save(self, out):
plt.savefig(out)
# A function to load up a graph to visualize. The filename specified by "infile" should contain a well-formed
# input file describing the graph. The format is a set of edge descriptions, one line per edge, of following format:
# (nodeLabe1, nodeLabel2, edgeLabel, [x1,y1],[x2,y2],[midx,midy])
# Reads each line and creates the appropriate node/edge objects.
def loadGraphFromFile(self, infile):
with open(infile) as f:
lines = f.readlines()
cleanlines = [x.strip() for x in lines] #strip off any weird leading or trailing spaces.
for line in cleanlines:
line=line.replace('\'','').replace('[','').replace(']','').replace(' ','').strip('()')
rawEdge=line.split(',') # now just have nice clean comma-separated values. Make it a list
[l1,l2,label,x1,y1,x2,y2]=rawEdge # grab all the components of the edge
if l1 not in self.nodeIndex:
self.nodeIndex[l1]=(int(x1),int(y1))
self.nodes.append(Node(int(x1),int(y1),l1))
if l2 not in self.nodeIndex:
self.nodeIndex[l2] = (int(x2), int(y2))
self.nodes.append(Node(int(x2), int(y2), l2))
# Now build and Edge object for it
newEdge=Edge((int(x1),int(y1)), (int(x2),int(y2)), int(label))
newEdge.setLabels(l1,l2)
self.edges.append(newEdge)
f.close()
# Function to ask a loaded graph to plot itself. If you're not in interactive Python mode, may need to call
# plot.show() to see it. Assumes that a graph has been loaded first!
def plot(self):
plt.close() # just in case previous graphs were open!
plt.ion # turn on interactive mode. Not sure if it's critical
# first plot the nodes
plt.plot([x.x for x in self.nodes], [x.y for x in self.nodes], 'ko',
color="#BBBCBD") # plot vertices circles first
for node in self.nodes: # now plot the labels
plt.text(node.x, node.y, node.label, color='b', weight='normal')
# Now plot in the edges
for edge in self.edges:
plt.plot([edge.x1, edge.x2], [edge.y1, edge.y2], 'b-', color='k', linewidth=0.5) # make the lines
plt.text(edge.midx, edge.midy, edge.label, size='x-small') # label the edge at its midpoint
self.paintGraph()
# Takes in start node label, and repaints that node as start (ie, different node shape and green)
start_color = 'c' #cyan https://matplotlib.org/stable/tutorials/colors/colors.html
def markStart(self,startLabel):
#self.paintNode(startLabel, self.start_color, weight='bold') # paint label of start node
self.plotVertex(startLabel, 'D', self.start_color) # give start node a vertex
self.paintGraph()
# Takes in end node label, and repaints that node as end (ie, different node shape and red)
def markGoal(self,goalLabel):
#self.paintNode(goalLabel, 'r', weight='bold') # paint label of endnode red
self.plotVertex(goalLabel, 'D', 'r') # give end node a different label shape/color
self.paintGraph() # force a repaint
# ExploreNode is a major method, called each time a new node is opened for exploration.
# takes in: the label of the next node to explore, and the path (list of nodelabels) to that node.
# it uses the latter to highlight the path leading to that node on the graph...could pass it an empty list
# if you don't want that function.
# First it "cleans up" the graph from the last round of exploration: it unmarks the parent node,
# and also unmarked all edges painted in the previous round by exploreEdges() and paintPath()
# It then resets self.paintedEdges and is ready to start the new cycle by painting the node being
# explored.
def exploreNode(self,nodeLabel,nodePath):
# First clean up. Unmark highlights nodes/edges from last step
for edge in self.markedEdges: # first unmark any edges painted in last steps
self.paintEdge(edge[0],edge[1],color='k')
if self.markedNodes: # if there was a previous node explored, unmark it
self.paintNode(self.markedNodes[0], color='k')
self.markedEdges = []
# Ok, now highlight the newly explored node and its path
self.paintNode(nodeLabel, color='r')
self.paintPath(nodePath, color='r') # mark path to current node in red
self.markedNodes= [nodeLabel] # add node to explored nodes list
self.paintGraph() # force a repaint
# ExploreEdges extends the explored frontier out from some parent node to its children.
# take in a parent node label, and a set of children node labels.
# it first paints all previously explored edges a neutral color, then paints the new
# edges to highlight them.
def exploreEdges(self,parentLabel, childLabelList):
for newDest in childLabelList:
#print("Painting: "+parentLabel+" to "+newDest)
self.markedEdges += [(parentLabel,newDest)] # add the new edge to explored edges
self.paintEdge(parentLabel,newDest,color='m') # paint edge to highlight
self.paintNode(newDest,color='g') # color new dest node as part of frontier
self.paintGraph() # to redraw the graph!
# paintPath takes in a contiguous path of node labels and paints it (default=red).
def paintPath(self, nodePath, color='r'):
last = ""
for node in nodePath:
if last:
self.paintEdge(last, node, color=color)
self.markedEdges += [(last, node)]
last = node
else:
last = node
############ INTERNAL UTILITY METHODS ######################
## Don't really need to call these to use DRDViz. These methods are called by the External Interface
## methods listed above to get the job done.
## Also include various little methods useful for debugging or other purposes.
# Takes in a node label and color. If node exists, paint its label that color
def paintNode(self,nodeLabel,color='r',weight='normal', size='medium'):
p1=self.nodeLoc(nodeLabel)
if p1:
plt.text(p1[0], p1[1], nodeLabel, color=color, size=size, weight=weight) # repaint the label!
return 1
else:
print("PaintNode: No node labeled "+nodeLabel+" exists.")
return 0
# Load a graph given a list of nodes object and a list of edge objects. Useful convenience when used in
# conjunction with a GraphMaker, allow you to just grab the Edge objects directly from the GraphMaker, rather
# than exporting them to a file first and then reloading them with loadGraphFromFile()
def loadGraphFromObjects(self, nodes, edges):
self.nodes = nodes
self.edges = edges
# Useful if some outside object wants to get a list of edges in the visualization.
# Just returns a big list of edges in form (endlabel,endlabel, edgelabel,[x1,y1],[x2,y2])
def getSearchSpace(self):
result = []
for edge in self.edges:
result.append((edge.endlabel1, edge.endlabel2, edge.label, edge.p1, edge.p2))
return result
# Just returns a graphed node object based on its label
def getNodeByLabel(self, nodeLabel):
for node in self.nodes:
if node.label == nodeLabel: return node
print("getNodebyLabel: No node labeled " + nodeLabel + " exists")
return 0
# a function that repaints (replots) an edge on the graphical plot
# take in to node labels and optionally, a color
def paintEdge(self, startLabel, endLabel, color='r'):
p1 = self.nodeLoc(startLabel)
p2 = self.nodeLoc(endLabel)
plt.plot([p1[0], p2[0]], [p1[1], p2[1]], color=color, linewidth=1) # repaint the edge
## Main External interface functions for using a RoadGraph
def paintGraph(self):
plt.pause(0.05)
# Job is to (re)plot a node vertex in different shape/color. Takes in node label, a new vertex shape code,
# and a new color, and redraws the vertex associated with that node accordingly.
def plotVertex(self,nodeLabel,mtype,color):
loc= self.nodeLoc(nodeLabel)
plt.plot(loc[0],loc[1],color+mtype)
def nodeLoc(self,label): # finds and returns the location of node labeled 'label'
for node in self.nodes:
if node.label==label: return [node.x,node.y]
print("nodeLoc: No node with label "+label+" exists")
#mainly for debugging: Functions to have the graph display its edges and nodes
def show(self):
print("Here is the graph:")
print("Nodes: ")
print([x.get() for x in self.nodes])
print("Edges: ")
print([x.get() for x in self.edges])
def die(self): # just kills any open graph
plt.close()
if __name__ == "__main__":
x=DRDViz()
x.loadGraphFromFile("test1.txt")
DRDViz_class = DRDViz

59
RouteFinding/edge.py Normal file
View file

@ -0,0 +1,59 @@
## The EDGE class is just a utility class for the GraphViz and GraphMaker classes. It simply represents an edge in
# a graph being constructed, capturing the endpoints of the edge as well as its label. Its biggest active
# contribution is that it is able to compute the edge label based on some distortion percentage (between 0-1) that is
# passed into the constructor as the "label".
__author__ = "Eck Doerry"
__copyright__ = "Copyright 2018, Northern Arizona University, Flagstaff AZ"
import numpy as np
import scipy.spatial as graph
import matplotlib.pyplot as plt
class Edge:
# Makes a new edge. You pass in two points (x,y tuples) plus a label. Label is used flexibly to determine
# how the edge is labeled. If label=0, then edge is just labeled with exact straightline distance between
# the points. If label is a value between 0-1, this is viewed as a percentage to distort the distance by.
# If label > 1, then is taken as an actual value that you want placed on that edge.
def __init__(self, p1,p2, label=0):
self.p1=p1
self.p2=p2
self.x1=p1[0]
self.y1=p1[1]
self.x2=p2[0]
self.y2=p2[1]
self.dist= int(graph.distance.euclidean(p1,p2)) # holds straight cartesian dist between points!
self.midx = abs(p1[0] - p2[0]) / 2 + min(p1[0], p2[0])
self.midy = abs(p1[1] - p2[1]) / 2 + min(p1[1], p2[1])
self.endlabel1=""
self.endlabel2=""
self.label=0 # holds the actual edge value of the edge. Calc'd by distorting the distance
# if a distortion was passed in, randomly distort edge length appropriately.
if label==0: self.label=self.dist # default. Edge label is its length
elif label<1: # label was a distortion percentage. randomly calc length based on it.
adjust=self.dist*label
distortion= np.random.random()*adjust # randomly take some percentage of the adjustment range
self.label = int(self.dist + distortion) # adds on distortion. Roads are ALWAYS longer than SLD
else: self.label=label # label was an actual number you want on the edge
def get(self):
return ([self.x1,self.y1],[self.x2,self.y2],self.label,[self.midx,self.midy])
def midpt(self):
return([self.midx,self.midy])
# Returns true if the edge connects the two labels. Have to check both directions!
def connects(self,label1,label2):
if (label1 == self.endlabel1 and label2 == self.endlabel2) or (label1 == self.endlabel2 and label2 == self.endlabel1):
return True
return False
def setLabels(self,l1,l2): # just stored the node labels at the ends of the edge. For convenience mostly.
self.endlabel1=l1
self.endlabel2=l2
# Unit test main fn
# x= Edge((23,56),(34,45))
# print("created an edge: "+ str(x.get()))

265
RouteFinding/graphmaker.py Normal file
View file

@ -0,0 +1,265 @@
## INTRO. Purpose of the GraphMaker class is to generate random graphs; these graphs can then also be edited, adding
# or deleting edges while visually inspecting the evolving map. This makes it quick and easy to create custom "road
# networks" to use as search spaces for developing and visualizing the behavior of searching algorithms.
#
# You ask it to create a graph, you can optionally ask it to
# plot that graph for you for visual verification purposes. You can then edit the graph as desired, quickly adding
# or deleting edge. Finall, you can export that graph as a simple text file of Edges
# for use in other programs.
#
# The class has multiple parameters that you can set to fine tune the nature of the graphs produced. For now, these are
# just set by editing the values in the __init__ function below. For instance,
# you can specify edge distance distortion, percentage of edges to drop (to make sparse graphs), min. distance between
# nodes generated, etc. See comments in the init function below
#
## Usage examples:
## x=GraphMaker(20) # create a new random 20 node map.
#
# x.plot() # now graphically show it
# x.edit() # enter edit mode. It repeatedly asks you to add/delete edges.
# x.export('outfile') # exports the current map to the file "outfile.txt" in the working dir
# TO-DO: implement a load-graph-from-file to *reload* a graph after saving...in case you want to edit and re-save
__author__ = "Eck Doerry"
__copyright__ = "Copyright 2018, Northern Arizona University, Flagstaff AZ"
import numpy as np
import scipy.spatial as graph
import matplotlib.pyplot as plt
from node import Node
from edge import Edge
class GraphMaker:
def __init__(self,nodeCount=10):
self.numNodes=nodeCount # number of nodes to create in graph.
self.expansion=2 # how wide the graph will be. Basically a factor that increases the x-y extents of plane
self.mindist = 0.3 * self.expansion # the minimum separation distance between nodes. Prevents cluttered graphs
# distDistortion. Often you might want to have a graph where the distances are *based on* the actual
# distances, but not exactly that. distDistortion is a percentage within which the cartesian distances
# between points will be randomly distorted while labeling the new edges in the graph. So at 0.3, 30% of the
# distance will be randomly distorted between 0 and 100%. So you could lose/gain 30% distance, randomly.
self.distDistortion = 0.5 # usually 0.3 is nice
# edgeLoss: in a roadmap, we don't want edges between EVERY point to EVERY other point. Hence we randomly drop some edges
# the edgeLoss specifies the percentage of edges we should randomly drop
self.edgeLoss= 0.1
self.nodes=[] # array of node objects in this graph
self.edges=[] # array of edges in this graph
self.buildGraph() # Build the new graph! Commented out. Call it separately after creation!
# if you don't like the graph generated, just try another one!
def redo(self,numNodes=-1):
self.edges=[]
self.nodes=[]
if numNodes>0: self.numNodes=numNodes
plt.close()
self.buildGraph()
self.plot()
# Lets you interactively edit a graph to whack out edges
def edit(self):
entry=""
print("Edit Mode: {k/b}a,b to kill/build an edge between a,b ; r to replot, q to quit")
while True:
plt.pause(0.05)
entry=input("What now?")
entry = entry.strip()
if entry=='r':
self.replot()
elif entry=='q': break
else:
command=entry[0]
[start,end]=entry[1:].split(',')
if command=='b': self.buildEdge(start,end)
else: self.whackEdge(start,end)
print("End Edit mode")
# This method lets one whack out edges. So you can edit away edges after you generate the graph!
def whackEdge(self,node1,node2):
node1=node1.upper()
node2=node2.upper()
newEdges=[]
for edge in self.edges:
if edge.connects(node1,node2): # whack the edge
plt.plot([edge.x1, edge.x2], [edge.y1, edge.y2], 'b-', color='w', linewidth=0.5) # make the lines
plt.text(edge.midx, edge.midy, edge.label, size='x-small', color='w') # label the edge at its midpoint
plt.pause(0.05)
else: newEdges.append(edge)
self.edges=newEdges
def buildEdge(self,node1,node2):
node1 = node1.upper()
node2 = node2.upper()
for edge in self.edges: # avoid building redundant new edges!
if edge.connects(node1,node2):
print("already an edge from "+node1+" to "+node2)
return
p1=self.nodeLocation(node1)
p2=self.nodeLocation(node2)
if p1!=0 and p2!=0: # both nodes exist
print("building edge!")
edge=Edge(p1,p2, self.distDistortion) # make a new edge
edge.setLabels(node1,node2)
self.edges.append(edge)
plt.plot([edge.x1, edge.x2], [edge.y1, edge.y2], 'b-', color='k', linewidth=0.5) # make the lines
plt.text(edge.midx, edge.midy, edge.label, size='x-small',color='k') # label the edge at its midpoint
else: print("start or end node doesn't exist")
# Just kill and redraw the plot. Useful after you've edited the graph
def replot(self):
plt.close()
self.plot()
plt.show()
# MAIN WORKER: Does the actual work of building a Roadmap.
# The biggest challenge here is linking the given points with a *non-intersecting* set of edges. In other words,
# we would like a "planar graph" imposed on these points. This is what Delauney function gives us: it basically
# finds non-overlapping triangles that cover all points; any one point is often a vertex in multiple triagles of
# the "puzzle" of triangles produced. We then need to extract unique edges and the nodes from this.
def buildGraph(self):
pointsArray = self.genPoints() # makes the array of (x,y) points to connect
tri=graph.Delaunay(pointsArray) # creates a planar graph on the given points. Creates a tri data structure.
triangles=pointsArray[tri.simplices] # list of triplets of three points. Each set of three points marks a
# triangle laid out by Delauney. Next need to extract the edges (point-to-point connections) from this.
# Now extract the unique edges from all the triangles to generate all edges in the graph
self.makeEdges(triangles) # populates self.edges with Edge objects
# Finally, turn the points array into actual Node objects.
Node.reset(Node) # Starts Node labeling at 'A'
self.nodes=list(map(Node,pointsArray[:,0], pointsArray[:,1])) #populates self.nodes with Node objects
self.labelEdges() # Just to make edge objects complete, go back and add endlabel (nodelabels) to those objects
# Makes all the edges in the new graph, based on the array of tris (basically triagles) produced by Delauney and
# pass in. Appends the new edges to the "edges" array.
def makeEdges(self,triArray):
edges = [] # temp array holding unique ((x,y),(x1,y1)) edge segments found so far
# an important helper. When we extract a new edges from a triagle, it might well be the duplicate
# of an edge from an adjacent triangle created by Delauney.
def checkAddAdj(adjs):
for adj in adjs:
if (adj in edges) or ((adj[1], adj[0]) in edges): # if edges already in there, skip it
continue
else:
edges.append((adj[0], adj[1]))
# go through the triangle array, extract each edge, check that we don't have it already, and add to edges.
for triplet in triArray:
s = list(map(list,triplet))
adjs = [(s[0], s[1]), (s[1], s[2]), (s[2], s[0])] # extracting the arcs between the points
checkAddAdj(adjs)
# okay, now edges array contains all unique edge segment. Now make edge objects out of them!
# use edgeLoss value to randomly drop some pct of Edges
for edge in edges:
if (np.random.rand() > self.edgeLoss):
self.edges.append(Edge(edge[0],edge[1],self.distDistortion)) #make Edge object
else: print("blip")
# For each edge, finds the label for its endpoints in the nodes, and adds to edge object.
def labelEdges(self):
for edge in self.edges:
edge.endlabel1=self.getNodeAtXY(edge.p1).label
edge.endlabel2=self.getNodeAtXY(edge.p2).label
# Function to ask the roadgraph to plot itself. If not interactive, need to call plot.show() to see it.
def plot(self):
plt.ion # turn on interactive mode. Not sure if it's critical
# first plot the nodes
plt.plot([x.x for x in self.nodes], [x.y for x in self.nodes], 'ko', color="#BBBCBD") # plot vertices circles first
for node in self.nodes: # now plot the labels
plt.text(node.x, node.y, node.label, color='b', size='large', weight='normal')
# Now plot in the edges
for edge in self.edges:
plt.plot([edge.x1,edge.x2],[edge.y1,edge.y2], 'b-', color='k', linewidth=0.5) # make the lines
plt.text(edge.midx,edge.midy,edge.label, size='x-small') # label the edge at its midpoint
# Function to return the roadgraph as simple list that can be fed to search program.
# prints it out, one line per edge. Format (nodeLabel1, nodeLabel2, edgeLabel, ((x1,y1),(x2,y2),(midx,midy)) )
# if you pass it a text string, it will print to that, else prints to console
def export(self,outfile=0):
if outfile != 0:
outfilename=outfile+".txt"
out=open(outfilename,'w')
for edge in self.edges:
theEdge=((self.getNodeAtXY(edge.p1).label,self.getNodeAtXY(edge.p2).label,edge.label))
locInfo=((edge.p1,edge.p2))
if outfile:
out.write(str(theEdge+locInfo)+"\n")
else:
print(theEdge+locInfo)
if outfile: out.close()
print("tada!")
# Returns a node that is located at a given point
def getNodeAtXY(self,aPoint): # gets the Node at location x-y of point
for node in self.nodes:
if (node.x==aPoint[0]) and (node.y==aPoint[1]): return node
return 0
# Just returns x-y location of a node, given its label
def nodeLocation(self,label): # finds and returns the location of node labeled 'label'
for node in self.nodes:
if node.label==label: return [node.x,node.y]
print("nodeLoc: No node with label "+label+" exists")
# a key function. Generates a random set of points that are some minimum distance apart.
def genPoints(self):
# While you still need points, generate random points, add to set if min dist from all currently found points.
points = [] # An array to put your points in as you find them.
found = 0 # Tracks points found so far
# Simple function that checks that candidate points are some min dist from all other points.
# keeps it from making cluttered graphs!
def checkpt(apoint, allpoints):
for point in allpoints:
dst = graph.distance.euclidean(point, apoint)
if (dst < self.mindist): return False
return True
while found < self.numNodes:
p1 = self.expansion * np.random.randn(2)
if checkpt(p1, points):
points.append(p1)
found = found + 1
# print("found one:"+str(found))
if len(points) == 0: points.append(p1)
# Now just turn the points into a numpy array
pArray = np.array(points)
# print(points)
# AWESOME. Now shift all the points into the positive x-y quadrant
xmin = min(pArray[:, [0]])[0]
ymin = min(pArray[:, [1]])[0]
xshift = abs(xmin) + 1
yshift = abs(ymin) + 1
posArray = pArray + [xshift, yshift]
posArray = posArray.astype(int) # truncate the entire array of points into integer values. Easier!
return posArray
#mainly for debugging: Functions to have the graph display its edges and nodes
def show(self):
print("Here is the graph:")
print("Nodes: ")
print([x.get() for x in self.nodes])
print("Edges: ")
print([x.get() for x in self.edges])
def die(self): # just kills any open graph
plt.close()
### UNIT TESTING
if __name__ == "__main__":
import sys
x=GraphMaker(sys.argv[1])
x.export(sys.argv[2])

66
RouteFinding/node.py Normal file
View file

@ -0,0 +1,66 @@
'''Node.py: This class implements simple node object and associated methods.
Nodes are created by passing in an x,y cartesian location for the node. Node labels are created
by just assigning letters to nodes in ascending ASCII order; current index is kept as class variable
to ensure that each new node has a unique label.
The most interesting internal method here is makeLabel, which generates unique labels. We want easy to read
alpha labels...so it basically counts up in Base26, generating the corresponding ASCII labels. Tricky!
'''
__author__ = "Eck Doerry"
__copyright__ = "Copyright 2018, Northern Arizona University, Flagstaff AZ"
import scipy.spatial as graph
import numpy as np
class Node:
# CLASS VAR: curIndex is used to make sure that all nodes have a unique label. Incremented every time a new
# node is generated.
curIndex=1
# CLASS METHOD. Allows for reseting the index counter for generating new nodes. Used only for debugging!
def reset(cls):
cls.curIndex=1
# Create a new node at point x-y. Optionally pass in a label for the node. If no explicit label is passed
# in (which is usually the case), it auto-generates the next label in a sequences starting with A-Z, then
# going to AA, AB, AC, etc.
def __init__(self,newX,newY, newLabel= '*'):
self.x=newX
self.y=newY
if newLabel=='*':
self.label=self.makeLabel()
else:
self.label=newLabel
# Makes an upper case label starting at 'A'. If >26 nodes, goes to 'AB', 'AC', etc.
# Works by basically counting in Base26: converts the index into strings in Base26!
def makeLabel(self):
index=self.curIndex
out = []
while index>25:
out.append(index % 26)
index = int(index / 26)
out.append(index-1)
out.reverse()
Node.curIndex +=1
return "".join([chr(65+x) for x in out])
# Distance: returns the cartesian distance between this node some other node that is passed in.
def distance(self,aNode):
return graph.distance.euclidean([self.x,self.y],[aNode.x,aNode.y])
def get(self):
return ([self.x,self.y],self.label)
# Unit tests
# x=Node(4,5)
# y=Node(1,2)
# print(x.get())
# print(y.get())

View file

@ -0,0 +1,128 @@
('AS', 'J', 99, [208, 215], [204, 116])
('J', 'T', 25, [204, 116], [227, 105])
('T', 'AS', 111, [227, 105], [208, 215])
('M', 'AS', 49, [159, 212], [208, 215])
('AS', 'AD', 79, [208, 215], [138, 252])
('AD', 'M', 45, [138, 252], [159, 212])
('AY', 'AI', 24, [33, 202], [56, 195])
('AI', 'AD', 99, [56, 195], [138, 252])
('AD', 'AY', 116, [138, 252], [33, 202])
('O', 'J', 34, [181, 142], [204, 116])
('AS', 'O', 77, [208, 215], [181, 142])
('AJ', 'AV', 18, [172, 57], [176, 75])
('AV', 'K', 32, [176, 75], [145, 84])
('K', 'AJ', 38, [145, 84], [172, 57])
('AA', 'AJ', 27, [145, 54], [172, 57])
('K', 'AA', 30, [145, 84], [145, 54])
('AK', 'M', 60, [110, 176], [159, 212])
('AI', 'AK', 57, [56, 195], [110, 176])
('M', 'AN', 47, [159, 212], [163, 165])
('AN', 'AS', 67, [163, 165], [208, 215])
('AN', 'O', 29, [163, 165], [181, 142])
('AL', 'N', 53, [77, 80], [67, 27])
('N', 'S', 17, [67, 27], [84, 31])
('S', 'AL', 49, [84, 31], [77, 80])
('AW', 'N', 47, [107, 1], [67, 27])
('N', 'AX', 66, [67, 27], [1, 19])
('AX', 'AW', 107, [1, 19], [107, 1])
('AW', 'S', 37, [107, 1], [84, 31])
('G', 'X', 28, [199, 81], [226, 72])
('AW', 'D', 26, [107, 1], [132, 9])
('D', 'S', 52, [132, 9], [84, 31])
('D', 'AA', 46, [132, 9], [145, 54])
('AA', 'S', 65, [145, 54], [84, 31])
('AW', 'H', 65, [107, 1], [172, 1])
('H', 'D', 40, [172, 1], [132, 9])
('Y', 'C', 26, [98, 75], [94, 101])
('C', 'AL', 27, [94, 101], [77, 80])
('AL', 'Y', 21, [77, 80], [98, 75])
('S', 'Y', 46, [84, 31], [98, 75])
('AA', 'Y', 51, [145, 54], [98, 75])
('A', 'B', 24, [92, 145], [95, 121])
('C', 'I', 32, [94, 101], [108, 130])
('B', 'C', 20, [95, 121], [94, 101])
('AK', 'U', 16, [110, 176], [122, 165])
('U', 'M', 59, [122, 165], [159, 212])
('AN', 'U', 41, [163, 165], [122, 165])
('U', 'AQ', 19, [122, 165], [140, 157])
('AQ', 'AN', 24, [140, 157], [163, 165])
('AU', 'AK', 26, [89, 160], [110, 176])
('AI', 'AU', 48, [56, 195], [89, 160])
('AV', 'AF', 19, [176, 75], [163, 90])
('AF', 'K', 18, [163, 90], [145, 84])
('AT', 'AP', 35, [30, 84], [58, 105])
('AP', 'AR', 51, [58, 105], [7, 103])
('AR', 'AT', 29, [7, 103], [30, 84])
('AT', 'AL', 47, [30, 84], [77, 80])
('AL', 'AP', 31, [77, 80], [58, 105])
('W', 'C', 19, [76, 108], [94, 101])
('B', 'W', 23, [95, 121], [76, 108])
('W', 'AP', 18, [76, 108], [58, 105])
('AL', 'W', 28, [77, 80], [76, 108])
('X', 'R', 36, [226, 72], [196, 52])
('R', 'AH', 20, [196, 52], [208, 36])
('AH', 'X', 40, [208, 36], [226, 72])
('G', 'R', 29, [199, 81], [196, 52])
('R', 'H', 56, [196, 52], [172, 1])
('H', 'AH', 50, [172, 1], [208, 36])
('R', 'AJ', 24, [196, 52], [172, 57])
('AJ', 'H', 56, [172, 57], [172, 1])
('R', 'AV', 30, [196, 52], [176, 75])
('G', 'AV', 23, [199, 81], [176, 75])
('AG', 'D', 22, [150, 23], [132, 9])
('H', 'AG', 31, [172, 1], [150, 23])
('AG', 'AA', 31, [150, 23], [145, 54])
('AJ', 'AG', 40, [172, 57], [150, 23])
('E', 'AA', 37, [123, 84], [145, 54])
('K', 'E', 22, [145, 84], [123, 84])
('E', 'Y', 26, [123, 84], [98, 75])
('E', 'C', 33, [123, 84], [94, 101])
('AB', 'AN', 36, [137, 140], [163, 165])
('U', 'AB', 29, [122, 165], [137, 140])
('AB', 'O', 44, [137, 140], [181, 142])
('AB', 'P', 29, [137, 140], [159, 121])
('P', 'O', 30, [159, 121], [181, 142])
('I', 'AE', 16, [108, 130], [108, 146])
('AE', 'A', 16, [108, 146], [92, 145])
('AE', 'AU', 23, [108, 146], [89, 160])
('AU', 'A', 15, [89, 160], [92, 145])
('AE', 'AB', 29, [108, 146], [137, 140])
('U', 'AE', 23, [122, 165], [108, 146])
('AK', 'AE', 30, [110, 176], [108, 146])
('AU', 'AC', 38, [89, 160], [55, 142])
('AC', 'A', 37, [55, 142], [92, 145])
('W', 'AC', 39, [76, 108], [55, 142])
('AC', 'AP', 37, [55, 142], [58, 105])
('AI', 'AC', 53, [56, 195], [55, 142])
('AC', 'B', 45, [55, 142], [95, 121])
('AC', 'AR', 61, [55, 142], [7, 103])
('AC', 'AY', 63, [55, 142], [33, 202])
('AY', 'AR', 102, [33, 202], [7, 103])
('O', 'AO', 36, [181, 142], [169, 108])
('AO', 'J', 35, [169, 108], [204, 116])
('P', 'AO', 16, [159, 121], [169, 108])
('F', 'N', 18, [49, 32], [67, 27])
('AL', 'F', 55, [77, 80], [49, 32])
('AT', 'F', 55, [30, 84], [49, 32])
('AM', 'AT', 27, [4, 76], [30, 84])
('AR', 'AM', 27, [7, 103], [4, 76])
('F', 'AM', 62, [49, 32], [4, 76])
('AM', 'AX', 57, [4, 76], [1, 19])
('Q', 'I', 19, [127, 124], [108, 130])
('C', 'Q', 40, [94, 101], [127, 124])
('E', 'Q', 40, [123, 84], [127, 124])
('Q', 'AE', 29, [127, 124], [108, 146])
('Q', 'AB', 18, [127, 124], [137, 140])
('Q', 'P', 32, [127, 124], [159, 121])
('AO', 'V', 25, [169, 108], [191, 96])
('V', 'J', 23, [191, 96], [204, 116])
('V', 'T', 37, [191, 96], [227, 105])
('V', 'G', 17, [191, 96], [199, 81])
('V', 'AV', 25, [191, 96], [176, 75])
('AO', 'AF', 18, [169, 108], [163, 90])
('L', 'E', 30, [150, 99], [123, 84])
('K', 'L', 15, [145, 84], [150, 99])
('L', 'Q', 33, [150, 99], [127, 124])
('L', 'P', 23, [150, 99], [159, 121])
('AF', 'L', 15, [163, 90], [150, 99])
('L', 'AO', 21, [150, 99], [169, 108])