@@ -401,7 +401,7 @@ def organize_polygon_rings(rings, return_errors=None):
401
401
return polys
402
402
403
403
class Shape (object ):
404
- def __init__ (self , shapeType = NULL , points = None , parts = None , partTypes = None ):
404
+ def __init__ (self , shapeType = NULL , points = None , parts = None , partTypes = None , oid = None ):
405
405
"""Stores the geometry of the different shape types
406
406
specified in the Shapefile spec. Shape types are
407
407
usually point, polyline, or polygons. Every shape type
@@ -418,8 +418,15 @@ def __init__(self, shapeType=NULL, points=None, parts=None, partTypes=None):
418
418
self .parts = parts or []
419
419
if partTypes :
420
420
self .partTypes = partTypes
421
+
421
422
# and a dict to silently record any errors encountered
422
423
self ._errors = {}
424
+
425
+ # add oid
426
+ if oid is not None :
427
+ self .__oid = oid
428
+ else :
429
+ self .__oid = - 1
423
430
424
431
@property
425
432
def __geo_interface__ (self ):
@@ -504,17 +511,17 @@ def __geo_interface__(self):
504
511
# if VERBOSE is True, issue detailed warning about any shape errors
505
512
# encountered during the Shapefile to GeoJSON conversion
506
513
if VERBOSE and self ._errors :
507
- header = 'Possible issue encountered when converting Shape to GeoJSON: '
514
+ header = 'Possible issue encountered when converting Shape #{} to GeoJSON: ' . format ( self . oid )
508
515
orphans = self ._errors .get ('polygon_orphaned_holes' , None )
509
516
if orphans :
510
- msg = header + 'GeoJSON format requires that all polygon interior holes be contained by an exterior ring, \
517
+ msg = header + 'Shapefile format requires that all polygon interior holes be contained by an exterior ring, \
511
518
but the Shape contained interior holes (defined by counter-clockwise orientation in the shapefile format) that were \
512
519
orphaned, i.e. not contained by any exterior rings. The rings were still included but were \
513
520
encoded as GeoJSON exterior rings instead of holes.'
514
521
logging .warning (msg )
515
522
only_holes = self ._errors .get ('polygon_only_holes' , None )
516
523
if only_holes :
517
- msg = header + 'GeoJSON format requires that polygons contain at least one exterior ring, \
524
+ msg = header + 'Shapefile format requires that polygons contain at least one exterior ring, \
518
525
but the Shape was entirely made up of interior holes (defined by counter-clockwise orientation in the shapefile format). The rings were \
519
526
still included but were encoded as GeoJSON exterior rings instead of holes.'
520
527
logging .warning (msg )
@@ -610,10 +617,18 @@ def _from_geojson(geoj):
610
617
shape .parts = parts
611
618
return shape
612
619
620
+ @property
621
+ def oid (self ):
622
+ """The index position of the shape in the original shapefile"""
623
+ return self .__oid
624
+
613
625
@property
614
626
def shapeTypeName (self ):
615
627
return SHAPETYPE_LOOKUP [self .shapeType ]
616
628
629
+ def __repr__ (self ):
630
+ return 'Shape #{}: {}' .format (self .__oid , self .shapeTypeName )
631
+
617
632
class _Record (list ):
618
633
"""
619
634
A class to hold a record. Subclasses list to ensure compatibility with
@@ -1097,10 +1112,10 @@ def __shpHeader(self):
1097
1112
else :
1098
1113
self .mbox .append (None )
1099
1114
1100
- def __shape (self ):
1115
+ def __shape (self , oid = None ):
1101
1116
"""Returns the header info and geometry for a single shape."""
1102
1117
f = self .__getFileObj (self .shp )
1103
- record = Shape ()
1118
+ record = Shape (oid = oid )
1104
1119
nParts = nPoints = zmin = zmax = mmin = mmax = None
1105
1120
(recNum , recLength ) = unpack (">2i" , f .read (8 ))
1106
1121
# Determine the start of the next record
@@ -1205,7 +1220,7 @@ def shape(self, i=0):
1205
1220
if j == i :
1206
1221
return k
1207
1222
shp .seek (offset )
1208
- return self .__shape ()
1223
+ return self .__shape (oid = i )
1209
1224
1210
1225
def shapes (self ):
1211
1226
"""Returns all shapes in a shapefile."""
@@ -1219,7 +1234,8 @@ def shapes(self):
1219
1234
shp .seek (100 )
1220
1235
shapes = Shapes ()
1221
1236
while shp .tell () < self .shpLength :
1222
- shapes .append (self .__shape ())
1237
+ i = len (shapes )
1238
+ shapes .append (self .__shape (oid = i ))
1223
1239
return shapes
1224
1240
1225
1241
def iterShapes (self ):
@@ -1229,8 +1245,10 @@ def iterShapes(self):
1229
1245
shp .seek (0 ,2 )
1230
1246
self .shpLength = shp .tell ()
1231
1247
shp .seek (100 )
1248
+ i = 0
1232
1249
while shp .tell () < self .shpLength :
1233
- yield self .__shape ()
1250
+ yield self .__shape (oid = i )
1251
+ i += 1
1234
1252
1235
1253
def __dbfHeader (self ):
1236
1254
"""Reads a dbf header. Xbase-related code borrows heavily from ActiveState Python Cookbook Recipe 362715 by Raymond Hettinger"""
0 commit comments