1

I'm just picking up NetworkX and trying to learn how to use it with Shapefiles.

Right now I have a .shp with a road network that I want to represent in a Graph with NetworkX, so that I can find the shortest path between 2 GPS points. I tried using this, but the problem is that when I run the write_shp() function I lose edges because DiGraph does not allow more than one edge between the same two nodes. The arrow in the figure below shows an example of an edge I lose by using a DiGraph.

enter image description here

So I was wondering if there's any way to create a MultiDiGraph so I don't lose any edges, or if there's any way around it that I could use. I was thinking maybe I could write some code to extract the attributes from the Shapefile and create the MultiDiGraph without using NetworkX's read_shp(), but I don't have any experience at all working with graphs, so I'm not exactly sure if it'd be possible.

I'd really appreciate any help or guidance you could give me, or if I've missed any documentation please let me know. Thanks in advance.

friveraa
  • 6,265
  • 2
  • 12
  • 7
  • Can you provide a minimal working example, and show what your desired output is? As best I can tell from your question an easy solution would be to copy the `read_shp` code in the linked documentation and change `net = nx.DiGraph()` into `net = nx.MultiDiGraph()`. – Joel Jun 11 '15 at 03:16

3 Answers3

3

As best I can follow from your question, the following will do it, basically copied from the original read_shp command.

def read_multi_shp(path):
    """
    copied from read_shp, but allowing MultiDiGraph instead.
    """
    try:
        from osgeo import ogr
    except ImportError:
        raise ImportError("read_shp requires OGR: http://www.gdal.org/")

    net = nx.MultiDiGraph() # <--- here is the main change I made

    def getfieldinfo(lyr, feature, flds):
            f = feature
            return [f.GetField(f.GetFieldIndex(x)) for x in flds]

    def addlyr(lyr, fields):
        for findex in xrange(lyr.GetFeatureCount()):
            f = lyr.GetFeature(findex)
            flddata = getfieldinfo(lyr, f, fields)
            g = f.geometry()
            attributes = dict(zip(fields, flddata))
            attributes["ShpName"] = lyr.GetName()
            if g.GetGeometryType() == 1:  # point
                net.add_node((g.GetPoint_2D(0)), attributes)
            if g.GetGeometryType() == 2:  # linestring
                attributes["Wkb"] = g.ExportToWkb()
                attributes["Wkt"] = g.ExportToWkt()
                attributes["Json"] = g.ExportToJson()
                last = g.GetPointCount() - 1
                net.add_edge(g.GetPoint_2D(0), g.GetPoint_2D(last), attr_dict=attributes) #<--- also changed this line

    if isinstance(path, str):
        shp = ogr.Open(path)
        lyrcount = shp.GetLayerCount()  # multiple layers indicate a directory
        for lyrindex in xrange(lyrcount):
            lyr = shp.GetLayerByIndex(lyrindex)
            flds = [x.GetName() for x in lyr.schema]
            addlyr(lyr, flds)
    return net

I changed the returned graph from a DiGraph to a MultiDigraph and I had to change the add_edge command since the MultiDiGraph version has different syntax from DiGraph

Joel
  • 22,598
  • 6
  • 69
  • 93
  • I'm getting TypeError: unhashable type: 'dict' in File "C:\Python34\lib\site-packages\networkx\classes\multidigraph.py", line 262, in add_edge keydict={key:datadict} – friveraa Jun 11 '15 at 03:38
  • Can you provide some sample input and expected output? Then I can test my answer. – Joel Jun 11 '15 at 03:47
  • What do you mean? I'm using read_shp() with a shapefile that contains a lot of lines (streets) with attributes, and what I want to get is a directed graph that contains all of them. Problem is, as I stated in the question, that when using read_shp() some of the edges are deleted, but I need to have all the lines as edges. I could send you the shapefile I'm using if that's what you need. – friveraa Jun 11 '15 at 04:00
  • try now? If there's some way you can provide a simple shapefile to use, that would be great. Otherwise I can't actually test what I'm suggesting to try. – Joel Jun 11 '15 at 04:48
  • I tried it again today after working on another solution, and it actually worked. I'm really sorry but I have no idea why it didn't work the other day. Thanks for helping out. – friveraa Jun 14 '15 at 21:46
  • I had done an edit right before my last post to fix the error you reported in your first comment above. That may be the issue. (you'll see the comment "also changed...") – Joel Jun 14 '15 at 22:16
1

If the multi-lines are disconnected at the joint, I think this library python-s2g(https://github.com/caesar0301/python-s2g) could help you. Even networkx's Graph object is used under the hood, those multi-paths are actually recorded by the graph data.

caesar0301
  • 1,913
  • 2
  • 22
  • 24
-1

I have implemented a solution here: https://gitlab.com/njacadieux/upstream_downstream_shortests_path_dijkstra

I read the shapefile using GeoPandas and not the networkx.readwrite.nx_shp.read_shp. When I build the graph, I check for parallel edges. If found, rather than skipping them, as does the networkx.readwrite.nx_shp.read_shp function, I split the parallel edges in two edges of equal length and then divide the user length by 2. The user length variable field name must be given.

SecretAgentMan
  • 2,856
  • 7
  • 21
  • 41