88
99from __future__ import annotations
1010
11- __version__ = "3.0.10 "
11+ __version__ = "3.0.11 "
1212
1313import abc
1414import array
@@ -773,11 +773,56 @@ def __init__(
773773 self .shapeType = shapeType
774774
775775 if partTypes is not None :
776- self .partTypes = partTypes
776+ if self .shapeType != MULTIPATCH :
777+ raise ShapefileException (
778+ f"Only a Multipatch shape supports partTypes, not: { self .__class__ .__name__ } "
779+ f" (shape type: { self .shapeTypeName } ) "
780+ f"Got: { partTypes = } "
781+ )
782+ self .partTypes = _Array [int ]("i" , partTypes )
777783
778784 default_points : PointsT = []
779785 default_parts : list [int ] = []
780786
787+ if points and lines :
788+ raise ShapefileException (
789+ "Constructing meaningful Shapes unambiguously from both "
790+ "points and lines is not supported. Provide one only. "
791+ f" Got: { points = } and { lines = } "
792+ )
793+ elif not points and not lines :
794+ if self .shapeType != NULL :
795+ raise ShapefileException (
796+ f"Shape: { self .__class__ .__name__ } or shape type: { self .shapeTypeName } "
797+ "requires non-empty points or non-empty lines."
798+ f" Got: { points = } and { lines = } "
799+ )
800+ elif self .shapeType == NULL :
801+ raise ShapefileException (
802+ f"NullShape or shape type: { self .shapeTypeName } "
803+ "must have zero points and zero lines (or neither set, or both None). "
804+ f" Got: { points = } and { lines = } "
805+ )
806+ elif self .shapeType in Point_shapeTypes :
807+ if not points or len (points ) >= 2 :
808+ raise ShapefileException (
809+ f"Single point Shape: { self .__class__ .__name__ } , shape type: { self .shapeTypeName } "
810+ "requires one or points (and possibly a z co-ordinate and m value), not "
811+ f"lines. Got: { points = } and { lines = } "
812+ )
813+ if lines :
814+ raise ShapefileException (
815+ f"Single point shape: { self .__class__ .__name__ } , shape type: { self .shapeTypeName } "
816+ f"does not support lines. Got: { lines = } "
817+ )
818+ elif self .shapeType in MultiPoint_shapeTypes and lines and len (lines ) >= 2 :
819+ raise ShapefileException (
820+ f"Multipoint shape: { self .__class__ .__name__ } , shape type: { self .shapeTypeName } "
821+ f"is a single part shape, but was given multiple parts - got { lines = } . "
822+ "Point clouds can be constructed from a list of list points supplied to lines "
823+ "(instead of points) but only one single 'line' is supported. "
824+ )
825+
781826 if lines is not None :
782827 if self .shapeType in Polygon_shapeTypes :
783828 lines = list (lines )
@@ -800,20 +845,19 @@ def __init__(
800845 # _from_geojson.
801846 default_parts = [0 ]
802847
848+ # PyShp 2 API compatibility requires self.points = []
849+ # on NullShapes (and self.parts = []).
803850 self .points : PointsT = points or default_points
804-
805851 self .parts : Sequence [int ] = parts or default_parts
806852
807- # and a dict to silently record any errors encountered in GeoJSON
853+ # and a dict to record any captured errors encountered in GeoJSON
808854 self ._errors : dict [str , int ] = {}
809855
810856 # add oid
811857 self .__oid : int = - 1 if oid is None else oid
812858
813- if bbox is not None :
814- self .bbox : BBox = bbox
815- elif len (self .points ) >= 2 :
816- self .bbox = self ._bbox_from_points ()
859+ if self .shapeType != NULL and self .shapeType not in Point_shapeTypes :
860+ self .bbox : BBox = bbox or self ._bbox_from_points ()
817861
818862 ms_found = True
819863 if m :
@@ -829,9 +873,9 @@ def __init__(
829873
830874 zs_found = True
831875 if z :
832- self .z : Sequence [float ] = z
876+ self .z : Sequence [float ] = _Array [ float ]( "d" , z )
833877 elif self .shapeType in _HasZ_shapeTypes :
834- self .z = [ _z_from_point (p ) for p in self .points ]
878+ self .z = _Array [ float ]( "d" , ( _z_from_point (p ) for p in self .points ))
835879 elif self .shapeType == POINTZ :
836880 self .z = (_z_from_point (self .points [0 ]),)
837881 else :
@@ -847,6 +891,21 @@ def __init__(
847891 elif zs_found :
848892 self .zbox = self ._zbox_from_zs ()
849893
894+ @property
895+ def oid (self ) -> int :
896+ """The index position of the shape in the original shapefile"""
897+ return self .__oid
898+
899+ @property
900+ def shapeTypeName (self ) -> str :
901+ return SHAPETYPE_LOOKUP [self .shapeType ]
902+
903+ def __repr__ (self ) -> str :
904+ class_name = self .__class__ .__name__
905+ if class_name == "Shape" :
906+ return f"Shape #{ self .__oid } : { self .shapeTypeName } "
907+ return f"{ class_name } #{ self .__oid } "
908+
850909 @staticmethod
851910 def _ensure_polygon_rings_closed (
852911 parts : list [PointsT ], # Mutated
@@ -1080,21 +1139,6 @@ def _from_geojson(geoj: GeoJSONHomogeneousGeometryObject) -> Shape:
10801139 index += len (ext_or_hole )
10811140 return Shape (shapeType = shapeType , points = points , parts = parts )
10821141
1083- @property
1084- def oid (self ) -> int :
1085- """The index position of the shape in the original shapefile"""
1086- return self .__oid
1087-
1088- @property
1089- def shapeTypeName (self ) -> str :
1090- return SHAPETYPE_LOOKUP [self .shapeType ]
1091-
1092- def __repr__ (self ) -> str :
1093- class_name = self .__class__ .__name__
1094- if class_name == "Shape" :
1095- return f"Shape #{ self .__oid } : { self .shapeTypeName } "
1096- return f"{ class_name } #{ self .__oid } "
1097-
10981142
10991143# Need unused arguments to keep the same call signature for
11001144# different implementations of from_byte_stream and write_to_byte_stream
@@ -1116,6 +1160,11 @@ def from_byte_stream(
11161160 oid : int | None = None ,
11171161 bbox : BBox | None = None ,
11181162 ) -> NullShape :
1163+ """In the ESRI spec, Null shapes are defined in .shp files
1164+ entirely by a single integer encoding shape type 0
1165+ (this happens in ShpWriter._shp_record, amongst the shape
1166+ record header code).
1167+ """
11191168 # Shape.__init__ sets self.points = points or []
11201169 return NullShape (oid = oid )
11211170
@@ -1125,6 +1174,7 @@ def write_to_byte_stream(
11251174 s : Shape ,
11261175 i : int ,
11271176 ) -> int :
1177+ """No op (see above)."""
11281178 return 0
11291179
11301180
@@ -1602,13 +1652,13 @@ def _write_zs_to_byte_stream(
16021652 num_bytes_written = b_io .write (pack ("<2d" , * zbox ))
16031653 except StructError :
16041654 raise ShapefileException (
1605- f"Failed to write elevation extremes for record { i } . Expected floats."
1655+ f"Failed to write elevation extremes (ZBox) for record { i } . Expected floats."
16061656 )
16071657 try :
16081658 num_bytes_written += b_io .write (pack (f"<{ len (s .z )} d" , * s .z ))
16091659 except StructError :
16101660 raise ShapefileException (
1611- f"Failed to write elevation values for record { i } . Expected floats."
1661+ f"Failed to write elevation values (z) for record { i } . Expected floats."
16121662 )
16131663
16141664 return num_bytes_written
0 commit comments