88
99from __future__ import annotations
1010
11- __version__ = "3.0.11 "
11+ __version__ = "3.0.12 "
1212
1313import abc
1414import array
@@ -133,7 +133,6 @@ class BBox(NamedTuple):
133133 ymin : float
134134 xmax : float
135135 ymax : float
136- # = tuple[float, float, float, float]
137136
138137
139138def _min_not_None (m1 : float | None , m2 : float | None ) -> float | None :
@@ -162,13 +161,10 @@ def expand(self, other: MBox) -> MBox:
162161 _max_not_None (self .mmax , other .mmax ),
163162 )
164163
165- # = tuple[float, float]
166-
167164
168165class ZBox (NamedTuple ):
169166 zmin : float
170167 zmax : float
171- # = tuple[float, float]
172168
173169
174170class WriteableBinStream (Protocol ):
@@ -720,6 +716,35 @@ def _z_from_point(point: PointT) -> float:
720716 return 0.0
721717
722718
719+ def _with_polygon_rings_closed (
720+ parts : Iterable [PointsT ],
721+ ) -> list [PointsT ]:
722+ return [part if part [0 ] == part [- 1 ] else part + [part [0 ]] for part in parts ]
723+
724+
725+ def _points_and_part_indices (
726+ parts : list [PointsT ],
727+ ) -> tuple [PointsT , list [int ]]:
728+ # Intended for Union[Polyline, Polygon, MultiPoint, MultiPatch]
729+ """From a list of parts (each part a list of points) return
730+ a flattened list of points, and a list of indexes into that
731+ flattened list corresponding to the start of each part.
732+
733+ Internal method for both multipoints (formed entirely by a single part),
734+ and shapes that have multiple collections of points (each one
735+ a part): (poly)lines, polygons, and multipatchs.
736+ """
737+ part_indexes : list [int ] = []
738+ points : PointsT = []
739+
740+ for part in parts :
741+ # set part index position
742+ part_indexes .append (len (points ))
743+ points .extend (part )
744+
745+ return points , part_indexes
746+
747+
723748class CanHaveBboxNoLinesKwargs (TypedDict , total = False ):
724749 oid : int | None
725750 points : PointsT | None
@@ -825,21 +850,17 @@ def __init__(
825850
826851 if lines is not None :
827852 if self .shapeType in Polygon_shapeTypes :
828- lines = list (lines )
829- self ._ensure_polygon_rings_closed (lines )
853+ lines = _with_polygon_rings_closed (lines )
830854
831- default_points , default_parts = self ._points_and_parts_indexes_from_lines (
832- lines
833- )
834- elif points and self .shapeType in _CanHaveBBox_shapeTypes :
855+ default_points , default_parts = _points_and_part_indices (lines )
856+
857+ elif not parts and self .shapeType in _CanHaveBBox_shapeTypes :
835858 # TODO: Raise issue.
836859 # This ensures Polylines, Polygons and Multipatches with no part information are a single
837860 # Polyline, Polygon or Multipatch respectively.
838861 #
839- # However this also allows MultiPoints shapes to have a single part index 0 as
840- # documented in README.md,also when set from points
841- # (even though this is just an artefact of initialising them as a length-1 nested
842- # list of points via _points_and_parts_indexes_from_lines).
862+ # This is consistent with MultiPoints shapes having single part index 0 as
863+ # documented in README.md, also when set from points
843864 #
844865 # Alternatively single points could be given parts = [0] too, as they do if formed
845866 # _from_geojson.
@@ -848,7 +869,7 @@ def __init__(
848869 # PyShp 2 API compatibility requires self.points = []
849870 # on NullShapes (and self.parts = []).
850871 self .points : PointsT = points or default_points
851- self .parts : Sequence [int ] = parts or default_parts
872+ self .parts = _Array [int ]( "i" , parts or default_parts )
852873
853874 # and a dict to record any captured errors encountered in GeoJSON
854875 self ._errors : dict [str , int ] = {}
@@ -900,43 +921,23 @@ def oid(self) -> int:
900921 def shapeTypeName (self ) -> str :
901922 return SHAPETYPE_LOOKUP [self .shapeType ]
902923
924+ @property
925+ def points_2D (self ) -> list [Point2D ]:
926+ return [(x , y ) for (x , y , * _rest ) in self .points ]
927+
928+ @property
929+ def points_3D (self ) -> list [Point3D ]:
930+ zs = getattr (self , "z" , None )
931+ if zs is None :
932+ return [(x , y , _z_from_point ((x , y ))) for (x , y , * _rest ) in self .points ]
933+ return [(x , y , z ) for (x , y , * _rest ), z in zip (self .points , zs )]
934+
903935 def __repr__ (self ) -> str :
904936 class_name = self .__class__ .__name__
905937 if class_name == "Shape" :
906938 return f"Shape #{ self .__oid } : { self .shapeTypeName } "
907939 return f"{ class_name } #{ self .__oid } "
908940
909- @staticmethod
910- def _ensure_polygon_rings_closed (
911- parts : list [PointsT ], # Mutated
912- ) -> None :
913- for part in parts :
914- if part [0 ] != part [- 1 ]:
915- part .append (part [0 ])
916-
917- @staticmethod
918- def _points_and_parts_indexes_from_lines (
919- parts : list [PointsT ],
920- ) -> tuple [PointsT , list [int ]]:
921- # Intended for Union[Polyline, Polygon, MultiPoint, MultiPatch]
922- """From a list of parts (each part a list of points) return
923- a flattened list of points, and a list of indexes into that
924- flattened list corresponding to the start of each part.
925-
926- Internal method for both multipoints (formed entirely by a single part),
927- and shapes that have multiple collections of points (each one
928- a part): (poly)lines, polygons, and multipatchs.
929- """
930- part_indexes : list [int ] = []
931- points : PointsT = []
932-
933- for part in parts :
934- # set part index position
935- part_indexes .append (len (points ))
936- points .extend (part )
937-
938- return points , part_indexes
939-
940941 def _bbox_from_points (self ) -> BBox :
941942 xs : list [float ] = []
942943 ys : list [float ] = []
0 commit comments