@@ -145,72 +145,87 @@ class HasGeoInterface(Protocol):
145145 @property
146146 def __geo_interface__ (self ) -> Any : ...
147147
148+
148149class GeoJSONPoint (TypedDict ):
149150 type : Literal ["Point" ]
150- # We fix to a tuple (to statically check the length is 2, 3 or 4) but
151+ # We fix to a tuple (to statically check the length is 2, 3 or 4) but
151152 # RFC7946 only requires: "A position is an array of numbers. There MUST be two or more
152153 # elements. "
153154 # RFC7946 also requires long/lat easting/northing which we do not enforce,
154155 # and despite the SHOULD NOT, we may use a 4th element for Shapefile M Measures.
155- coordinates : Union [Point , tuple [()]]
156-
156+ coordinates : Union [Point , tuple [()]]
157+
158+
157159class GeoJSONMultiPoint (TypedDict ):
158160 type : Literal ["MultiPoint" ]
159161 coordinates : Points
160162
163+
161164class GeoJSONLineString (TypedDict ):
162165 type : Literal ["LineString" ]
163166 # "Two or more positions" not enforced by type checker
164167 # https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.4
165168 coordinates : Points
166-
169+
170+
167171class GeoJSONMultiLineString (TypedDict ):
168172 type : Literal ["MultiLineString" ]
169173 coordinates : list [Points ]
170174
175+
171176class GeoJSONPolygon (TypedDict ):
172177 type : Literal ["Polygon" ]
173- # Other requirements for Polygon not enforced by type checker
178+ # Other requirements for Polygon not enforced by type checker
174179 # https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6
175180 coordinates : list [Points ]
176181
182+
177183class GeoJSONMultiPolygon (TypedDict ):
178184 type : Literal ["MultiPolygon" ]
179185 coordinates : list [list [Points ]]
180186
187+
181188GeoJSONHomogeneousGeometryObject = Union [
182- GeoJSONPoint , GeoJSONMultiPoint ,
183- GeoJSONLineString , GeoJSONMultiLineString ,
184- GeoJSONPolygon , GeoJSONMultiPolygon ,
189+ GeoJSONPoint ,
190+ GeoJSONMultiPoint ,
191+ GeoJSONLineString ,
192+ GeoJSONMultiLineString ,
193+ GeoJSONPolygon ,
194+ GeoJSONMultiPolygon ,
185195]
186196
197+
187198class GeoJSONGeometryCollection (TypedDict ):
188199 type : Literal ["GeometryCollection" ]
189200 geometries : list [GeoJSONHomogeneousGeometryObject ]
190201
202+
191203# RFC7946 3.1
192204GeoJSONObject = Union [GeoJSONHomogeneousGeometryObject , GeoJSONGeometryCollection ]
193205
206+
194207class GeoJSONFeature (TypedDict ):
195208 type : Literal ["Feature" ]
196- properties : Optional [dict [str , Any ]] # RFC7946 3.2 "(any JSON object or a JSON null value)"
209+ properties : Optional [
210+ dict [str , Any ]
211+ ] # RFC7946 3.2 "(any JSON object or a JSON null value)"
197212 geometry : Optional [GeoJSONObject ]
198213
199214
200215class GeoJSONFeatureCollection (TypedDict ):
201216 type : Literal ["FeatureCollection" ]
202217 features : list [GeoJSONFeature ]
203218
219+
204220class GeoJSONFeatureCollectionWithBBox (GeoJSONFeatureCollection , total = False ):
205221 # bbox is optional
206- # typing.NotRequired requires Python 3.11
222+ # typing.NotRequired requires Python 3.11
207223 # and we must support 3.9 (at least until October)
208224 # https://docs.python.org/3/library/typing.html#typing.Required
209225 # Is there a backport?
210226 bbox : list [float ]
211227
212228
213-
214229# Helpers
215230
216231MISSING = [None , "" ]
@@ -278,7 +293,7 @@ def __repr__(self):
278293
279294
280295def signed_area (
281- coords : Coords ,
296+ coords : Points ,
282297 fast : bool = False ,
283298) -> float :
284299 """Return the signed area enclosed by a ring using the linear time
@@ -296,22 +311,22 @@ def signed_area(
296311 return area2 / 2.0
297312
298313
299- def is_cw (coords : Coords ) -> bool :
314+ def is_cw (coords : Points ) -> bool :
300315 """Returns True if a polygon ring has clockwise orientation, determined
301316 by a negatively signed area.
302317 """
303318 area2 = signed_area (coords , fast = True )
304319 return area2 < 0
305320
306321
307- def rewind (coords : Reversible [Coord ]) -> Coords :
322+ def rewind (coords : Reversible [Point ]) -> Points :
308323 """Returns the input coords in reversed order."""
309324 return list (reversed (coords ))
310325
311326
312- def ring_bbox (coords : Coords ) -> BBox :
327+ def ring_bbox (coords : Points ) -> BBox :
313328 """Calculates and returns the bounding box of a ring."""
314- xs , ys = zip (* coords )
329+ xs , ys = map ( list , list ( zip (* coords ))[: 2 ]) # ignore any z or m values
315330 bbox = min (xs ), min (ys ), max (xs ), max (ys )
316331 return bbox
317332
@@ -332,7 +347,7 @@ def bbox_contains(bbox1: BBox, bbox2: BBox) -> bool:
332347 return contains
333348
334349
335- def ring_contains_point (coords : Coords , p : Point2D ) -> bool :
350+ def ring_contains_point (coords : Points , p : Point2D ) -> bool :
336351 """Fast point-in-polygon crossings algorithm, MacMartin optimization.
337352
338353 Adapted from code by Eric Haynes
@@ -381,7 +396,7 @@ class RingSamplingError(Exception):
381396 pass
382397
383398
384- def ring_sample (coords : Coords , ccw : bool = False ) -> Point2D :
399+ def ring_sample (coords : Points , ccw : bool = False ) -> Point2D :
385400 """Return a sample point guaranteed to be within a ring, by efficiently
386401 finding the first centroid of a coordinate triplet whose orientation
387402 matches the orientation of the ring and passes the point-in-ring test.
@@ -431,14 +446,15 @@ def itercoords():
431446 )
432447
433448
434- def ring_contains_ring (coords1 : Coords , coords2 : list [Point2D ]) -> bool :
449+ def ring_contains_ring (coords1 : Points , coords2 : list [Point ]) -> bool :
435450 """Returns True if all vertexes in coords2 are fully inside coords1."""
436- return all (ring_contains_point (coords1 , p2 ) for p2 in coords2 )
451+ # Ignore Z and M values in coords2
452+ return all (ring_contains_point (coords1 , p2 [:2 ]) for p2 in coords2 )
437453
438454
439455def organize_polygon_rings (
440- rings : Iterable [Coords ], return_errors : Optional [dict [str , int ]] = None
441- ) -> list [list [Coords ]]:
456+ rings : Iterable [Points ], return_errors : Optional [dict [str , int ]] = None
457+ ) -> list [list [Points ]]:
442458 """Organize a list of coordinate rings into one or more polygons with holes.
443459 Returns a list of polygons, where each polygon is composed of a single exterior
444460 ring, and one or more interior holes. If a return_errors dict is provided (optional),
@@ -992,8 +1008,8 @@ def __init__(self, shape: Optional[Shape] = None, record: Optional[_Record] = No
9921008 def __geo_interface__ (self ) -> GeoJSONFeature :
9931009 return {
9941010 "type" : "Feature" ,
995- "properties" : None
996- if self .record is None
1011+ "properties" : None
1012+ if self .record is None
9971013 else self .record .as_dict (date_strings = True ),
9981014 "geometry" : None
9991015 if self .shape is None or self .shape .shapeType == NULL
@@ -1015,11 +1031,8 @@ def __geo_interface__(self) -> GeoJSONGeometryCollection:
10151031 # Note: currently this will fail if any of the shapes are null-geometries
10161032 # could be fixed by storing the shapefile shapeType upon init, returning geojson type with empty coords
10171033 collection = GeoJSONGeometryCollection (
1018- type = "GeometryCollection" ,
1019- geometries = [shape .__geo_interface__
1020- for shape in self
1021- if shape is not None
1022- ],
1034+ type = "GeometryCollection" ,
1035+ geometries = [shape .__geo_interface__ for shape in self if shape is not None ],
10231036 )
10241037 return collection
10251038
@@ -1035,10 +1048,6 @@ def __repr__(self):
10351048
10361049 @property
10371050 def __geo_interface__ (self ) -> GeoJSONFeatureCollection :
1038- collection = {
1039- "type" : "FeatureCollection" ,
1040- "features" : [] #shaperec.__geo_interface__ for shaperec in self],
1041- }
10421051 return GeoJSONFeatureCollection (
10431052 type = "FeatureCollection" ,
10441053 features = [shaperec .__geo_interface__ for shaperec in self ],
@@ -1362,7 +1371,7 @@ def __iter__(self):
13621371 def __geo_interface__ (self ) -> GeoJSONFeatureCollectionWithBBox :
13631372 shaperecords = self .shapeRecords ()
13641373 fcollection = GeoJSONFeatureCollectionWithBBox (
1365- bbox = list (self .bbox ),
1374+ bbox = list (self .bbox ),
13661375 ** shaperecords .__geo_interface__ ,
13671376 )
13681377 return fcollection
0 commit comments