@@ -844,111 +844,87 @@ def shapeTypeName(self) -> str:
844844 def __repr__ (self ):
845845 return f"Shape #{ self .__oid } : { self .shapeTypeName } "
846846
847+ # pylint: disable=unused-argument
848+ def _get_and_set_bbox_from_shp_file (self , f ):
849+ return None
850+
851+ @staticmethod
852+ def _get_nparts_from_shp_file (f ):
853+ return None
854+
855+ @staticmethod
856+ def _get_npoints_from_shp_file (f ):
857+ return None
858+
859+ def _set_parts_from_shp_file (self , f , nParts ):
860+ pass
861+
862+ def _set_part_types_from_shp_file (self , f , nParts ):
863+ pass
864+
865+ def _set_points_from_shp_file (self , f , nPoints ):
866+ pass
867+
868+ def _set_z_from_shp_file (self , f , nPoints ):
869+ pass
870+
871+ def _set_m_from_shp_file (self , f , nPoints , next_shape ):
872+ pass
873+
874+ def _get_and_set_2D_point_from_shp_file (self , f ):
875+ return None
876+
877+ def _set_single_point_z_from_shp_file (self , f ):
878+ pass
879+
880+ def _set_single_point_m_from_shp_file (self , f , next_shape ):
881+ pass
882+
883+ # pylint: enable=unused-argument
884+
847885 @classmethod
848886 def _from_shp_file (cls , f , next_shape , oid = None , bbox = None ):
849- # Previously, we also set __zmin = __zmax = __mmin = __mmax = None
850- nParts : Optional [int ] = None
851- nPoints : Optional [int ] = None
852-
853887 shape = cls (oid = oid )
854888
855- # All shape types capable of having a bounding box
856- # elif shapeType in (3, 13, 23, 5, 15, 25, 8, 18, 28, 31):
857- if isinstance (shape , _CanHaveBBox ):
858- # record.bbox = tuple(_Array[float]("d", unpack("<4d", f.read(32))))
859- shape .bbox = _Array [float ]("d" , unpack ("<4d" , f .read (32 )))
860- # if bbox specified and no overlap, skip this shape
861- if bbox is not None and not bbox_overlap (bbox , tuple (shape .bbox )):
862- # because we stop parsing this shape, skip to beginning of
863- # next shape before we return
864- return None
865- # Shape types with parts
866- # if shapeType in (3, 13, 23, 5, 15, 25, 31):
867- if issubclass (cls , (Polyline , Polygon , MultiPatch )):
868- nParts = unpack ("<i" , f .read (4 ))[0 ]
889+ bbox = shape ._get_and_set_bbox_from_shp_file (f ) # pylint: disable=assignment-from-none
869890
870- # Shape types with points
871- # if shapeType in (3, 13, 23, 5, 15, 25, 8, 18, 28, 31):
872- if isinstance ( shape , _CanHaveBBox ):
873- nPoints = unpack ( "<i" , f . read ( 4 ))[ 0 ]
874- # Read points - produces a list of [x,y] values
891+ # if bbox specified and no overlap, skip this shape
892+ if bbox is not None and not bbox_overlap ( bbox , tuple ( shape . bbox )): # pylint: disable=no-member
893+ # because we stop parsing this shape, skip to beginning of
894+ # next shape before we return
895+ return None
875896
876- if nParts :
877- shape .parts = _Array [int ]("i" , unpack (f"<{ nParts } i" , f .read (nParts * 4 )))
897+ nParts : Optional [int ] = shape ._get_nparts_from_shp_file (f )
898+ nPoints : Optional [int ] = shape ._get_npoints_from_shp_file (f )
899+ # Previously, we also set __zmin = __zmax = __mmin = __mmax = None
878900
879- # Read part types for Multipatch - 31
880- # if shapeType == 31:
881- if cls is MultiPatch :
882- shape .partTypes = _Array [int ](
883- "i" , unpack (f"<{ nParts } i" , f .read (nParts * 4 ))
884- )
901+ if nParts :
902+ shape ._set_parts_from_shp_file (f , nParts )
903+ shape ._set_part_types_from_shp_file (f , nParts )
885904
886905 if nPoints :
887- flat = unpack (f"<{ 2 * nPoints } d" , f .read (16 * nPoints ))
888- shape .points = list (zip (* (iter (flat ),) * 2 ))
889-
890- # Read z extremes and values
891- # if shapeType in (13, 15, 18, 31):
892- if isinstance (shape , _HasZ ):
893- __zmin , __zmax = unpack ("<2d" , f .read (16 ))
894- shape .z = _Array [float ](
895- "d" , unpack (f"<{ nPoints } d" , f .read (nPoints * 8 ))
896- )
906+ shape ._set_points_from_shp_file (f , nPoints )
897907
898- # Read m extremes and values
899- # if shapeType in (13, 23, 15, 25, 18, 28, 31):
900- if isinstance (shape , _HasM ):
901- if next_shape - f .tell () >= 16 :
902- __mmin , __mmax = unpack ("<2d" , f .read (16 ))
903- # Measure values less than -10e38 are nodata values according to the spec
904- if next_shape - f .tell () >= nPoints * 8 :
905- shape .m = []
906- for m in _Array [float ](
907- "d" , unpack (f"<{ nPoints } d" , f .read (nPoints * 8 ))
908- ):
909- if m > NODATA :
910- shape .m .append (m )
911- else :
912- shape .m .append (None )
913- else :
914- shape .m = [None for _ in range (nPoints )]
908+ shape ._set_z_from_shp_file (f , nPoints )
909+
910+ shape ._set_m_from_shp_file (f , nPoints , next_shape )
915911
916912 # Read a single point
917913 # if shapeType in (1, 11, 21):
918- if cls is Point :
919- x , y = _Array [float ]("d" , unpack ("<2d" , f .read (16 )))
920-
921- shape .points = [(x , y )]
922- if bbox is not None :
923- # create bounding box for Point by duplicating coordinates
924- # skip shape if no overlap with bounding box
925- if not bbox_overlap (bbox , (x , y , x , y )):
926- return None
927-
928- # Read a single Z value
929- # if shapeType == 11:
930- if cls is PointZ :
931- shape .z = tuple (unpack ("<d" , f .read (8 )))
932-
933- # Read a single M value
934- # if shapeType in (21, 11):
935- if cls is PointM :
936- if next_shape - f .tell () >= 8 :
937- (m ,) = unpack ("<d" , f .read (8 ))
938- else :
939- m = NODATA
940- # Measure values less than -10e38 are nodata values according to the spec
941- if m > NODATA :
942- shape .m = (m ,)
943- else :
944- shape .m = (None ,)
914+ point_2D = shape ._get_and_set_2D_point_from_shp_file (f ) # pylint: disable=assignment-from-none
945915
946- return shape
916+ if bbox is not None and point_2D is not None :
917+ x , y = point_2D # pylint: disable=unpacking-non-sequence
918+ # create bounding box for Point by duplicating coordinates
919+ # skip shape if no overlap with bounding box
920+ if not bbox_overlap (bbox , (x , y , x , y )):
921+ return None
947922
948- # pylint: enable=attribute-defined-outside-init
949- # Seek to the end of this record as defined by the record header because
950- # the shapefile spec doesn't require the actual content to meet the header
951- # definition. Probably allowed for lazy feature deletion.
923+ shape ._set_single_point_z_from_shp_file (f )
924+
925+ shape ._set_single_point_m_from_shp_file (f , next_shape )
926+
927+ return shape
952928
953929
954930def _read_shape_from_shp_file (
@@ -967,6 +943,9 @@ def _read_shape_from_shp_file(
967943 ShapeClass = SHAPE_CLASS_FROM_SHAPETYPE [shapeType ]
968944 shape = ShapeClass ._from_shp_file (f , next_shape , oid = oid , bbox = bbox )
969945
946+ # Seek to the end of this record as defined by the record header because
947+ # the shapefile spec doesn't require the actual content to meet the header
948+ # definition. Probably allowed for lazy feature deletion.
970949 f .seek (next_shape )
971950
972951 return shape
@@ -983,16 +962,43 @@ class _CanHaveBBox(Shape):
983962 # Not a BBox because the legacy implementation was a list, not a 4-tuple.
984963 bbox : Optional [Sequence [float ]] = None
985964
965+ def _get_and_set_bbox_from_shp_file (self , f ):
966+ # record.bbox = tuple(_Array[float]("d", unpack("<4d", f.read(32))))
967+ self .bbox = _Array [float ]("d" , unpack ("<4d" , f .read (32 )))
968+ return self .bbox
969+
970+ @staticmethod
971+ def _get_npoints_from_shp_file (f ):
972+ return unpack ("<i" , f .read (4 ))[0 ]
973+
974+ def _set_points_from_shp_file (self , f , nPoints ):
975+ flat = unpack (f"<{ 2 * nPoints } d" , f .read (16 * nPoints ))
976+ self .points = list (zip (* (iter (flat ),) * 2 ))
977+
978+
979+ class _CanHaveParts (_CanHaveBBox ):
980+ @staticmethod
981+ def _get_nparts_from_shp_file (f ):
982+ return unpack ("<i" , f .read (4 ))[0 ]
983+
984+ def _set_parts_from_shp_file (self , f , nParts ):
985+ self .parts = _Array [int ]("i" , unpack (f"<{ nParts } i" , f .read (nParts * 4 )))
986+
986987
987988class Point (Shape ):
988989 shapeType = POINT
989990
991+ def _get_and_set_2D_point_from_shp_file (self , f ):
992+ x , y = _Array [float ]("d" , unpack ("<2d" , f .read (16 )))
993+
994+ self .points = [(x , y )]
995+
990996
991- class Polyline (_CanHaveBBox ):
997+ class Polyline (_CanHaveParts ):
992998 shapeType = POLYLINE
993999
9941000
995- class Polygon (_CanHaveBBox ):
1001+ class Polygon (_CanHaveParts ):
9961002 shapeType = POLYGON
9971003
9981004
@@ -1003,21 +1009,53 @@ class MultiPoint(_CanHaveBBox):
10031009class _HasM (Shape ):
10041010 m : Sequence [Optional [float ]]
10051011
1012+ def _set_m_from_shp_file (self , f , nPoints , next_shape ):
1013+ if next_shape - f .tell () >= 16 :
1014+ __mmin , __mmax = unpack ("<2d" , f .read (16 ))
1015+ # Measure values less than -10e38 are nodata values according to the spec
1016+ if next_shape - f .tell () >= nPoints * 8 :
1017+ self .m = []
1018+ for m in _Array [float ]("d" , unpack (f"<{ nPoints } d" , f .read (nPoints * 8 ))):
1019+ if m > NODATA :
1020+ self .m .append (m )
1021+ else :
1022+ self .m .append (None )
1023+ else :
1024+ self .m = [None for _ in range (nPoints )]
1025+
10061026
10071027class _HasZ (Shape ):
10081028 z : Sequence [float ]
10091029
1030+ def _set_z_from_shp_file (self , f , nPoints ):
1031+ __zmin , __zmax = unpack ("<2d" , f .read (16 )) # pylint: disable=unused-private-member
1032+ self .z = _Array [float ]("d" , unpack (f"<{ nPoints } d" , f .read (nPoints * 8 )))
1033+
10101034
1011- class MultiPatch (_HasM , _HasZ , _CanHaveBBox ):
1035+ class MultiPatch (_HasM , _HasZ , _CanHaveParts ):
10121036 shapeType = MULTIPATCH
10131037
1038+ def _set_part_types_from_shp_file (self , f , nParts ):
1039+ self .partTypes = _Array [int ]("i" , unpack (f"<{ nParts } i" , f .read (nParts * 4 )))
1040+
10141041
10151042class PointM (Point , _HasM ):
10161043 shapeType = POINTM
10171044 # same default as in Writer.__shpRecord (if s.shapeType in (11, 21):)
10181045 # PyShp encodes None m values as NODATA
10191046 m = (None ,)
10201047
1048+ def _set_single_point_m_from_shp_file (self , f , next_shape ):
1049+ if next_shape - f .tell () >= 8 :
1050+ (m ,) = unpack ("<d" , f .read (8 ))
1051+ else :
1052+ m = NODATA
1053+ # Measure values less than -10e38 are nodata values according to the spec
1054+ if m > NODATA :
1055+ self .m = (m ,)
1056+ else :
1057+ self .m = (None ,)
1058+
10211059
10221060class PolylineM (Polyline , _HasM ):
10231061 shapeType = POLYLINEM
@@ -1036,6 +1074,9 @@ class PointZ(PointM, _HasZ):
10361074 # same default as in Writer.__shpRecord (if s.shapeType == 11:)
10371075 z : Sequence [float ] = (0.0 ,)
10381076
1077+ def _set_single_point_z_from_shp_file (self , f ):
1078+ self .z = tuple (unpack ("<d" , f .read (8 )))
1079+
10391080
10401081class PolylineZ (PolylineM , _HasZ ):
10411082 shapeType = POLYLINEZ
@@ -1735,7 +1776,6 @@ def __shpHeader(self):
17351776 "Shapefile Reader requires a shapefile or file-like object. (no shp file found"
17361777 )
17371778
1738- # pylint: disable=attribute-defined-outside-init
17391779 shp = self .shp
17401780 # File length (16-bit word * 2 = bytes)
17411781 shp .seek (24 )
@@ -1756,14 +1796,11 @@ def __shpHeader(self):
17561796 else :
17571797 self .mbox .append (None )
17581798
1759- # pylint: enable=attribute-defined-outside-init
1760-
17611799 def __shape (
17621800 self , oid : Optional [int ] = None , bbox : Optional [BBox ] = None
17631801 ) -> Optional [Shape ]:
17641802 """Returns the header info and geometry for a single shape."""
17651803
1766- # pylint: disable=attribute-defined-outside-init
17671804 f = self .__getFileObj (self .shp )
17681805
17691806 shape = _read_shape_from_shp_file (f , oid , bbox )
@@ -1901,7 +1938,6 @@ def iterShapes(self, bbox: Optional[BBox] = None) -> Iterator[Optional[Shape]]:
19011938 def __dbfHeader (self ):
19021939 """Reads a dbf header. Xbase-related code borrows heavily from ActiveState Python Cookbook Recipe 362715 by Raymond Hettinger"""
19031940
1904- # pylint: disable=attribute-defined-outside-init
19051941 if not self .dbf :
19061942 raise ShapefileException (
19071943 "Shapefile Reader requires a shapefile or file-like object. (no dbf file found)"
@@ -1948,8 +1984,6 @@ def __dbfHeader(self):
19481984 self .__fullRecStruct = recStruct
19491985 self .__fullRecLookup = recLookup
19501986
1951- # pylint: enable=attribute-defined-outside-init
1952-
19531987 def __recordFmt (self , fields : Optional [Container [str ]] = None ) -> tuple [str , int ]:
19541988 """Calculates the format and size of a .dbf record. Optional 'fields' arg
19551989 specifies which fieldnames to unpack and which to ignore. Note that this
0 commit comments