Skip to content

Commit 112d7b9

Browse files
committed
expose checks for valid queries through the RecManager.
1 parent ce0e34a commit 112d7b9

File tree

3 files changed

+79
-16
lines changed

3 files changed

+79
-16
lines changed

src/natcap/invest/recreation/recmodel_client.py

+20
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,26 @@ def _retrieve_user_days(
646646
'flickr': 'PUD',
647647
'twitter': 'TUD'
648648
}
649+
for dataset in dataset_list:
650+
# validate available year range
651+
min_year, max_year = recmodel_manager.get_valid_year_range(dataset)
652+
LOGGER.info(
653+
f"{dataset} server supports year queries between {min_year} and {max_year}")
654+
if not min_year <= int(start_year) <= max_year:
655+
raise ValueError(
656+
f"Start year must be between {min_year} and {max_year}.\n"
657+
f" User input: ({start_year})")
658+
if not min_year <= int(end_year) <= max_year:
659+
raise ValueError(
660+
f"End year must be between {min_year} and {max_year}.\n"
661+
f" User input: ({end_year})")
662+
n_points, max_allowable = recmodel_manager.get_aoi_query_size(dataset)
663+
if n_points > max_allowable:
664+
raise ValueError(
665+
f'The AOI extent is too large. Its bounding box contains '
666+
f'{n_points} {dataset} points. Please reduce the extent of '
667+
f'the AOI until it contains fewer than '
668+
f'{max_allowable} points.')
649669
results = recmodel_manager.calculate_userdays(
650670
zip_file_binary, start_year, end_year, dataset_list)
651671
for dataset in dataset_list:

src/natcap/invest/recreation/recmodel_server.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
CSV_ROWS_PER_PARSE = 2 ** 10
4545
LOGGER_TIME_DELAY = 5.0
4646
INITIAL_BOUNDING_BOX = [-180, -90, 180, 90]
47+
# Max points within an AOI bounding box before rejecting the AOI.
48+
MAX_ALLOWABLE_QUERY = 30_000_000
4749

4850
Pyro5.config.SERIALIZER = 'marshal' # lets us pass null bytes in strings
4951

@@ -85,7 +87,8 @@ def __init__(
8587
raw_csv_filename=None,
8688
quadtree_pickle_filename=None,
8789
max_points_per_node=GLOBAL_MAX_POINTS_PER_NODE,
88-
max_depth=GLOBAL_DEPTH, dataset_name='flickr'):
90+
max_depth=GLOBAL_DEPTH, dataset_name='flickr',
91+
max_allowable_query=MAX_ALLOWABLE_QUERY):
8992
"""Initialize RecModel object.
9093
9194
The object can be initialized either with a path to a CSV file
@@ -156,6 +159,7 @@ def __init__(
156159
# self.global_cache_dir = global_cache
157160
self.min_year = min_year
158161
self.max_year = max_year
162+
self.max_allowable_query = max_allowable_query
159163
self.acronym = 'PUD' if dataset_name == 'flickr' else 'TUD'
160164

161165
def get_valid_year_range(self):
@@ -198,6 +202,9 @@ def fetch_workspace_aoi(self, workspace_id): # pylint: disable=no-self-use
198202
with open(out_zip_file_path, 'rb') as out_zipfile:
199203
return out_zipfile.read()
200204

205+
def get_aoi_query_size(self):
206+
return (50_000_000, self.max_allowable_query)
207+
201208
# @_try_except_wrapper("exception in calc_user_days_in_aoi")
202209
def calc_user_days_in_aoi(
203210
self, zip_file_binary, date_range, out_vector_filename):
@@ -982,25 +989,33 @@ class RecManager(object):
982989
def __init__(self, servers_dict):
983990
self.servers = servers_dict
984991

992+
def get_valid_year_range(self, dataset):
993+
server = self.servers[dataset]
994+
return server.get_valid_year_range()
995+
996+
def get_aoi_query_size(self, dataset):
997+
server = self.servers[dataset]
998+
return server.get_aoi_query_size()
999+
9851000
@_try_except_wrapper("calculate_userdays exited while multiprocessing.")
9861001
def calculate_userdays(self, zip_file_binary, start_year, end_year, dataset_list):
9871002
results = {}
9881003
with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:
9891004
future_to_label = {}
9901005
for dataset in dataset_list:
9911006
server = self.servers[dataset]
992-
# validate available year range
993-
min_year, max_year = server.get_valid_year_range()
994-
LOGGER.info(
995-
f"Server supports year queries between {min_year} and {max_year}")
996-
if not min_year <= int(start_year) <= max_year:
997-
raise ValueError(
998-
f"Start year must be between {min_year} and {max_year}.\n"
999-
f" User input: ({start_year})")
1000-
if not min_year <= int(end_year) <= max_year:
1001-
raise ValueError(
1002-
f"End year must be between {min_year} and {max_year}.\n"
1003-
f" User input: ({end_year})")
1007+
# # validate available year range
1008+
# min_year, max_year = server.get_valid_year_range()
1009+
# LOGGER.info(
1010+
# f"Server supports year queries between {min_year} and {max_year}")
1011+
# if not min_year <= int(start_year) <= max_year:
1012+
# raise ValueError(
1013+
# f"Start year must be between {min_year} and {max_year}.\n"
1014+
# f" User input: ({start_year})")
1015+
# if not min_year <= int(end_year) <= max_year:
1016+
# raise ValueError(
1017+
# f"End year must be between {min_year} and {max_year}.\n"
1018+
# f" User input: ({end_year})")
10041019

10051020
# append jan 1 to start and dec 31 to end
10061021
date_range = (str(start_year)+'-01-01',

tests/test_recreation.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,11 @@ def test_local_aggregate_points(self):
246246
# transfer zipped file to server
247247
date_range = (('2005-01-01'), ('2014-12-31'))
248248
out_vector_filename = 'test_aoi_for_subset_pud.shp'
249-
print('calc photo userdays')
249+
250250
zip_result, workspace_id, version_str = (
251251
recreation_server.calc_user_days_in_aoi(
252252
zip_file_binary, date_range, out_vector_filename))
253-
print('calc photo userdays done')
253+
254254
# unpack result
255255
result_zip_path = os.path.join(self.workspace_dir, 'pud_result.zip')
256256
with open(result_zip_path, 'wb') as file:
@@ -427,7 +427,7 @@ def test_construct_query_twitter_qt(self):
427427
# user,date,lat,lon
428428
# 1117195232,2023-01-01,-22.908,-43.1975
429429
# 54900515,2023-01-01,44.62804,10.60603
430-
430+
431431
def make_twitter_csv(target_filename):
432432
dates = numpy.arange(
433433
numpy.datetime64('2017-01-01'), numpy.datetime64('2017-12-31'))
@@ -780,6 +780,34 @@ def test_end_year_out_of_range(self):
780780
with self.assertRaises(ValueError):
781781
recmodel_client.execute(args)
782782

783+
def test_aoi_too_large(self):
784+
"""Test server checks aoi size; client raises exception."""
785+
from natcap.invest.recreation import recmodel_client
786+
787+
args = {
788+
'aoi_path': os.path.join(SAMPLE_DATA, 'andros_aoi.shp'),
789+
'cell_size': 7000.0,
790+
'compute_regression': True,
791+
'start_year': MIN_YEAR,
792+
'end_year': MAX_YEAR,
793+
'grid_aoi': True,
794+
'grid_type': 'hexagon',
795+
'predictor_table_path': os.path.join(
796+
SAMPLE_DATA, 'predictors.csv'),
797+
'results_suffix': '',
798+
'scenario_predictor_table_path': os.path.join(
799+
SAMPLE_DATA, 'predictors_scenario.csv'),
800+
'workspace_dir': self.workspace_dir,
801+
'hostname': self.hostname,
802+
'port': self.port
803+
}
804+
805+
with self.assertRaises(ValueError) as cm:
806+
recmodel_client.execute(args)
807+
actual_message = str(cm.exception)
808+
expected_message = 'The AOI extent is too large'
809+
self.assertIn(expected_message, actual_message)
810+
783811

784812
class RecreationClientRegressionTests(unittest.TestCase):
785813
"""Regression & Unit tests for recmodel_client."""

0 commit comments

Comments
 (0)