6

I know this isn't possible in arcpy, so I thought I'd give it a stab using comtypes. I am trying to translate some of the VB.NET code to create a network dataset from the help docs:

http://resources.arcgis.com/en/help/arcobjects-net/conceptualhelp/index.html#/How_to_create_a_network_dataset/0001000000w7000000/

I'm trying to automate this process for an engineer as he is not very experienced with GIS. He will always be using a file geodatabase with one line feature class as edge features (fc name will vary) and two point feature classes as junction features (sources and sinks to act as origins and destinations). Once the network is built, he can use other script tools I am providing him to do the whole analysis.

I think I am pretty close as I am able to create the edge and junction features, but my code is failing at one of the last steps which is getting the extension for the IFeatureDatasetExtensionContainer interface via the FindExtension method. The help docs say I can use the esriGeoDatabase.esriDatasetType constant but this seems to be causing an error:

http://resources.arcgis.com/en/help/arcobjects-net/componenthelp/index.html#//0025000002ww000000

here is where it is failing:

# create network data set based on data element
fdxc = CType(fdsGDS, esriGeoDatabase.IFeatureDatasetExtensionContainer)
fdx = fdxc.FindExtension(esriGeoDatabase.esriDatasetType(7))  # get error here
dsc = CType(fdx, esriGeoDatabase.IDatasetContainer2)
netds = CType(dsc.CreateDataset(nd), esriGeoDatabase.INetworkDataset)

Here is my error:

Traceback (most recent call last):
  File "\\arcserver1\GIS\_Resources\ESRI\Python\BMI_Library\arcobjects\network_dataset.py", line 114, in <module>
    create_nd(gdb)
  File "\\arcserver1\GIS\_Resources\ESRI\Python\BMI_Library\arcobjects\network_dataset.py", line 98, in create_nd
    fdx = fdxc.FindExtension(esriGeoDatabase.esriDatasetType(7))
COMError: (-2147220729, None, (u'The specified feature dataset extension type was not found.', u'Esri GeoDatabase', u'esri_csGeoDatabase.hlp', 0, None))

And here is my full code if that helps:

def create_nd(gdb):
    arcpy.CheckOutExtension('Network')
    getModule('esriGeoDatabase.olb')
    getModule('esriDataSourcesGDB.olb')
    import comtypes.gen.esriGeoDatabase as esriGeoDatabase
    import comtypes.gen.esriDataSourcesGDB as esriDataSourcesGDB

    # create new empty data element for buildable network dataset
    nd = NewObj(esriGeoDatabase.DENetworkDataset,
                esriGeoDatabase.IDENetworkDataset2)
    nd.Buildable = True
    nd.NetworkType = esriGeoDatabase.esriNetworkDatasetType(1)

    # open feature dataset and create IGeoDataset interface
    pWSF = NewObj(esriDataSourcesGDB.FileGDBWorkspaceFactory, esriGeoDatabase.IWorkspaceFactory)
    gdbWSF = CType(pWSF, esriGeoDatabase.IWorkspaceFactory)
    print gdbWSF.WorkspaceDescription, gdbWSF.WorkspaceType
    gdbFWS = CType(gdbWSF.OpenFromFile(gdb, 0), esriGeoDatabase.IFeatureWorkspace)
    print gdbFWS
    openFWS = gdbFWS.OpenFeatureDataset('FlowNet')
    fdsGDS = CType(openFWS, esriGeoDatabase.IGeoDataset)
    print fdsGDS

    # copy feature dataset's extent and spatial reference to network dataset element
    deGDS = CType(nd, esriGeoDatabase.IDEGeoDataset)
    deGDS.Extent = fdsGDS.Extent
    deGDS.SpatialReference = fdsGDS.SpatialReference
    print deGDS.Extent

    # get flow lines
    arcpy.env.workspace = os.path.join(gdb, 'FlowNet')
    lines = arcpy.ListFeatureClasses('*', 'Polyline')[0]  

    # create data element
    dataElement = CType(nd, esriGeoDatabase.IDataElement)
    dataElement.Name = 'FlowNetwork'
    print dataElement.Name

    # specify edge features
    edgeNet = NewObj(esriGeoDatabase.EdgeFeatureSource,
                     esriGeoDatabase.INetworkSource)
    edgeNet.Name = lines
    edgeNet.ElementType = esriGeoDatabase.esriNetworkElementType(2)
##    edgeNet.SourceType = esriGeoDatabase.esriNetworkSourceType(3)
##    edgeNet.UsesGeometryInConnectivity = True

    #set edge feature's connectivity (Any Vertex)
    edgeFS = CType(edgeNet, esriGeoDatabase.IEdgeFeatureSource)
    edgeFS.UsesSubtypes = False
    edgeFS.ClassConnectivityGroup = 1
    edgeFS.ClassConnectivityPolicy = esriGeoDatabase.esriNetworkEdgeConnectivityPolicy(0) # any vertex


    # add sources and sinks as junctions
    # make origins
    sinkNet = NewObj(esriGeoDatabase.JunctionFeatureSource,
                     esriGeoDatabase.INetworkSource)
    sinkNet.Name = 'Sinks'
    sinkNet.ElementType = esriGeoDatabase.esriNetworkElementType(1)
##    sinkNet.SourceType = esriGeoDatabase.esriNetworkSourceType(2)
##    sinkNet.UsesGeometryInConnectivity = True

    # set connectivity
    sinkElm = CType(sinkNet, esriGeoDatabase.IJunctionFeatureSource)
    sinkElm.UsesSubtypes = False
    sinkElm.ClassConnectivityGroup = 1
    sinkElm.ClassConnectivityPolicy = esriGeoDatabase.esriNetworkJunctionConnectivityPolicy(0) # honor

    # make destinations
    sourceNet = NewObj(esriGeoDatabase.JunctionFeatureSource,
                     esriGeoDatabase.INetworkSource)
    sourceNet.Name = 'Sources'
    sourceNet.ElementType = esriGeoDatabase.esriNetworkElementType(1)
##    sourceNet.SourceType = esriGeoDatabase.esriNetworkSourceType(2)
##    sourceNet.UsesGeometryInConnectivity = True

    # set connectivity
    sourceElm = CType(sourceNet, esriGeoDatabase.IJunctionFeatureSource)
    sourceElm.UsesSubtypes = False
    sourceElm.ClassConnectivityGroup = 1
    sourceElm.ClassConnectivityPolicy = esriGeoDatabase.esriNetworkJunctionConnectivityPolicy(0) # honor

    # add attributes (Length, Cost, Units)
    evalNetAttr = NewObj(esriGeoDatabase.EvaluatedNetworkAttribute,
                         esriGeoDatabase.IEvaluatedNetworkAttribute)
    newAttr2 = CType(evalNetAttr, esriGeoDatabase.INetworkAttribute2)
    newAttr2.Name = 'Length'
    newAttr2.UsageType = esriGeoDatabase.esriNetworkAttributeUsageType(0) # cost
    newAttr2.DataType = esriGeoDatabase.esriNetworkAttributeDataType(2) # double
    newAttr2.Units = esriGeoDatabase.esriNetworkAttributeUnits(3)  # feet
    newAttr2.UseByDefault = True

    # create network data set based on data element
    fdxc = CType(fdsGDS, esriGeoDatabase.IFeatureDatasetExtensionContainer)
    fdx = fdxc.FindExtension(esriGeoDatabase.esriDatasetType(7)) # fails here
    dsc = CType(fdx, esriGeoDatabase.IDatasetContainer2)
    netds = CType(dsc.CreateDataset(nd), esriGeoDatabase.INetworkDataset)

    # build network dataset now that it's created
    netBuild = CType(netds, esriGeoDatabase.INetworkBuild)
    for item in [edgeNet, sinkNet, sourceNet]:
        netBuild.AddSource(item)
    netBuild.AddAttribute(newAttr2)
    netBuild.BuildNetwork(fsdGDS.Extent)
    arcpy.CheckInExtension('Network')
    return

if __name__ == '__main__':

    gdb = r'C:\Users\calebma\Desktop\NA_Testing\test2.gdb'
    create_nd(gdb)
    print 'done'
  • I know this thread is old but do you still have the full code for building a network dataset using arcpy? I tried to use the 'full code' you posted but it doesn't seem like the actual full code. For example there's no 'import arcpy'. I am a newbie at this and I am a little lost trying to use your code in python. Also, the arcgis help you linked to is in .net and c#. How did you incorporate it with arcpy? – ketar Aug 15 '17 at 14:58
  • It is not incorporated with arcpy, it is using the comtypes module which is a third party module that generates python bindings to COM based libraries. This is used to access ArcObjects from Python. The code above got me 3/4 of the way there, but I never actually got around to finishing it. You would need to follow the ArcObjects help docs to set up the networks according to your specific needs. – crmackey Aug 15 '17 at 20:28
4

OK going to have a stab at this:

Most of your code appears to be building a Network dataset but this line

fdx = fdxc.FindExtension(esriGeoDatabase.esriDatasetType(7))  # get error here

is trying to return a GeometricNetwork (7), have a try at returning a Networkdataset (19)?

  • Wow, that should have been obvious! I guess I need more coffee. Thanks! – crmackey Aug 7 '14 at 15:48
3

I've also created a script that creates a Network Dataset using comtypes and arcobjects COM Objects. The information posted here, these two GIS-se posts and the ArcGIS tutorial linked in the question were extremely helpful me in putting this together. I went into a little more depth in configuring the network attributes and other settings of the Network Dataset than what is in the content of the question and answers here so I'm posting my code (which is a solution to the question posed fwiw) in hopes that it may be helpful to others. The script below does import a couple of things from other python modules and those references can be seen in this github repo which hosts the larger project that this is a part of.

from datetime import datetime
from os.path import basename, join

from arcpy import env, CheckInExtension, GetInstallInfo
from comtypes.client import CreateObject, GetModule

ARCOBJECTS_DIR = join(GetInstallInfo()['InstallDir'], 'com')
GetModule(join(ARCOBJECTS_DIR, 'esriDataSourcesFile.olb'))
GetModule(join(ARCOBJECTS_DIR, 'esriGeoDatabase.olb'))
GetModule(join(ARCOBJECTS_DIR, 'esriSystem.olb'))

from comtypes.gen.esriDataSourcesGDB import FileGDBWorkspaceFactory
from comtypes.gen.esriGeoDatabase import esriDatasetType, \
    esriNetworkAttributeDataType, esriNetworkAttributeUnits, \
    esriNetworkAttributeUsageType, esriNetworkDatasetType, \
    esriNetworkEdgeConnectivityPolicy, esriNetworkEdgeDirection, \
    esriNetworkElementType, DENetworkDataset, EdgeFeatureSource, \
    EvaluatedNetworkAttribute, IDataElement, IDataset, IDatasetContainer3, \
    IDEGeoDataset, IDENetworkDataset2, IEdgeFeatureSource, IEnumDataset, \
    IEvaluatedNetworkAttribute, IFeatureDatasetExtension, \
    IFeatureDatasetExtensionContainer, IFeatureWorkspace, IGeoDataset, \
    INetworkAttribute2, INetworkBuild, INetworkDataset, \
    INetworkConstantEvaluator, INetworkEvaluator, INetworkFieldEvaluator2, \
    INetworkSource, IWorkspace, IWorkspaceFactory, IWorkspaceExtension3, \
    IWorkspaceExtensionManager, NetworkConstantEvaluator, \
    NetworkFieldEvaluator
from comtypes.gen.esriSystem import IArray, IUID, UID

from lightraildev.common import checkout_arcgis_extension, ATTRIBUTE_LEN, \
    ATTRIBUTE_MIN, ATTRIBUTE_PED, OSM_PED_FC, OSM_PED_FDS, OSM_PED_GDB, \
    OSM_PED_ND

# for some reason esriSystem.Array objects can't be created normally 
# via comtypes, I found a workaround on pg 7 of the linked pdf, below is 
# the object's GUID which can be supplied in place of the object
# http://www.pierssen.com/arcgis10/upload/python/arcmap_and_python.pdf
ARRAY_GUID = "{8F2B6061-AB00-11D2-87F4-0000F8751720}"

FDS_NAME = basename(OSM_PED_FDS)
FC_NAME = basename(OSM_PED_FC)
ND_NAME = basename(OSM_PED_ND)


def create_gdb_network_dataset():
    """"""

    # create an empty data element for a buildable network dataset
    net = new_obj(DENetworkDataset, IDENetworkDataset2)
    net.Buildable = True
    net.NetworkType = esriNetworkDatasetType(1)

    # open the feature class and ctype to the IGeoDataset interface
    gdb_ws_factory = new_obj(FileGDBWorkspaceFactory, IWorkspaceFactory)
    gdb_workspace = ctype(gdb_ws_factory.OpenFromFile(OSM_PED_GDB, 0),
                          IFeatureWorkspace)
    gdb_feat_ds = ctype(gdb_workspace.OpenFeatureDataset(FDS_NAME),
                        IGeoDataset)

    # copy the feature dataset's extent and spatial reference to the
    # network dataset data element
    net_geo_elem = ctype(net, IDEGeoDataset)
    net_geo_elem.Extent = gdb_feat_ds.Extent
    net_geo_elem.SpatialReference = gdb_feat_ds.SpatialReference

    # specify the name of the network dataset
    net_element = ctype(net, IDataElement)
    net_element.Name = ND_NAME

    edge_net = new_obj(EdgeFeatureSource, INetworkSource)
    edge_net.Name = FC_NAME
    edge_net.ElementType = esriNetworkElementType(2)

    # set the edge feature source's connectivity settings, connect
    # network through any coincident vertex
    edge_feat = ctype(edge_net, IEdgeFeatureSource)
    edge_feat.ClassConnectivityGroup = 1
    edge_feat.ClassConnectivityPolicy = esriNetworkEdgeConnectivityPolicy(0)
    edge_feat.UsesSubtypes = False

    source_array = new_obj(ARRAY_GUID, IArray)
    source_array.Add(edge_net)
    net.Sources = source_array

    add_network_attributes(net, edge_net)

    # get the feature dataset extension and create the network dataset
    # based on the data element.
    osm_data_xc = ctype(gdb_feat_ds, IFeatureDatasetExtensionContainer)
    osm_data_ext = ctype(osm_data_xc.FindExtension(esriDatasetType(19)),
                         IFeatureDatasetExtension)
    osm_data_cont = ctype(osm_data_ext, IDatasetContainer3)
    net_dataset = ctype(osm_data_cont.CreateDataset(net), INetworkDataset)

    # once the network dataset is created, build it
    net_build = ctype(net_dataset, INetworkBuild)
    net_build.BuildNetwork(net_geo_elem.Extent)


def add_network_attributes(net, edge_net):
    """"""

    attribute_array = new_obj(ARRAY_GUID, IArray)
    language = 'VBScript'

    # 1) 'ped_permissions' attribute establishes the streets and trails that
    # pedestrians can and cannot be routed along
    ped_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    ped_attr = ctype(ped_eval_attr, INetworkAttribute2)
    ped_attr.DataType = esriNetworkAttributeDataType(3)  # boolean
    ped_attr.Name = ATTRIBUTE_PED
    ped_attr.Units = esriNetworkAttributeUnits(0)  # unknown
    ped_attr.UsageType = esriNetworkAttributeUsageType(2)  # restriction
    ped_attr.UseByDefault = True

    ped_expr = 'restricted'
    ped_logic = (
        'Set foot_list = CreateObject("System.Collections.ArrayList")'   '\n'
        'foot_list.Add "designated"'                                     '\n'
        'foot_list.Add "permissive"'                                     '\n'
        'foot_list.Add "yes"'                                          '\n\n'

        'Set hwy_list = CreateObject("System.Collections.ArrayList")'    '\n'
        'hwy_list.Add "construction"'                                    '\n'
        'hwy_list.Add "motorway"'                                        '\n'
        'hwy_list.Add "trunk"'                                         '\n\n'

        'If foot_list.Contains([foot]) Then'                             '\n'
        '    restricted = False'                                         '\n'
        'ElseIf _'                                                       '\n'
        '        ([access] = "no") Or _'                                 '\n'
        '        ([foot] = "no") Or _'                                   '\n'
        '        ([indoor] = "yes") Or _'                                '\n'
        '        (hwy_list.Contains([highway])) Then'                    '\n'
        '    restricted = True'                                          '\n'
        'Else'                                                           '\n'
        '    restricted = False'                                         '\n'
        'End If'
    )

    set_evaluator_logic(ped_eval_attr, edge_net, ped_expr, ped_logic, language)
    set_evaluator_constants(ped_eval_attr, False)
    attribute_array.Add(ped_eval_attr)

    # 2) 'length' attribute, in feet
    len_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    len_attr = ctype(len_eval_attr, INetworkAttribute2)
    len_attr.DataType = esriNetworkAttributeDataType(2)  # double
    len_attr.Name = ATTRIBUTE_LEN
    len_attr.Units = esriNetworkAttributeUnits(3)  # feet
    len_attr.UsageType = esriNetworkAttributeUsageType(0)  # cost
    len_attr.UseByDefault = True

    len_expr = '[Shape]'
    set_evaluator_logic(len_eval_attr, edge_net, len_expr, '', language)
    set_evaluator_constants(len_eval_attr, 0)
    attribute_array.Add(len_eval_attr)

    # 3) 'minutes' attribute, assumes a walking speed of 3 mph
    min_eval_attr = new_obj(EvaluatedNetworkAttribute,
                            IEvaluatedNetworkAttribute)
    min_attr = ctype(min_eval_attr, INetworkAttribute2)
    min_attr.DataType = esriNetworkAttributeDataType(2)  # double
    min_attr.Name = ATTRIBUTE_MIN
    min_attr.Units = esriNetworkAttributeUnits(21)  # minutes
    min_attr.UsageType = esriNetworkAttributeUsageType(0)  # cost
    min_attr.UseByDefault = True

    min_expr = 'walk_minutes'
    min_logic = 'walk_minutes = [Shape] / (5280 * (3 / 60))'
    set_evaluator_logic(min_eval_attr, edge_net, min_expr, min_logic, language)
    set_evaluator_constants(min_eval_attr, 0)
    attribute_array.Add(min_eval_attr)

    net.Attributes = attribute_array


def set_evaluator_logic(eval_attr, edge_net, expression, pre_logic, lang):
    """This function implements the same logic for an attribute in
    both the along and against directions
    """

    # for esriNetworkEdgeDirection 1=along, 2=against
    for direction in (1, 2):
        net_eval = new_obj(NetworkFieldEvaluator, INetworkFieldEvaluator2)
        net_eval.SetLanguage(lang)
        net_eval.SetExpression(expression, pre_logic)
        eval_attr.Evaluator.setter(
            eval_attr, edge_net, esriNetworkEdgeDirection(direction),
            ctype(net_eval, INetworkEvaluator))


def set_evaluator_constants(eval_attr, constant):
    """This function sets all evaluator constants to the same value"""

    # for ConstantValue False means traversable (that is, not restricted),
    # for esriNetworkElementType 1=junction, 2=edge, 3=turn
    for element_type in (1, 2, 3):
        const_eval = new_obj(NetworkConstantEvaluator,
                             INetworkConstantEvaluator)
        const_eval.ConstantValue = constant
        eval_attr.DefaultEvaluator.setter(
            eval_attr, esriNetworkElementType(element_type),
            ctype(const_eval, INetworkEvaluator))


# the following two functions are derived from the linked module and make the
# code more VB.net(ic) the language from which the tutorial this code from is
# written in, all they really do is make comtypes functionality slightly less
# verbose, but that's valuable since they're called so many times
# http://www.pierssen.com/arcgis10/upload/python/snippets102.py


def new_obj(class_, interface):
    """Creates a new comtypes POINTER object where 'class_' is the class
    to be instantiated, 'interface' is the interface to be assigned
    """

    pointer = CreateObject(class_, interface=interface)
    return pointer


def ctype(obj, interface):
    """Casts obj to interface and returns comtypes POINTER"""

    pointer = obj.QueryInterface(interface)
    return pointer


def main():
    """"""

    env.overwriteOutput = True
    checkout_arcgis_extension('Network')

    create_gdb_network_dataset()
    CheckInExtension('Network')


if __name__ == '__main__':
    main()
  • I know this is old but I get the error, 'No module named lightraildev.common'. Do you know what would be causing that? – ketar Aug 15 '17 at 16:07
  • @ketar the code here is part of a larger project and won't run on it's own, this is the module that you're missing. I haven't looked at this project in awhile but there may be other modules in that repo that you'll need as well – Grant Humphries Aug 15 '17 at 17:30
  • Thank you for the response. I am digging into your code together with the arcgis help. I think I am making some headway. – ketar Aug 15 '17 at 17:33

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.