Skip to content

Commit 74982b1

Browse files
committed
RFC5: apply distance limiting
1 parent ecd81c1 commit 74982b1

5 files changed

Lines changed: 94 additions & 10 deletions

File tree

pygeoapi/api/__init__.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,3 +1747,53 @@ def evaluate_limit(requested: Union[None, int], server_limits: dict,
17471747
else:
17481748
LOGGER.debug('limit requested')
17491749
return min(requested2, max_)
1750+
1751+
1752+
def evaluate_limit_distance(server_limits: dict, collection_limits: dict,
1753+
request_bbox: list = []) -> bool:
1754+
"""
1755+
Helper function to evaluate limit parameter
1756+
1757+
:param server_limits: `dict` of server limits
1758+
:param collection_limits: `dict` of collection limits
1759+
:param request_bbox: `list` bbox of request
1760+
1761+
:returns: `bool` successful distance check
1762+
"""
1763+
1764+
exceed_msg = None
1765+
1766+
effective_limits = ChainMap(collection_limits, server_limits)
1767+
1768+
on_exceed = effective_limits.get('on_exceed', 'throttle')
1769+
max_distance_x = effective_limits.get('max_distance_x')
1770+
max_distance_y = effective_limits.get('max_distance_y')
1771+
max_distance_units = effective_limits.get('max_distance_units')
1772+
1773+
LOGGER.debug(f'On exceed: {on_exceed}')
1774+
LOGGER.debug(f'Maximum distance x: {max_distance_x}')
1775+
LOGGER.debug(f'Maximum distance y: {max_distance_y}')
1776+
LOGGER.debug(f'Maximum distance units: {max_distance_units}')
1777+
1778+
# TODO: assess distance units
1779+
1780+
if not request_bbox:
1781+
LOGGER.debug('no bbox requested')
1782+
return True
1783+
1784+
if max_distance_x is not None:
1785+
requested_distance_x = abs(request_bbox[2] - request_bbox[0])
1786+
if requested_distance_x > max_distance_x:
1787+
exceed_msg = 'Maximum distance x exceeded'
1788+
1789+
if max_distance_y is not None:
1790+
requested_distance_y = abs(request_bbox[3] - request_bbox[1])
1791+
if requested_distance_y > max_distance_y:
1792+
exceed_msg = 'Maximum distance y exceeded'
1793+
1794+
if exceed_msg is not None:
1795+
LOGGER.warning(exceed_msg)
1796+
if on_exceed == 'error':
1797+
raise ValueError(exceed_msg)
1798+
1799+
return True

pygeoapi/api/coverages.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,8 @@
5151
)
5252

5353
from . import (
54-
APIRequest, API, F_JSON, SYSTEM_LOCALE, validate_bbox, validate_datetime,
55-
validate_subset
56-
)
54+
APIRequest, API, F_JSON, SYSTEM_LOCALE, evaluate_limit_distance,
55+
validate_bbox, validate_datetime, validate_subset)
5756

5857
LOGGER = logging.getLogger(__name__)
5958

@@ -112,6 +111,11 @@ def get_collection_coverage(
112111
else:
113112
try:
114113
bbox = validate_bbox(bbox)
114+
server_limits = api.config['server'].get('limits', {})
115+
collection_limits = api.config['resources'][dataset].get('limits', {}) # noqa
116+
117+
_ = evaluate_limit_distance(request.params.get('bbox', []),
118+
server_limits, collection_limits)
115119
except ValueError as err:
116120
msg = str(err)
117121
return api.get_exception(

pygeoapi/api/environmental_data_retrieval.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from shapely.wkt import loads as shapely_loads
4949

5050
from pygeoapi import l10n
51-
from pygeoapi.api import evaluate_limit
51+
from pygeoapi.api import evaluate_limit, evaluate_limit_distance
5252
from pygeoapi.formatter.base import FormatterSerializationError
5353
from pygeoapi.crs import (create_crs_transform_spec, set_content_crs_header)
5454
from pygeoapi.openapi import get_oas_30_parameters
@@ -345,6 +345,12 @@ def get_collection_edr_query(api: API, request: APIRequest,
345345
LOGGER.debug('Processing cube bbox')
346346
try:
347347
bbox = validate_bbox(request.params.get('bbox'))
348+
server_limits = api.config['server'].get('limits', {})
349+
collection_limits = api.config['resources'][dataset].get('limits', {}) # noqa
350+
351+
_ = evaluate_limit_distance(request.params.get('bbox', []),
352+
server_limits, collection_limits)
353+
348354
if not bbox and query_type == 'cube':
349355
raise ValueError('bbox parameter required by cube queries')
350356
except ValueError as err:

pygeoapi/api/itemtypes.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from pyproj.exceptions import CRSError
4949

5050
from pygeoapi import l10n
51-
from pygeoapi.api import evaluate_limit
51+
from pygeoapi.api import evaluate_limit, evaluate_limit_distance
5252
from pygeoapi.api.pubsub import publish_message
5353
from pygeoapi.crs import (DEFAULT_CRS, DEFAULT_STORAGE_CRS,
5454
create_crs_transform_spec, get_supported_crs_list,
@@ -290,9 +290,13 @@ def get_collection_items(
290290
'level (RFC5)')
291291
LOGGER.warning(msg)
292292
try:
293+
server_limits = api.config['server'].get('limits', {})
294+
collection_limits = collections[dataset].get('limits', {})
295+
293296
limit = evaluate_limit(request.params.get('limit'),
294-
api.config['server'].get('limits', {}),
295-
collections[dataset].get('limits', {}))
297+
server_limits, collection_limits)
298+
_ = evaluate_limit_distance(request.params.get('bbox', []),
299+
server_limits, collection_limits)
296300
except ValueError as err:
297301
return api.get_exception(
298302
HTTPStatus.BAD_REQUEST, headers, request.format,

tests/api/test_api.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@
4141
from pygeoapi.api import (
4242
API, APIRequest, FORMAT_TYPES, F_HTML, F_JSON, F_JSONLD, F_GZIP,
4343
__version__, validate_bbox, validate_datetime, evaluate_limit,
44-
validate_subset, landing_page, openapi_, conformance, describe_collections,
45-
get_collection_schema,
46-
)
44+
evaluate_limit_distance, validate_subset, landing_page, openapi_,
45+
conformance, describe_collections, get_collection_schema)
4746
from pygeoapi.util import yaml_load, get_api_rules, get_base_url
4847

4948
from tests.util import (get_test_file_path, mock_api_request, mock_flask,
@@ -913,3 +912,24 @@ def test_evaluate_limit():
913912

914913
assert evaluate_limit(None, server, collection) == 10
915914
assert evaluate_limit('40', server, collection) == 3
915+
916+
917+
def test_evaluate_limit_distance():
918+
collection = {
919+
'on_exceed': 'error',
920+
'max_distance_x': 10,
921+
'max_distance_y': 10
922+
}
923+
924+
with pytest.raises(ValueError):
925+
assert evaluate_limit_distance(
926+
{}, collection, request_bbox=[-180, -90, 180, 90])
927+
928+
assert evaluate_limit_distance({}, collection,
929+
request_bbox=[-142, 42, -140, 44])
930+
931+
assert evaluate_limit_distance({}, {}, request_bbox=[-180, -90, 180, 90])
932+
933+
collection['on_exceed'] = 'throttle'
934+
assert evaluate_limit_distance(
935+
{}, collection, request_bbox=[-180, -90, 180, 90])

0 commit comments

Comments
 (0)