Skip to content

Commit af80dcb

Browse files
committed
Add BBox Validation to the Type.
1 parent 26e8beb commit af80dcb

File tree

2 files changed

+56
-28
lines changed

2 files changed

+56
-28
lines changed

geojson_pydantic/features.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,6 @@ 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 len(bbox) == 6:
34-
if bbox[0] > bbox[3] or bbox[1] > bbox[4] or bbox[2] > bbox[5]: # type: ignore
35-
raise ValueError(
36-
"BBox must be in the form [west, south, bottom, east, north, top]"
37-
)
38-
elif len(bbox) == 4:
39-
if bbox[0] > bbox[2] or bbox[1] > bbox[3]:
40-
raise ValueError("BBox must be in the form [west, south, east, north]")
41-
else:
42-
raise ValueError(
43-
"BBox must be in the form [west, south, east, north] or [west, south, bottom, east, north, top]"
44-
)
45-
return bbox
46-
4725
@validator("geometry", pre=True, always=True)
4826
def set_geometry(cls, geometry: Any) -> Any:
4927
"""set geometry from geo interface or input"""

geojson_pydantic/types.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,63 @@
11
"""Types for geojson_pydantic models"""
22

3-
from typing import TYPE_CHECKING, List, Tuple, Union
3+
from typing import TYPE_CHECKING, Any, Callable, Generator, List, Tuple, Union
44

5-
from pydantic import conlist
5+
from pydantic import ConstrainedList, conlist
66

7-
BBox = Union[
8-
Tuple[float, float, float, float], # 2D bbox
9-
Tuple[float, float, float, float, float, float], # 3D bbox
10-
]
7+
8+
class _BBoxBase(ConstrainedList):
9+
"""Base Class with additional Validation for order."""
10+
11+
# This is needed because pydantic checks it rather than `item_type`
12+
__args__ = (float,)
13+
14+
item_type = float
15+
16+
@classmethod
17+
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
18+
"""Yield the validators."""
19+
yield from super().__get_validators__()
20+
yield cls.validate_bbox
21+
22+
@classmethod
23+
def validate_bbox(cls, bbox: List[float]) -> List[float]:
24+
"""Validate BBox values are ordered correctly."""
25+
if not bbox:
26+
return bbox
27+
28+
offset = len(bbox) // 2
29+
errors: List[str] = []
30+
# Check X
31+
if bbox[0] > bbox[offset]:
32+
errors.append(f"Min X ({bbox[0]}) must be <= Max X ({bbox[offset]}).")
33+
# Check Y
34+
if bbox[1] > bbox[1 + offset]:
35+
errors.append(f"Min Y ({bbox[1]}) must be <= Max Y ({bbox[1 + offset]}).")
36+
# If 3D, check Z values.
37+
if offset > 2 and bbox[2] > bbox[2 + offset]:
38+
errors.append(f"Min Z ({bbox[2]}) must be <= Max Z ({bbox[2 + offset]}).")
39+
40+
if errors:
41+
raise ValueError("Invalid BBox. Error(s): " + " ".join(errors))
42+
43+
return bbox
44+
45+
46+
class BBox2D(_BBoxBase):
47+
"""2D Bounding Box"""
48+
49+
min_items = 4
50+
max_items = 4
51+
52+
53+
class BBox3D(_BBoxBase):
54+
"""3D Bounding Box"""
55+
56+
min_items = 6
57+
max_items = 6
58+
59+
60+
BBox = Union[BBox3D, BBox2D]
1161
Position = Union[Tuple[float, float], Tuple[float, float, float]]
1262

1363
# Coordinate arrays

0 commit comments

Comments
 (0)