Mercurial > repos > shellac > sam_consensus_v3
comparison 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 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4f3585e2f14b |
|---|---|
| 1 """ | |
| 2 ********* | |
| 3 Shapefile | |
| 4 ********* | |
| 5 | |
| 6 Generates a networkx.DiGraph from point and line shapefiles. | |
| 7 | |
| 8 "The Esri Shapefile or simply a shapefile is a popular geospatial vector | |
| 9 data format for geographic information systems software. It is developed | |
| 10 and regulated by Esri as a (mostly) open specification for data | |
| 11 interoperability among Esri and other software products." | |
| 12 See https://en.wikipedia.org/wiki/Shapefile for additional information. | |
| 13 """ | |
| 14 import networkx as nx | |
| 15 | |
| 16 __all__ = ["read_shp", "write_shp"] | |
| 17 | |
| 18 | |
| 19 def read_shp(path, simplify=True, geom_attrs=True, strict=True): | |
| 20 """Generates a networkx.DiGraph from shapefiles. Point geometries are | |
| 21 translated into nodes, lines into edges. Coordinate tuples are used as | |
| 22 keys. Attributes are preserved, line geometries are simplified into start | |
| 23 and end coordinates. Accepts a single shapefile or directory of many | |
| 24 shapefiles. | |
| 25 | |
| 26 "The Esri Shapefile or simply a shapefile is a popular geospatial vector | |
| 27 data format for geographic information systems software [1]_." | |
| 28 | |
| 29 Parameters | |
| 30 ---------- | |
| 31 path : file or string | |
| 32 File, directory, or filename to read. | |
| 33 | |
| 34 simplify: bool | |
| 35 If True, simplify line geometries to start and end coordinates. | |
| 36 If False, and line feature geometry has multiple segments, the | |
| 37 non-geometric attributes for that feature will be repeated for each | |
| 38 edge comprising that feature. | |
| 39 | |
| 40 geom_attrs: bool | |
| 41 If True, include the Wkb, Wkt and Json geometry attributes with | |
| 42 each edge. | |
| 43 | |
| 44 NOTE: if these attributes are available, write_shp will use them | |
| 45 to write the geometry. If nodes store the underlying coordinates for | |
| 46 the edge geometry as well (as they do when they are read via | |
| 47 this method) and they change, your geomety will be out of sync. | |
| 48 | |
| 49 strict: bool | |
| 50 If True, raise NetworkXError when feature geometry is missing or | |
| 51 GeometryType is not supported. | |
| 52 If False, silently ignore missing or unsupported geometry in features. | |
| 53 | |
| 54 Returns | |
| 55 ------- | |
| 56 G : NetworkX graph | |
| 57 | |
| 58 Raises | |
| 59 ------ | |
| 60 ImportError | |
| 61 If ogr module is not available. | |
| 62 | |
| 63 RuntimeError | |
| 64 If file cannot be open or read. | |
| 65 | |
| 66 NetworkXError | |
| 67 If strict=True and feature is missing geometry or GeometryType is | |
| 68 not supported. | |
| 69 | |
| 70 Examples | |
| 71 -------- | |
| 72 >>> G = nx.read_shp("test.shp") # doctest: +SKIP | |
| 73 | |
| 74 References | |
| 75 ---------- | |
| 76 .. [1] https://en.wikipedia.org/wiki/Shapefile | |
| 77 """ | |
| 78 try: | |
| 79 from osgeo import ogr | |
| 80 except ImportError as e: | |
| 81 raise ImportError("read_shp requires OGR: http://www.gdal.org/") from e | |
| 82 | |
| 83 if not isinstance(path, str): | |
| 84 return | |
| 85 | |
| 86 net = nx.DiGraph() | |
| 87 shp = ogr.Open(path) | |
| 88 if shp is None: | |
| 89 raise RuntimeError(f"Unable to open {path}") | |
| 90 for lyr in shp: | |
| 91 fields = [x.GetName() for x in lyr.schema] | |
| 92 for f in lyr: | |
| 93 g = f.geometry() | |
| 94 if g is None: | |
| 95 if strict: | |
| 96 raise nx.NetworkXError("Bad data: feature missing geometry") | |
| 97 else: | |
| 98 continue | |
| 99 flddata = [f.GetField(f.GetFieldIndex(x)) for x in fields] | |
| 100 attributes = dict(zip(fields, flddata)) | |
| 101 attributes["ShpName"] = lyr.GetName() | |
| 102 # Note: Using layer level geometry type | |
| 103 if g.GetGeometryType() == ogr.wkbPoint: | |
| 104 net.add_node((g.GetPoint_2D(0)), **attributes) | |
| 105 elif g.GetGeometryType() in (ogr.wkbLineString, ogr.wkbMultiLineString): | |
| 106 for edge in edges_from_line(g, attributes, simplify, geom_attrs): | |
| 107 e1, e2, attr = edge | |
| 108 net.add_edge(e1, e2) | |
| 109 net[e1][e2].update(attr) | |
| 110 else: | |
| 111 if strict: | |
| 112 raise nx.NetworkXError( | |
| 113 "GeometryType {} not supported".format(g.GetGeometryType()) | |
| 114 ) | |
| 115 | |
| 116 return net | |
| 117 | |
| 118 | |
| 119 def edges_from_line(geom, attrs, simplify=True, geom_attrs=True): | |
| 120 """ | |
| 121 Generate edges for each line in geom | |
| 122 Written as a helper for read_shp | |
| 123 | |
| 124 Parameters | |
| 125 ---------- | |
| 126 | |
| 127 geom: ogr line geometry | |
| 128 To be converted into an edge or edges | |
| 129 | |
| 130 attrs: dict | |
| 131 Attributes to be associated with all geoms | |
| 132 | |
| 133 simplify: bool | |
| 134 If True, simplify the line as in read_shp | |
| 135 | |
| 136 geom_attrs: bool | |
| 137 If True, add geom attributes to edge as in read_shp | |
| 138 | |
| 139 | |
| 140 Returns | |
| 141 ------- | |
| 142 edges: generator of edges | |
| 143 each edge is a tuple of form | |
| 144 (node1_coord, node2_coord, attribute_dict) | |
| 145 suitable for expanding into a networkx Graph add_edge call | |
| 146 """ | |
| 147 try: | |
| 148 from osgeo import ogr | |
| 149 except ImportError as e: | |
| 150 raise ImportError( | |
| 151 "edges_from_line requires OGR: " "http://www.gdal.org/" | |
| 152 ) from e | |
| 153 | |
| 154 if geom.GetGeometryType() == ogr.wkbLineString: | |
| 155 if simplify: | |
| 156 edge_attrs = attrs.copy() | |
| 157 last = geom.GetPointCount() - 1 | |
| 158 if geom_attrs: | |
| 159 edge_attrs["Wkb"] = geom.ExportToWkb() | |
| 160 edge_attrs["Wkt"] = geom.ExportToWkt() | |
| 161 edge_attrs["Json"] = geom.ExportToJson() | |
| 162 yield (geom.GetPoint_2D(0), geom.GetPoint_2D(last), edge_attrs) | |
| 163 else: | |
| 164 for i in range(0, geom.GetPointCount() - 1): | |
| 165 pt1 = geom.GetPoint_2D(i) | |
| 166 pt2 = geom.GetPoint_2D(i + 1) | |
| 167 edge_attrs = attrs.copy() | |
| 168 if geom_attrs: | |
| 169 segment = ogr.Geometry(ogr.wkbLineString) | |
| 170 segment.AddPoint_2D(pt1[0], pt1[1]) | |
| 171 segment.AddPoint_2D(pt2[0], pt2[1]) | |
| 172 edge_attrs["Wkb"] = segment.ExportToWkb() | |
| 173 edge_attrs["Wkt"] = segment.ExportToWkt() | |
| 174 edge_attrs["Json"] = segment.ExportToJson() | |
| 175 del segment | |
| 176 yield (pt1, pt2, edge_attrs) | |
| 177 | |
| 178 elif geom.GetGeometryType() == ogr.wkbMultiLineString: | |
| 179 for i in range(geom.GetGeometryCount()): | |
| 180 geom_i = geom.GetGeometryRef(i) | |
| 181 yield from edges_from_line(geom_i, attrs, simplify, geom_attrs) | |
| 182 | |
| 183 | |
| 184 def write_shp(G, outdir): | |
| 185 """Writes a networkx.DiGraph to two shapefiles, edges and nodes. | |
| 186 Nodes and edges are expected to have a Well Known Binary (Wkb) or | |
| 187 Well Known Text (Wkt) key in order to generate geometries. Also | |
| 188 acceptable are nodes with a numeric tuple key (x,y). | |
| 189 | |
| 190 "The Esri Shapefile or simply a shapefile is a popular geospatial vector | |
| 191 data format for geographic information systems software [1]_." | |
| 192 | |
| 193 Parameters | |
| 194 ---------- | |
| 195 outdir : directory path | |
| 196 Output directory for the two shapefiles. | |
| 197 | |
| 198 Returns | |
| 199 ------- | |
| 200 None | |
| 201 | |
| 202 Examples | |
| 203 -------- | |
| 204 nx.write_shp(digraph, '/shapefiles') # doctest +SKIP | |
| 205 | |
| 206 References | |
| 207 ---------- | |
| 208 .. [1] https://en.wikipedia.org/wiki/Shapefile | |
| 209 """ | |
| 210 try: | |
| 211 from osgeo import ogr | |
| 212 except ImportError as e: | |
| 213 raise ImportError("write_shp requires OGR: http://www.gdal.org/") from e | |
| 214 # easier to debug in python if ogr throws exceptions | |
| 215 ogr.UseExceptions() | |
| 216 | |
| 217 def netgeometry(key, data): | |
| 218 if "Wkb" in data: | |
| 219 geom = ogr.CreateGeometryFromWkb(data["Wkb"]) | |
| 220 elif "Wkt" in data: | |
| 221 geom = ogr.CreateGeometryFromWkt(data["Wkt"]) | |
| 222 elif type(key[0]).__name__ == "tuple": # edge keys are packed tuples | |
| 223 geom = ogr.Geometry(ogr.wkbLineString) | |
| 224 _from, _to = key[0], key[1] | |
| 225 try: | |
| 226 geom.SetPoint(0, *_from) | |
| 227 geom.SetPoint(1, *_to) | |
| 228 except TypeError: | |
| 229 # assume user used tuple of int and choked ogr | |
| 230 _ffrom = [float(x) for x in _from] | |
| 231 _fto = [float(x) for x in _to] | |
| 232 geom.SetPoint(0, *_ffrom) | |
| 233 geom.SetPoint(1, *_fto) | |
| 234 else: | |
| 235 geom = ogr.Geometry(ogr.wkbPoint) | |
| 236 try: | |
| 237 geom.SetPoint(0, *key) | |
| 238 except TypeError: | |
| 239 # assume user used tuple of int and choked ogr | |
| 240 fkey = [float(x) for x in key] | |
| 241 geom.SetPoint(0, *fkey) | |
| 242 | |
| 243 return geom | |
| 244 | |
| 245 # Create_feature with new optional attributes arg (should be dict type) | |
| 246 def create_feature(geometry, lyr, attributes=None): | |
| 247 feature = ogr.Feature(lyr.GetLayerDefn()) | |
| 248 feature.SetGeometry(g) | |
| 249 if attributes is not None: | |
| 250 # Loop through attributes, assigning data to each field | |
| 251 for field, data in attributes.items(): | |
| 252 feature.SetField(field, data) | |
| 253 lyr.CreateFeature(feature) | |
| 254 feature.Destroy() | |
| 255 | |
| 256 # Conversion dict between python and ogr types | |
| 257 OGRTypes = {int: ogr.OFTInteger, str: ogr.OFTString, float: ogr.OFTReal} | |
| 258 | |
| 259 # Check/add fields from attribute data to Shapefile layers | |
| 260 def add_fields_to_layer(key, value, fields, layer): | |
| 261 # Field not in previous edges so add to dict | |
| 262 if type(value) in OGRTypes: | |
| 263 fields[key] = OGRTypes[type(value)] | |
| 264 else: | |
| 265 # Data type not supported, default to string (char 80) | |
| 266 fields[key] = ogr.OFTString | |
| 267 # Create the new field | |
| 268 newfield = ogr.FieldDefn(key, fields[key]) | |
| 269 layer.CreateField(newfield) | |
| 270 | |
| 271 drv = ogr.GetDriverByName("ESRI Shapefile") | |
| 272 shpdir = drv.CreateDataSource(outdir) | |
| 273 # delete pre-existing output first otherwise ogr chokes | |
| 274 try: | |
| 275 shpdir.DeleteLayer("nodes") | |
| 276 except: | |
| 277 pass | |
| 278 nodes = shpdir.CreateLayer("nodes", None, ogr.wkbPoint) | |
| 279 | |
| 280 # Storage for node field names and their data types | |
| 281 node_fields = {} | |
| 282 | |
| 283 def create_attributes(data, fields, layer): | |
| 284 attributes = {} # storage for attribute data (indexed by field names) | |
| 285 for key, value in data.items(): | |
| 286 # Reject spatial data not required for attribute table | |
| 287 if key != "Json" and key != "Wkt" and key != "Wkb" and key != "ShpName": | |
| 288 # Check/add field and data type to fields dict | |
| 289 if key not in fields: | |
| 290 add_fields_to_layer(key, value, fields, layer) | |
| 291 # Store the data from new field to dict for CreateLayer() | |
| 292 attributes[key] = value | |
| 293 return attributes, layer | |
| 294 | |
| 295 for n in G: | |
| 296 data = G.nodes[n] | |
| 297 g = netgeometry(n, data) | |
| 298 attributes, nodes = create_attributes(data, node_fields, nodes) | |
| 299 create_feature(g, nodes, attributes) | |
| 300 | |
| 301 try: | |
| 302 shpdir.DeleteLayer("edges") | |
| 303 except: | |
| 304 pass | |
| 305 edges = shpdir.CreateLayer("edges", None, ogr.wkbLineString) | |
| 306 | |
| 307 # New edge attribute write support merged into edge loop | |
| 308 edge_fields = {} # storage for field names and their data types | |
| 309 | |
| 310 for e in G.edges(data=True): | |
| 311 data = G.get_edge_data(*e) | |
| 312 g = netgeometry(e, data) | |
| 313 attributes, edges = create_attributes(e[2], edge_fields, edges) | |
| 314 create_feature(g, edges, attributes) | |
| 315 | |
| 316 nodes, edges = None, None |
