diff env/lib/python3.9/site-packages/networkx/readwrite/nx_shp.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/networkx/readwrite/nx_shp.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,316 @@
+"""
+*********
+Shapefile
+*********
+
+Generates a networkx.DiGraph from point and line shapefiles.
+
+"The Esri Shapefile or simply a shapefile is a popular geospatial vector
+data format for geographic information systems software. It is developed
+and regulated by Esri as a (mostly) open specification for data
+interoperability among Esri and other software products."
+See https://en.wikipedia.org/wiki/Shapefile for additional information.
+"""
+import networkx as nx
+
+__all__ = ["read_shp", "write_shp"]
+
+
+def read_shp(path, simplify=True, geom_attrs=True, strict=True):
+    """Generates a networkx.DiGraph from shapefiles. Point geometries are
+    translated into nodes, lines into edges. Coordinate tuples are used as
+    keys. Attributes are preserved, line geometries are simplified into start
+    and end coordinates. Accepts a single shapefile or directory of many
+    shapefiles.
+
+    "The Esri Shapefile or simply a shapefile is a popular geospatial vector
+    data format for geographic information systems software [1]_."
+
+    Parameters
+    ----------
+    path : file or string
+       File, directory, or filename to read.
+
+    simplify:  bool
+        If True, simplify line geometries to start and end coordinates.
+        If False, and line feature geometry has multiple segments, the
+        non-geometric attributes for that feature will be repeated for each
+        edge comprising that feature.
+
+    geom_attrs: bool
+        If True, include the Wkb, Wkt and Json geometry attributes with
+        each edge.
+
+        NOTE:  if these attributes are available, write_shp will use them
+        to write the geometry.  If nodes store the underlying coordinates for
+        the edge geometry as well (as they do when they are read via
+        this method) and they change, your geomety will be out of sync.
+
+    strict: bool
+        If True, raise NetworkXError when feature geometry is missing or
+        GeometryType is not supported.
+        If False, silently ignore missing or unsupported geometry in features.
+
+    Returns
+    -------
+    G : NetworkX graph
+
+    Raises
+    ------
+    ImportError
+       If ogr module is not available.
+
+    RuntimeError
+       If file cannot be open or read.
+
+    NetworkXError
+       If strict=True and feature is missing geometry or GeometryType is
+       not supported.
+
+    Examples
+    --------
+    >>> G = nx.read_shp("test.shp")  # doctest: +SKIP
+
+    References
+    ----------
+    .. [1] https://en.wikipedia.org/wiki/Shapefile
+    """
+    try:
+        from osgeo import ogr
+    except ImportError as e:
+        raise ImportError("read_shp requires OGR: http://www.gdal.org/") from e
+
+    if not isinstance(path, str):
+        return
+
+    net = nx.DiGraph()
+    shp = ogr.Open(path)
+    if shp is None:
+        raise RuntimeError(f"Unable to open {path}")
+    for lyr in shp:
+        fields = [x.GetName() for x in lyr.schema]
+        for f in lyr:
+            g = f.geometry()
+            if g is None:
+                if strict:
+                    raise nx.NetworkXError("Bad data: feature missing geometry")
+                else:
+                    continue
+            flddata = [f.GetField(f.GetFieldIndex(x)) for x in fields]
+            attributes = dict(zip(fields, flddata))
+            attributes["ShpName"] = lyr.GetName()
+            # Note:  Using layer level geometry type
+            if g.GetGeometryType() == ogr.wkbPoint:
+                net.add_node((g.GetPoint_2D(0)), **attributes)
+            elif g.GetGeometryType() in (ogr.wkbLineString, ogr.wkbMultiLineString):
+                for edge in edges_from_line(g, attributes, simplify, geom_attrs):
+                    e1, e2, attr = edge
+                    net.add_edge(e1, e2)
+                    net[e1][e2].update(attr)
+            else:
+                if strict:
+                    raise nx.NetworkXError(
+                        "GeometryType {} not supported".format(g.GetGeometryType())
+                    )
+
+    return net
+
+
+def edges_from_line(geom, attrs, simplify=True, geom_attrs=True):
+    """
+    Generate edges for each line in geom
+    Written as a helper for read_shp
+
+    Parameters
+    ----------
+
+    geom:  ogr line geometry
+        To be converted into an edge or edges
+
+    attrs:  dict
+        Attributes to be associated with all geoms
+
+    simplify:  bool
+        If True, simplify the line as in read_shp
+
+    geom_attrs:  bool
+        If True, add geom attributes to edge as in read_shp
+
+
+    Returns
+    -------
+     edges:  generator of edges
+        each edge is a tuple of form
+        (node1_coord, node2_coord, attribute_dict)
+        suitable for expanding into a networkx Graph add_edge call
+    """
+    try:
+        from osgeo import ogr
+    except ImportError as e:
+        raise ImportError(
+            "edges_from_line requires OGR: " "http://www.gdal.org/"
+        ) from e
+
+    if geom.GetGeometryType() == ogr.wkbLineString:
+        if simplify:
+            edge_attrs = attrs.copy()
+            last = geom.GetPointCount() - 1
+            if geom_attrs:
+                edge_attrs["Wkb"] = geom.ExportToWkb()
+                edge_attrs["Wkt"] = geom.ExportToWkt()
+                edge_attrs["Json"] = geom.ExportToJson()
+            yield (geom.GetPoint_2D(0), geom.GetPoint_2D(last), edge_attrs)
+        else:
+            for i in range(0, geom.GetPointCount() - 1):
+                pt1 = geom.GetPoint_2D(i)
+                pt2 = geom.GetPoint_2D(i + 1)
+                edge_attrs = attrs.copy()
+                if geom_attrs:
+                    segment = ogr.Geometry(ogr.wkbLineString)
+                    segment.AddPoint_2D(pt1[0], pt1[1])
+                    segment.AddPoint_2D(pt2[0], pt2[1])
+                    edge_attrs["Wkb"] = segment.ExportToWkb()
+                    edge_attrs["Wkt"] = segment.ExportToWkt()
+                    edge_attrs["Json"] = segment.ExportToJson()
+                    del segment
+                yield (pt1, pt2, edge_attrs)
+
+    elif geom.GetGeometryType() == ogr.wkbMultiLineString:
+        for i in range(geom.GetGeometryCount()):
+            geom_i = geom.GetGeometryRef(i)
+            yield from edges_from_line(geom_i, attrs, simplify, geom_attrs)
+
+
+def write_shp(G, outdir):
+    """Writes a networkx.DiGraph to two shapefiles, edges and nodes.
+    Nodes and edges are expected to have a Well Known Binary (Wkb) or
+    Well Known Text (Wkt) key in order to generate geometries. Also
+    acceptable are nodes with a numeric tuple key (x,y).
+
+    "The Esri Shapefile or simply a shapefile is a popular geospatial vector
+    data format for geographic information systems software [1]_."
+
+    Parameters
+    ----------
+    outdir : directory path
+       Output directory for the two shapefiles.
+
+    Returns
+    -------
+    None
+
+    Examples
+    --------
+    nx.write_shp(digraph, '/shapefiles') # doctest +SKIP
+
+    References
+    ----------
+    .. [1] https://en.wikipedia.org/wiki/Shapefile
+    """
+    try:
+        from osgeo import ogr
+    except ImportError as e:
+        raise ImportError("write_shp requires OGR: http://www.gdal.org/") from e
+    # easier to debug in python if ogr throws exceptions
+    ogr.UseExceptions()
+
+    def netgeometry(key, data):
+        if "Wkb" in data:
+            geom = ogr.CreateGeometryFromWkb(data["Wkb"])
+        elif "Wkt" in data:
+            geom = ogr.CreateGeometryFromWkt(data["Wkt"])
+        elif type(key[0]).__name__ == "tuple":  # edge keys are packed tuples
+            geom = ogr.Geometry(ogr.wkbLineString)
+            _from, _to = key[0], key[1]
+            try:
+                geom.SetPoint(0, *_from)
+                geom.SetPoint(1, *_to)
+            except TypeError:
+                # assume user used tuple of int and choked ogr
+                _ffrom = [float(x) for x in _from]
+                _fto = [float(x) for x in _to]
+                geom.SetPoint(0, *_ffrom)
+                geom.SetPoint(1, *_fto)
+        else:
+            geom = ogr.Geometry(ogr.wkbPoint)
+            try:
+                geom.SetPoint(0, *key)
+            except TypeError:
+                # assume user used tuple of int and choked ogr
+                fkey = [float(x) for x in key]
+                geom.SetPoint(0, *fkey)
+
+        return geom
+
+    # Create_feature with new optional attributes arg (should be dict type)
+    def create_feature(geometry, lyr, attributes=None):
+        feature = ogr.Feature(lyr.GetLayerDefn())
+        feature.SetGeometry(g)
+        if attributes is not None:
+            # Loop through attributes, assigning data to each field
+            for field, data in attributes.items():
+                feature.SetField(field, data)
+        lyr.CreateFeature(feature)
+        feature.Destroy()
+
+    # Conversion dict between python and ogr types
+    OGRTypes = {int: ogr.OFTInteger, str: ogr.OFTString, float: ogr.OFTReal}
+
+    # Check/add fields from attribute data to Shapefile layers
+    def add_fields_to_layer(key, value, fields, layer):
+        # Field not in previous edges so add to dict
+        if type(value) in OGRTypes:
+            fields[key] = OGRTypes[type(value)]
+        else:
+            # Data type not supported, default to string (char 80)
+            fields[key] = ogr.OFTString
+        # Create the new field
+        newfield = ogr.FieldDefn(key, fields[key])
+        layer.CreateField(newfield)
+
+    drv = ogr.GetDriverByName("ESRI Shapefile")
+    shpdir = drv.CreateDataSource(outdir)
+    # delete pre-existing output first otherwise ogr chokes
+    try:
+        shpdir.DeleteLayer("nodes")
+    except:
+        pass
+    nodes = shpdir.CreateLayer("nodes", None, ogr.wkbPoint)
+
+    # Storage for node field names and their data types
+    node_fields = {}
+
+    def create_attributes(data, fields, layer):
+        attributes = {}  # storage for attribute data (indexed by field names)
+        for key, value in data.items():
+            # Reject spatial data not required for attribute table
+            if key != "Json" and key != "Wkt" and key != "Wkb" and key != "ShpName":
+                # Check/add field and data type to fields dict
+                if key not in fields:
+                    add_fields_to_layer(key, value, fields, layer)
+                # Store the data from new field to dict for CreateLayer()
+                attributes[key] = value
+        return attributes, layer
+
+    for n in G:
+        data = G.nodes[n]
+        g = netgeometry(n, data)
+        attributes, nodes = create_attributes(data, node_fields, nodes)
+        create_feature(g, nodes, attributes)
+
+    try:
+        shpdir.DeleteLayer("edges")
+    except:
+        pass
+    edges = shpdir.CreateLayer("edges", None, ogr.wkbLineString)
+
+    # New edge attribute write support merged into edge loop
+    edge_fields = {}  # storage for field names and their data types
+
+    for e in G.edges(data=True):
+        data = G.get_edge_data(*e)
+        g = netgeometry(e, data)
+        attributes, edges = create_attributes(e[2], edge_fields, edges)
+        create_feature(g, edges, attributes)
+
+    nodes, edges = None, None