2

I am using ArcGIS 10.4.1 for Desktop with a Standard level license and the Network Analyst extension.

It does not seem to be possible to create an ArcGIS network dataset except via manually running the New Network Dataset wizard from the Catalog window.

However, what I am hoping to do is to use ArcPy to read turn information in a table and create turns in a Turn Feature Class. Below I will present some ArcPy code which creates a file geodatabase, a feature dataset, a test street feature class and a table containing two fields FromStreetID and ToStreetID. In the example a row of FromStreetID = 8 and ToStreetID = 9 would mean that a turn from StreetID = 8 onto StreetID = 9 is NOT permitted.

To build the test data I run the script below from IDLE which takes about 10 seconds. Be warned that it will delete a file geodatabase named C:\Temp\test.gdb, if it already exists.

import arcpy

if arcpy.Exists("C:/Temp/Test.gdb"):
    arcpy.Delete_management("C:/Temp/Test.gdb")

arcpy.CreateFileGDB_management("C:/Temp","Test","CURRENT")
arcpy.CreateFeatureDataset_management("C:/Temp/Test.gdb","TestFD")
arcpy.CreateFishnet_management("C:/Temp/Test.gdb/TestFD/TestFishnetPoly",
                               "0 0","0 1","1","1","4","4","",
                               "NO_LABELS","DEFAULT","POLYGON")
arcpy.Intersect_analysis("C:/Temp/Test.gdb/TestFD/TestFishnetPoly #",
                         "C:/Temp/Test.gdb/TestFD/TestFishnetLine",
                         "ALL","-1 Unknown","LINE")
arcpy.Dissolve_management("C:/Temp/Test.gdb/TestFD/TestFishnetLine",
                          "C:/Temp/Test.gdb/TestFD/TestStreet",
                          "","","SINGLE_PART","DISSOLVE_LINES")
arcpy.AddField_management("C:/Temp/Test.gdb/TestFD/TestStreet","StreetID","LONG")
arcpy.CalculateField_management("C:/Temp/Test.gdb/TestFD/TestStreet","StreetID","!OBJECTID!","PYTHON_9.3")
arcpy.CreateTable_management("C:/Temp/Test.gdb","TurnNotAllowed")

arcpy.AddField_management("C:/Temp/Test.gdb/TurnNotAllowed","FromStreetID","LONG")
arcpy.AddField_management("C:/Temp/Test.gdb/TurnNotAllowed","ToStreetID","LONG")

cursor = arcpy.da.InsertCursor("C:/Temp/Test.gdb/TurnNotAllowed",["FromStreetID","ToStreetID"])
cursor.insertRow([8,9])
del cursor

arcpy.CreateTurnFeatureClass_na("C:/Temp/Test.gdb/TestFD","TestTurnFC","5")
arcpy.AddField_management("C:/Temp/Test.gdb/TestFD/TestTurnFC","TurnPermitted","SHORT")

I open ArcMap and in the Catalog window I right click on `C:\Temp\Test.gdb\TestFD and create a New > Network Dataset

  • On the first panel accept the defaults by clicking Next
  • On the second panel tick the checkbox next to TestStreet and click Next
  • On the third panel (about turns) accept the defaults by clicking Next
  • On the fourth panel (about connectivity) accept the defaults by clicking Next
  • On the fifth panel (about elevation) click the radio button for None and click Next
  • On the sixth panel click Add
  • In the Add New Attribute dialog set Name: TurnPermitted, Usage Type: Restriction, Restriction Usage: Prohibited, and then click OK
  • Click Next
  • On the seventh panel (about travel mode) accept the defaults by clicking Next
  • On the eighth panel (about driving directions) accept the defaults by clicking Next
  • On the ninth panel (about building a service area index) accept the defaults by clicking Next
  • On the tenth panel click Finish
  • Click Yes to build the network dataset
  • Click Yes to draw all datasets and ignore the no spatial reference warning
  • Zoom To Layer on the TestFD_ND_Junctions layer and label the TestStreet layer using the StreetID field to get the display below.

enter image description here

Manually, I know how to look for a route between the junction that I have circled and labelled 1 in red to the one labelled 2.

  • I open the Network Analyst toolbar
  • from its pulldown I start a New Route
  • I use the Create Network Location Tool to click on the two locations, and then click Solve
  • I get the expected route through street IDs of 8, 9, and then 1
  • I remove the Route from the Table of Contents

To stop turns being allowed from street 8 onto 9 I start editing the TestTurnFC layer and Continue past the warning.

  • I open the Create Features window from the Editor toolbar and choose the TestTurnFC template to click anywhere along the edge which is Street 8 and then double-click anywhere on the edge which is Street 9. (Make sure Edge Snapping is set).
  • While the turn is still selected I open the Attributes window and set TurnPermitted: -1
  • I Save and Stop Editing.
  • In the Catalog window I open the Properties of TestFD_ND to the Attributes tab and with TurnPermitted highlighted I click Evaluators
  • On the Default Values tab I use the Evaluator Properties of element Turn to set a value of -1 and then make sure the Value is still Ignore Restriction.
  • I click OK twice to accept the settings that I just made.

I build the network dataset from the Network Analyst toolbar.

When I start a New Route and use the same junctions as before the route I get goes through streets 8, 10, 11, 3 and 1 i.e. it observes the no right turn from street 8 to 9.

enter image description here

This is a lot of preamble to get to where I am stuck.

Is it possible to use ArcPy to automate the editing steps above where instead of manually digitizing from the edge on street 8 to the edge on street 9, I instead read those street IDs from the TurnNotAllowed table, and create the turns using code?


My attempt to automate the editing steps above is to run the code below which:

  • uses list comprehension with a SearchCursor to make a list of prohibited turns
  • uses dictionary comprehension with a SearchCursor to make a dictionary of point locations which are halfway along the streets from and to which turns are not allowed
  • uses an Editor session to run an InsertCursor
  • uses the InsertCursor to add a polyline from the midpoint of the street into the turn to the midpoint of the street that comes out of the turn.

import arcpy

turnNotAllowedList = [[row[0],row[1]] for row in arcpy.da.SearchCursor(
    "C:/Temp/Test.gdb/TurnNotAllowed",["FromStreetID","ToStreetID"])]
print turnNotAllowedList

streetDict = {row[1]: row[0].positionAlongLine(0.5,True).firstPoint for row in arcpy.da.SearchCursor(
    "C:/Temp/Test.gdb/TestFD/TestStreet", ["SHAPE@","StreetID"])}
print streetDict

edit = arcpy.da.Editor("C:/Temp/Test.gdb")
edit.startEditing(False,False)
edit.startOperation()

cursor = arcpy.da.InsertCursor("C:/Temp/Test.gdb/TestFD/TestTurnFC",
                               ["SHAPE@","TurnPermitted"])
for turn in turnNotAllowedList:
    array = arcpy.Array()
    array.add(streetDict[turn[0]])
    array.add(streetDict[turn[1]])
    polyline = arcpy.Polyline(array)
    cursor.insertRow([polyline,-1])
del cursor

edit.stopOperation()
edit.stopEditing(True)

The code appears to successfully add the turn feature (arrowed below). However, when I try to build the network the Network Dataset Build Report says:

The network was built, but with some errors. The network can still be used in analysis, however the invalid features will not participate in the network.

Clicking the Show Build Errors button indicates that it is the turn I created using the InsertCursor which is the problem:

SourceName: TestTurnFC, ObjectID: 1, Cannot interpret turn row.

enter image description here

My question therefore remains as above:

Is it possible to use ArcPy to automate the editing steps above where instead of manually digitizing from the edge on street 8 to the edge on street 9, I instead read those street IDs from the TurnNotAllowed table, and create the turns using code?

  • Do the features in question have z values? You didn't specify it in your example, but your environment settings could cause the fishnet to be generated with them present. Not that it should matter, but I've had issues like this in the past that was caused by Z values... – Evil Genius Oct 20 '16 at 11:32
  • @EvilGenius No Z values (at least not intentionally) but I think I may have a solution which is to not use the InsertCursor to write direct to the turn feature class but instead write the same geometries to a line feature class. Then when editing with the template for the turn FC copy/paste the lines from the line FC to the turn FC. Seems to work on my test. – PolyGeo Oct 20 '16 at 11:36
0

My solution to this was to not use the InsertCursor to write direct to the turn feature class but instead to write the same geometries to a line feature class first. Then when editing with the template for the turn feature class I copy/pasted the lines from the line feature class to the turn feature class.

The code to prepare the test data ready for creating the network dataset and then populating it with turns from a table is now:

import arcpy

if arcpy.Exists("C:/Temp/Test.gdb"):
    arcpy.Delete_management("C:/Temp/Test.gdb")

arcpy.CreateFileGDB_management("C:/Temp","Test","CURRENT")
arcpy.CreateFeatureDataset_management("C:/Temp/Test.gdb","TestFD")
arcpy.CreateFishnet_management("C:/Temp/Test.gdb/TestFD/TestFishnetPoly",
                               "0 0","0 1","1","1","4","4","",
                               "NO_LABELS","DEFAULT","POLYGON")
arcpy.Intersect_analysis("C:/Temp/Test.gdb/TestFD/TestFishnetPoly #",
                         "C:/Temp/Test.gdb/TestFD/TestFishnetLine",
                         "ALL","-1 Unknown","LINE")
arcpy.Dissolve_management("C:/Temp/Test.gdb/TestFD/TestFishnetLine",
                          "C:/Temp/Test.gdb/TestFD/TestStreet",
                          "","","SINGLE_PART","DISSOLVE_LINES")
arcpy.AddField_management("C:/Temp/Test.gdb/TestFD/TestStreet","StreetID","LONG")
arcpy.CalculateField_management("C:/Temp/Test.gdb/TestFD/TestStreet","StreetID","!OBJECTID!","PYTHON_9.3")
arcpy.CreateTable_management("C:/Temp/Test.gdb","TurnNotAllowed")

arcpy.AddField_management("C:/Temp/Test.gdb/TurnNotAllowed","FromStreetID","LONG")
arcpy.AddField_management("C:/Temp/Test.gdb/TurnNotAllowed","ToStreetID","LONG")

cursor = arcpy.da.InsertCursor("C:/Temp/Test.gdb/TurnNotAllowed",["FromStreetID","ToStreetID"])
cursor.insertRow([8,9])
cursor.insertRow([10,11])
del cursor

arcpy.CreateTurnFeatureClass_na("C:/Temp/Test.gdb/TestFD","TestTurnFC","5")
arcpy.AddField_management("C:/Temp/Test.gdb/TestFD/TestTurnFC","TurnPermitted","SHORT")

arcpy.CreateFeatureclass_management(out_path="C:/Temp/Test.gdb/TestFD", out_name="TurnLines", geometry_type="POLYLINE", template="", has_m="DISABLED", has_z="DISABLED", spatial_reference="GEOGCS['GCS_GDA_1994',DATUM['D_GDA_1994',SPHEROID['GRS_1980',6378137.0,298.257222101]],PRIMEM['Greenwich',0.0],UNIT['Degree',0.0174532925199433]];-400 -400 1000000000;-100000 10000;-100000 10000;8.98315284119521E-09;0.001;0.001;IsHighPrecision", config_keyword="", spatial_grid_1="0", spatial_grid_2="0", spatial_grid_3="0")
arcpy.AddField_management("C:/Temp/Test.gdb/TestFD/TurnLines","TurnPermitted","SHORT")

turnNotAllowedList = [[row[0],row[1]] for row in arcpy.da.SearchCursor(
    "C:/Temp/Test.gdb/TurnNotAllowed",["FromStreetID","ToStreetID"])]
print turnNotAllowedList

streetDict = {row[1]: row[0].positionAlongLine(0.5,True).firstPoint for row in arcpy.da.SearchCursor(
    "C:/Temp/Test.gdb/TestFD/TestStreet", ["SHAPE@","StreetID"])}
print streetDict

cursor = arcpy.da.InsertCursor("C:/Temp/Test.gdb/TestFD/TurnLines",
                               ["SHAPE@","TurnPermitted"])
for turn in turnNotAllowedList:
    array = arcpy.Array()
    array.add(streetDict[turn[0]])
    array.add(streetDict[turn[1]])
    polyline = arcpy.Polyline(array)
    cursor.insertRow([polyline,-1])
del cursor

I added a second row to the table so that when I did a copy/paste I could see that it also worked for more than one line/turn.


This solution has since been used on complex street networks with tens of thousands of network elements.

If you would like to see an easier solution than this ArcPy workaround then I recommend voting for the ArcGIS Idea to provide a Tool for importing turns from table in network dataset.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.