This looks like a Steiner tree problem. You'll need some programming to solve it.
Picture below shows manually improved output of algorithm from networkX:
and I think computing "flow" in this network will bring you even closer to near optimal solution:
In this case SE student looks like a best candidate to start route.
What you are doing will give you blurry pattern like that:
The script below assumes:
- you have point layer - nodes at roads ends at least 2 of them (terminals) selected
- polyline layer - edges, that have from and to INDICES of nodes populated
- field TIMES in polylines stores a cost of travel through that edge (length?)
Script creates undirected graph first from all nodes (each has to be reachable from others) and computes much smaller new graph where nodes are terminals only and cost of travel between them is shortest distance. After that it computes minimum spanning tree of smaller graph and traces paths between terminals of original large graph. Remember, it is only approximation of optimal Steiner tree, see Wikipedia.
Script is tailored for my work environment (up to line 45) and not very tidy, so ask if any questions.
import arcpy, traceback, os, sys
import itertools
from itertools import tee,chain,combinations
scriptsPath=os.path.dirname(os.path.realpath(__file__))
os.chdir(scriptsPath)
import COMMON
import networkx as nx
import random, numpy, copy
try:
def showPyMessage():
arcpy.AddMessage(str(time.ctime()) + " - " + message)
def pairwise(iterable, cyclic=False):
"s -> (s0, s1), (s1, s2), (s2, s3), ..."
a, b = tee(iterable)
first = next(b, None)
if cyclic is True:
return zip(a, chain(b, (first,)))
return zip(a, b)
## FIND ENVIRONMENT TABLE
mxd = arcpy.mapping.MapDocument("CURRENT")
theT=COMMON.getTable(mxd)
## FIND NODES LAYER
theNodesLayer = COMMON.getInfoFromTable(theT,1)
theNodesLayer = COMMON.isLayerExist(mxd,theNodesLayer)
## GET NUMBER OF NODES AND NUMBER OF SELECTED
aBmNodes = theNodesLayer.getSelectionSet()
nSet=len(aBmNodes)
if nSet<2:
arcpy.AddWarning("Please select at least 2 nodes")
raise SystemExit('Missing input')
arcpy.AddMessage(aBmNodes)
## GET LINKS LAYER
theLinksLayer = COMMON.getInfoFromTable(theT,9)
theLinksLayer = COMMON.isLayerExist(mxd,theLinksLayer)
arcpy.SelectLayerByAttribute_management(theLinksLayer, "CLEAR_SELECTION")
linksFromI=COMMON.getInfoFromTable(theT,14)
linksToI=COMMON.getInfoFromTable(theT,13)
G=nx.Graph()
arcpy.AddMessage("Adding links to graph")
with arcpy.da.SearchCursor(theLinksLayer, (linksFromI,linksToI,"Times")) as cursor:
for i,(f,t,c) in enumerate(cursor):
G.add_edge(f,t,weight=c,no=i)
D=nx.Graph()
for f,t in combinations(aBmNodes, 2):
L=nx.dijkstra_path_length(G,f,t)
D.add_edge(f,t,weight=L)
T=nx.minimum_spanning_tree(D)
del D
aList=[]
for F,T in T.edges():
nodes=nx.dijkstra_path(G,F,T)
for f,t in pairwise(nodes):
aList.append(G[f][t]['no'])
with arcpy.da.UpdateCursor(theLinksLayer, (linksFromI,linksToI,"PART")) as cursor:
for f,t,c in cursor:
cursor.updateRow((f,t,0))
theLinksLayer.setSelectionSet ("NEW",aList)
with arcpy.da.UpdateCursor(theLinksLayer, (linksFromI,linksToI,"PART")) as cursor:
for f,t,c in cursor:
cursor.updateRow((f,t,1))
arcpy.RefreshActiveView()
except:
message = "\n*** PYTHON ERRORS *** "; showPyMessage()
message = "Python Traceback Info: " + traceback.format_tb(sys.exc_info()[2])[0]; showPyMessage()
message = "Python Error Info: " + str(sys.exc_type)+ ": " + str(sys.exc_value) + "\n"; showPyMessage()