Skip to content

Commit d2a484d

Browse files
Merge pull request #122 from eseglem/feature/bbox-validation
Additional bbox validation.
2 parents 613bd1b + b664207 commit d2a484d

File tree

4 files changed

+60
-33
lines changed

4 files changed

+60
-33
lines changed

geojson_pydantic/features.py

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from geojson_pydantic.geo_interface import GeoInterfaceMixin
99
from geojson_pydantic.geometries import Geometry, GeometryCollection
10-
from geojson_pydantic.types import BBox
10+
from geojson_pydantic.types import BBox, validate_bbox
1111

1212
Props = TypeVar("Props", bound=Union[Dict[str, Any], BaseModel])
1313
Geom = TypeVar("Geom", bound=Union[Geometry, GeometryCollection])
@@ -22,33 +22,7 @@ class Feature(GenericModel, Generic[Geom, Props], GeoInterfaceMixin):
2222
id: Optional[Union[StrictInt, StrictStr]] = None
2323
bbox: Optional[BBox] = None
2424

25-
class Config:
26-
"""Model configuration."""
27-
28-
use_enum_values = True
29-
30-
@validator("bbox", pre=True)
31-
def check_bbox(cls, bbox: BBox) -> BBox:
32-
"""Check that bbox is valid."""
33-
if bbox is None:
34-
return bbox
35-
36-
if len(bbox) == 6:
37-
if bbox[0] > bbox[3] or bbox[1] > bbox[4] or bbox[2] > bbox[5]: # type: ignore
38-
raise ValueError(
39-
"BBox must be in the form [west, south, bottom, east, north, top]"
40-
)
41-
42-
elif len(bbox) == 4:
43-
if bbox[0] > bbox[2] or bbox[1] > bbox[3]:
44-
raise ValueError("BBox must be in the form [west, south, east, north]")
45-
46-
else:
47-
raise ValueError(
48-
"BBox must be in the form [west, south, east, north] or [west, south, bottom, east, north, top]"
49-
)
50-
51-
return bbox
25+
_validate_bbox = validator("bbox", allow_reuse=True)(validate_bbox)
5226

5327
@validator("geometry", pre=True, always=True)
5428
def set_geometry(cls, geometry: Any) -> Any:
@@ -77,3 +51,5 @@ def __len__(self) -> int:
7751
def __getitem__(self, index: int) -> Feature:
7852
"""get feature at a given index"""
7953
return self.features[index]
54+
55+
_validate_bbox = validator("bbox", allow_reuse=True)(validate_bbox)

geojson_pydantic/geometries.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
MultiPolygonCoords,
2020
PolygonCoords,
2121
Position,
22+
validate_bbox,
2223
)
2324

2425

@@ -107,6 +108,8 @@ def wkt(self) -> str:
107108

108109
return wkt
109110

111+
_validate_bbox = validator("bbox", allow_reuse=True)(validate_bbox)
112+
110113

111114
class Point(_GeometryBase):
112115
"""Point Model"""
@@ -287,6 +290,8 @@ def wkt(self) -> str:
287290
z = " Z " if "Z" in geometries else " "
288291
return f"{self.type.upper()}{z}{geometries}"
289292

293+
_validate_bbox = validator("bbox", allow_reuse=True)(validate_bbox)
294+
290295
@validator("geometries")
291296
def check_geometries(cls, geometries: List) -> List:
292297
"""Add warnings for conditions the spec does not explicitly forbid."""

geojson_pydantic/types.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,46 @@
11
"""Types for geojson_pydantic models"""
22

3-
from typing import TYPE_CHECKING, List, Tuple, Union
3+
from typing import TYPE_CHECKING, List, Optional, Tuple, TypeVar, Union
44

55
from pydantic import conlist
66

7+
T = TypeVar("T")
8+
79
BBox = Union[
810
Tuple[float, float, float, float], # 2D bbox
911
Tuple[float, float, float, float, float, float], # 3D bbox
1012
]
13+
14+
15+
def validate_bbox(bbox: Optional[BBox]) -> Optional[BBox]:
16+
"""Validate BBox values are ordered correctly."""
17+
# If bbox is None, there is nothing to validate.
18+
if bbox is None:
19+
return None
20+
21+
# A list to store any errors found so we can raise them all at once.
22+
errors: List[str] = []
23+
24+
# Determine where the second position starts. 2 for 2D, 3 for 3D.
25+
offset = len(bbox) // 2
26+
27+
# Check X
28+
if bbox[0] > bbox[offset]:
29+
errors.append(f"Min X ({bbox[0]}) must be <= Max X ({bbox[offset]}).")
30+
# Check Y
31+
if bbox[1] > bbox[1 + offset]:
32+
errors.append(f"Min Y ({bbox[1]}) must be <= Max Y ({bbox[1 + offset]}).")
33+
# If 3D, check Z values.
34+
if offset > 2 and bbox[2] > bbox[2 + offset]:
35+
errors.append(f"Min Z ({bbox[2]}) must be <= Max Z ({bbox[2 + offset]}).")
36+
37+
# Raise any errors found.
38+
if errors:
39+
raise ValueError("Invalid BBox. Error(s): " + " ".join(errors))
40+
41+
return bbox
42+
43+
1144
Position = Union[Tuple[float, float], Tuple[float, float, float]]
1245

1346
# Coordinate arrays

tests/test_features.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,21 +232,34 @@ def test_feature_validation():
232232
Feature(type="Feature", properties=None)
233233

234234
assert Feature(
235-
type="Feature", properties=None, bbox=[0, 0, 100, 100], geometry=None
235+
type="Feature", properties=None, bbox=(0, 0, 100, 100), geometry=None
236236
)
237237
assert Feature(
238-
type="Feature", properties=None, bbox=[0, 0, 0, 100, 100, 100], geometry=None
238+
type="Feature", properties=None, bbox=(0, 0, 0, 100, 100, 100), geometry=None
239239
)
240240

241241
with pytest.raises(ValidationError):
242242
# bad bbox2d
243-
Feature(type="Feature", properties=None, bbox=[100, 100, 0, 0], geometry=None)
243+
Feature(type="Feature", properties=None, bbox=(100, 100, 0, 0), geometry=None)
244244

245245
with pytest.raises(ValidationError):
246246
# bad bbox3d
247247
Feature(
248248
type="Feature",
249249
properties=None,
250-
bbox=[100, 100, 100, 0, 0, 0],
250+
bbox=(100, 100, 100, 0, 0, 0),
251+
geometry=None,
252+
)
253+
254+
255+
def test_bbox_validation():
256+
# Some attempts at generic validation did not validate the types within
257+
# bbox before passing them to the function and resulted in TypeErrors.
258+
# This test exists to ensure that doesn't happen in the future.
259+
with pytest.raises(ValidationError):
260+
Feature(
261+
type="Feature",
262+
properties=None,
263+
bbox=(0, "a", 0, 1, 1, 1),
251264
geometry=None,
252265
)

0 commit comments

Comments
 (0)