comparison planemo/lib/python3.7/site-packages/networkx/readwrite/nx_shp.py @ 1:56ad4e20f292 draft

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