22shapefile.py
33Provides read and write support for ESRI Shapefiles.
44author: jlawhead<at>geospatialpython.com
5- date: 20130622
6- version: 1.1.7
5+ date: 20130727
6+ version: 1.2.0
77Compatible with Python versions 2.4-3.x
88"""
9- __version__ = "1.1.7"
9+
10+ __version__ = "1.2.0"
1011
1112from struct import pack , unpack , calcsize , error
1213import os
1314import sys
1415import time
1516import array
1617import tempfile
18+
1719#
1820# Constants for shape types
21+ default_encoding = 'utf-8'
1922NULL = 0
2023POINT = 1
2124POLYLINE = 3
@@ -40,7 +43,7 @@ def b(v):
4043 if PYTHON3 :
4144 if isinstance (v , str ):
4245 # For python 3 encode str to bytes.
43- return v .encode ('utf-8' )
46+ return v .encode (default_encoding )
4447 elif isinstance (v , bytes ):
4548 # Already bytes.
4649 return v
@@ -55,7 +58,7 @@ def u(v):
5558 if PYTHON3 :
5659 if isinstance (v , bytes ):
5760 # For python 3 decode bytes to str.
58- return v .decode ('utf-8' )
61+ return v .decode (default_encoding )
5962 elif isinstance (v , str ):
6063 # Already str.
6164 return v
@@ -80,7 +83,7 @@ def __repr__(self):
8083
8184def signed_area (coords ):
8285 """Return the signed area enclosed by a ring using the linear time
83- algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value < = 0
86+ algorithm at http://www.cgafaq.info/wiki/Polygon_Area. A value > = 0
8487 indicates a counter-clockwise oriented ring.
8588 """
8689 xs , ys = map (list , zip (* coords ))
@@ -232,7 +235,7 @@ def __init__(self, *args, **kwargs):
232235 self .dbf = kwargs ["dbf" ]
233236 if hasattr (self .dbf , "seek" ):
234237 self .dbf .seek (0 )
235- if self .shp or self .dbf :
238+ if self .shp or self .dbf :
236239 self .load ()
237240 else :
238241 raise ShapefileException ("Shapefile Reader requires a shapefile or file-like object." )
@@ -357,7 +360,7 @@ def __shape(self):
357360 record .m = unpack ("<d" , f .read (8 ))
358361 # Seek to the end of this record as defined by the record header because
359362 # the shapefile spec doesn't require the actual content to meet the header
360- # definition. Probably allowed for lazy feature deletion.
363+ # definition. Probably allowed for lazy feature deletion.
361364 f .seek (next )
362365 return record
363366
@@ -398,6 +401,12 @@ def shape(self, i=0):
398401 def shapes (self ):
399402 """Returns all shapes in a shapefile."""
400403 shp = self .__getFileObj (self .shp )
404+ # Found shapefiles which report incorrect
405+ # shp file length in the header. Can't trust
406+ # that so we seek to the end of the file
407+ # and figure it out.
408+ shp .seek (0 ,2 )
409+ self .shpLength = shp .tell ()
401410 shp .seek (100 )
402411 shapes = []
403412 while shp .tell () < self .shpLength :
@@ -408,9 +417,11 @@ def iterShapes(self):
408417 """Serves up shapes in a shapefile as an iterator. Useful
409418 for handling large shapefiles."""
410419 shp = self .__getFileObj (self .shp )
420+ shp .seek (0 ,2 )
421+ self .shpLength = shp .tell ()
411422 shp .seek (100 )
412423 while shp .tell () < self .shpLength :
413- yield self .__shape ()
424+ yield self .__shape ()
414425
415426 def __dbfHeaderLength (self ):
416427 """Retrieves the header length of a dbf file header."""
@@ -751,6 +762,8 @@ def __shpRecords(self):
751762 recNum += 1
752763 start = f .tell ()
753764 # Shape Type
765+ if self .shapeType != 31 :
766+ s .shapeType = self .shapeType
754767 f .write (pack ("<i" , s .shapeType ))
755768 # All shape types capable of having a bounding box
756769 if s .shapeType in (3 ,5 ,8 ,13 ,15 ,18 ,23 ,25 ,28 ,31 ):
@@ -787,14 +800,19 @@ def __shpRecords(self):
787800 except error :
788801 raise ShapefileException ("Failed to write elevation extremes for record %s. Expected floats." % recNum )
789802 try :
790- #[f.write(pack("<d", p[2])) for p in s.points]
791- f .write (pack ("<%sd" % len (s .z ), * s .z ))
803+ if hasattr (s ,"z" ):
804+ f .write (pack ("<%sd" % len (s .z ), * s .z ))
805+ else :
806+ [f .write (pack ("<d" , p [2 ])) for p in s .points ]
792807 except error :
793808 raise ShapefileException ("Failed to write elevation values for record %s. Expected floats." % recNum )
794809 # Write m extremes and values
795- if s .shapeType in (23 ,25 ,31 ):
810+ if s .shapeType in (13 , 15 , 18 , 23 ,25 , 28 ,31 ):
796811 try :
797- f .write (pack ("<2d" , * self .__mbox ([s ])))
812+ if hasattr (s ,"m" ):
813+ f .write (pack ("<%sd" % len (s .m ), * s .m ))
814+ else :
815+ f .write (pack ("<2d" , * self .__mbox ([s ])))
798816 except error :
799817 raise ShapefileException ("Failed to write measure extremes for record %s. Expected floats" % recNum )
800818 try :
@@ -809,16 +827,36 @@ def __shpRecords(self):
809827 raise ShapefileException ("Failed to write point for record %s. Expected floats." % recNum )
810828 # Write a single Z value
811829 if s .shapeType == 11 :
812- try :
813- f .write (pack ("<1d" , s .points [0 ][2 ]))
814- except error :
815- raise ShapefileException ("Failed to write elevation value for record %s. Expected floats." % recNum )
830+ if hasattr (s , "z" ):
831+ try :
832+ if not s .z :
833+ s .z = (0 ,)
834+ f .write (pack ("<d" , s .z [0 ]))
835+ except error :
836+ raise ShapefileException ("Failed to write elevation value for record %s. Expected floats." % recNum )
837+ else :
838+ try :
839+ if len (s .points [0 ])< 3 :
840+ s .points [0 ].append (0 )
841+ f .write (pack ("<d" , s .points [0 ][2 ]))
842+ except error :
843+ raise ShapefileException ("Failed to write elevation value for record %s. Expected floats." % recNum )
816844 # Write a single M value
817845 if s .shapeType in (11 ,21 ):
818- try :
819- f .write (pack ("<1d" , s .points [0 ][3 ]))
820- except error :
821- raise ShapefileException ("Failed to write measure value for record %s. Expected floats." % recNum )
846+ if hasattr (s , "m" ):
847+ try :
848+ if not s .m :
849+ s .m = (0 ,)
850+ f .write (pack ("<1d" , s .m [0 ]))
851+ except error :
852+ raise ShapefileException ("Failed to write measure value for record %s. Expected floats." % recNum )
853+ else :
854+ try :
855+ if len (s .points [0 ])< 4 :
856+ s .points [0 ].append (0 )
857+ f .write (pack ("<1d" , s .points [0 ][3 ]))
858+ except error :
859+ raise ShapefileException ("Failed to write measure value for record %s. Expected floats." % recNum )
822860 # Finalize record length as 16-bit words
823861 finish = f .tell ()
824862 length = (finish - start ) // 2
@@ -880,10 +918,13 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
880918 polyShape = _Shape (shapeType )
881919 polyShape .parts = []
882920 polyShape .points = []
921+ # Make sure polygons are closed
922+ if shapeType in (5 ,15 ,25 ,31 ):
923+ for part in parts :
924+ if part [0 ] != part [- 1 ]:
925+ part .append (part [0 ])
883926 for part in parts :
884- # Make sure polygon is closed
885- if shapeType in (5 ,15 ,25 ,31 ) and part [0 ] != part [- 1 ]:
886- part .append (part [0 ])
927+ polyShape .parts .append (len (polyShape .points ))
887928 for point in part :
888929 # Ensure point is list
889930 if not isinstance (point , list ):
@@ -892,7 +933,6 @@ def poly(self, parts=[], shapeType=POLYGON, partTypes=[]):
892933 while len (point ) < 4 :
893934 point .append (0 )
894935 polyShape .points .append (point )
895- polyShape .parts .append (len (polyShape .points ))
896936 if polyShape .shapeType == 31 :
897937 if not partTypes :
898938 for part in parts :
@@ -970,8 +1010,8 @@ def save(self, target=None, shp=None, shx=None, dbf=None):
9701010 be written exclusively using saveShp, saveShx, and saveDbf respectively.
9711011 If target is specified but not shp,shx, or dbf then the target path and
9721012 file name are used. If no options or specified, a unique base file name
973- is generated to save the files and the base file name is returned as a
974- string.
1013+ is generated to save the files and the base file name is returned as a
1014+ string.
9751015 """
9761016 # Create a unique file name if one is not defined
9771017 if shp :
@@ -985,7 +1025,7 @@ def save(self, target=None, shp=None, shx=None, dbf=None):
9851025 if not target :
9861026 temp = tempfile .NamedTemporaryFile (prefix = "shapefile_" ,dir = os .getcwd ())
9871027 target = temp .name
988- generated = True
1028+ generated = True
9891029 self .saveShp (target )
9901030 self .shp .close ()
9911031 self .saveShx (target )
@@ -994,7 +1034,6 @@ def save(self, target=None, shp=None, shx=None, dbf=None):
9941034 self .dbf .close ()
9951035 if generated :
9961036 return target
997-
9981037class Editor (Writer ):
9991038 def __init__ (self , shapefile = None , shapeType = POINT , autoBalance = 1 ):
10001039 self .autoBalance = autoBalance
0 commit comments