11from dataclasses import dataclass
22from enum import Enum
33from pathlib import Path
4- from typing import Any , Optional
4+ from typing import Any , Iterable , Optional
55
66import geopandas as gpd
77import numpy as np
1111from shapely .geometry .base import BaseGeometry
1212
1313from siapy .core .exceptions import ConfigurationError , InvalidFilepathError , InvalidInputError , InvalidTypeError
14- from siapy .entities .pixels import PixelCoordinate , Pixels
14+ from siapy .entities .pixels import CoordinateInput , PixelCoordinate , Pixels , validate_pixel_input
1515
1616__all__ = [
1717 "Shape" ,
@@ -72,6 +72,13 @@ def __repr__(self) -> str:
7272 def __len__ (self ) -> int :
7373 return len (self .df )
7474
75+ def __array__ (self , dtype : np .dtype | None = None ) -> NDArray [np .floating [Any ]]:
76+ """Convert this shape object to a numpy array when requested by NumPy."""
77+ array = self .to_numpy ()
78+ if dtype is not None :
79+ return array .astype (dtype )
80+ return array
81+
7582 @classmethod
7683 def open_shapefile (cls , filepath : str | Path , label : str = "" ) -> "Shape" :
7784 filepath = Path (filepath )
@@ -108,30 +115,44 @@ def from_point(cls, x: float, y: float, label: str = "") -> "Shape":
108115 return cls (geometry = Point (x , y ), label = label )
109116
110117 @classmethod
111- def from_multipoint (cls , points : "Pixels" , label : str = "" ) -> "Shape" :
118+ def from_multipoint (cls , points : Pixels | pd .DataFrame | Iterable [CoordinateInput ], label : str = "" ) -> "Shape" :
119+ points = validate_pixel_input (points )
112120 if len (points ) < 1 :
113121 raise ConfigurationError ("At least one point is required" )
114122 coords = points .to_list ()
115123 return cls (geometry = MultiPoint (coords ), label = label )
116124
117125 @classmethod
118- def from_line (cls , pixels : Pixels , label : str = "" ) -> "Shape" :
126+ def from_line (cls , pixels : Pixels | pd .DataFrame | Iterable [CoordinateInput ], label : str = "" ) -> "Shape" :
127+ pixels = validate_pixel_input (pixels )
119128 if len (pixels ) < 2 :
120129 raise ConfigurationError ("At least two points are required for a line" )
121130
122131 return cls (geometry = LineString (pixels .to_list ()), label = label )
123132
124133 @classmethod
125- def from_multiline (cls , line_segments : list [Pixels ], label : str = "" ) -> "Shape" :
134+ def from_multiline (
135+ cls , line_segments : list [Pixels | pd .DataFrame | Iterable [CoordinateInput ]], label : str = ""
136+ ) -> "Shape" :
126137 if not line_segments :
127138 raise ConfigurationError ("At least one line segment is required" )
128139
129- lines = [LineString (segment .to_list ()) for segment in line_segments ]
140+ lines = []
141+ for segment in line_segments :
142+ validated_segment = validate_pixel_input (segment )
143+ lines .append (LineString (validated_segment .to_list ()))
144+
130145 multi_line = MultiLineString (lines )
131146 return cls (geometry = multi_line , label = label )
132147
133148 @classmethod
134- def from_polygon (cls , exterior : Pixels , holes : Optional [list [Pixels ]] = None , label : str = "" ) -> "Shape" :
149+ def from_polygon (
150+ cls ,
151+ exterior : Pixels | pd .DataFrame | Iterable [CoordinateInput ],
152+ holes : Optional [list [Pixels | pd .DataFrame | Iterable [CoordinateInput ]]] = None ,
153+ label : str = "" ,
154+ ) -> "Shape" :
155+ exterior = validate_pixel_input (exterior )
135156 if len (exterior ) < 3 :
136157 raise ConfigurationError ("At least three points are required for a polygon" )
137158
@@ -144,7 +165,8 @@ def from_polygon(cls, exterior: Pixels, holes: Optional[list[Pixels]] = None, la
144165 # Close each hole if not already closed
145166 closed_holes = []
146167 for hole in holes :
147- hole_coords = hole .to_list ()
168+ validated_hole = validate_pixel_input (hole )
169+ hole_coords = validated_hole .to_list ()
148170 if hole_coords [0 ] != hole_coords [- 1 ]:
149171 hole_coords .append (hole_coords [0 ])
150172 closed_holes .append (hole_coords )
@@ -155,13 +177,16 @@ def from_polygon(cls, exterior: Pixels, holes: Optional[list[Pixels]] = None, la
155177 return cls (geometry = geometry , label = label )
156178
157179 @classmethod
158- def from_multipolygon (cls , polygons : list [Pixels ], label : str = "" ) -> "Shape" :
180+ def from_multipolygon (
181+ cls , polygons : list [Pixels | pd .DataFrame | Iterable [CoordinateInput ]], label : str = ""
182+ ) -> "Shape" :
159183 if not polygons :
160184 raise ConfigurationError ("At least one polygon is required" )
161185
162186 polygon_objects = []
163187 for pixels in polygons :
164- coords = pixels .to_list ()
188+ validated_pixels = validate_pixel_input (pixels )
189+ coords = validated_pixels .to_list ()
165190 # Close the polygon if not already closed
166191 if coords [0 ] != coords [- 1 ]:
167192 coords .append (coords [0 ])
0 commit comments