This Python script contains a tool for Emme that lists nodes and their directionally sorted adjacent nodes. It classifies nodes as north, northwest, west, etc. based on their angle relative to the selected node. The tool exports the results to text files with 4 or fewer nodes, 5 nodes, or 6+ nodes per line. It raises errors if no output file is specified.
1. work/Error.bmp
work/Output.bmp
Codes.txt
#!/usr/bin/python
import math as _math
import traceback as _traceback
import os
import inro.modeller as _m
import inro.emme.core.exception as _except
class AdjacentNodes(_m.Tool()):
node_selection = _m.Attribute(unicode)
north_angle = _m.Attribute(float)
2. export_file = _m.Attribute(unicode)
export_file_5 = _m.Attribute(unicode)
export_file_6 = _m.Attribute(unicode)
scenario = _m.Attribute(_m.InstanceType)
tool_run_msg = ""
def __init__(self):
self.north_angle = 90
self.node_selection = "all"
def page(self):
pb = _m.ToolPageBuilder(self,
title="List Adjacent Nodes",
description="Exports the nodes specified by a selection
expression to "
"a text file, with a list of the adjacent nodes "
"sorted North, Northwest, West, Southwest, South,
Southeast, "
"East, and Northeast.<br>",
3. branding_text="INRO - Emme")
if self.tool_run_msg != "":
pb.tool_run_status(self.tool_run_msg_status)
pb.add_text_box("node_selection", size=20,
title="Node selection expression:", multi_line=True)
pb.add_text_box("north_angle", size=10,
title="North Angle:",
note="Angle which is North, counterclockwise relative
to the horizontal axis.")
pb.add_select_file("export_file",
window_type="save_file", file_filter="Text Documents (*.txt)",
start_path=os.path.dirname(_m.Modeller().desktop.project_file_
name()),
title="Export file (4 or less nodes) :")
pb.add_select_file("export_file_5",
window_type="save_file", file_filter="Text Documents (*.txt)",
start_path=os.path.dirname(_m.Modeller().desktop.project_file_
name()),
title="Export file (5 nodes):")
6. node_ids = export_utils.apply_node_selection(scenario,
{"node": node_selection})
classify = ClassifyAdjacentNodes(north_angle)
f = None
f5 = None
f6 = None
file_hdr = "Node N NW W SW S SE
E NEn"
if export_file:
f = open(export_file, 'w')
f.write(file_hdr)
if export_file_5 and export_file_5 != export_file:
f5 = open(export_file_5, 'w')
f5.write(file_hdr)
if export_file_6 and export_file_6 != export_file and
export_file_6 != export_file_5:
f6 = open(export_file_6, 'w')
f6.write(file_hdr)
if not (f or f5 or f6):
7. raise _except.Error("Missing output file name")
for node_id in node_ids:
node = network.node(node_id)
adj, node_count = classify(node)
line_buf = str(node_id)
l = 0
while True:
for i in range(8):
d = ["N", "NW", "W", "SW", "S", "SE", "E",
"NE"][i]
if d in adj and l < len(adj[d]):
line_buf = "%-*s %s" % (i * 8 + 7, line_buf,
adj[d][l])
if not line_buf:
break
line_buf += "n"
if node_count < 5:
if f:
f.write(line_buf)
8. elif node_count < 6:
if f5:
f5.write(line_buf)
elif f6:
f6.write(line_buf)
line_buf = ""
l += 1
return
@_m.method(return_type=_m.UnicodeType)
def tool_run_msg_status(self):
return self.tool_run_msg
class ClassifyAdjacentNodes(object):
def __init__(self, north_angle=90):
self.directions = ["N", "NW", "W", "SW", "S", "SE", "E",
"NE"] # counter clockwise order, starting with North
self.expected_angles = [(x + north_angle) % 360 for x in
9. range(0, 360, 45)]
def __call__(self, at_node):
out_link_nodes = set([l.j_node for l in
at_node.outgoing_links()])
in_link_nodes = set([l.i_node for l in
at_node.incoming_links()])
adjacent_nodes = out_link_nodes.union(in_link_nodes)
directional_nodes = {}
for node in adjacent_nodes:
closest = self._closest_direction(at_node, node)
if not closest in directional_nodes:
directional_nodes[closest] = [node]
else:
directional_nodes[closest] += [node]
return directional_nodes, len(adjacent_nodes)
def _closest_direction(self, at_node, dir_node):
10. angle = _math.degrees(_math.atan2(dir_node.y -
at_node.y,
dir_node.x - at_node.x))
angle_names = zip(self.expected_angles, self.directions)
angle_diffs = [(self._normalize(a - angle), n) for a, n in
angle_names]
angle_diffs.sort()
return angle_diffs[0][1]
def _normalize(self, ang_diff):
v = abs(ang_diff) % 360
if v > 180:
return 360 - v
return v