Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PB-200: Add geometry checks to items #444

Merged
merged 10 commits into from
Aug 20, 2024
2 changes: 2 additions & 0 deletions app/stac_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ def geometry_from_bbox(bbox):
# if large values, SRID is LV95. The default SRID is 4326
if list_bbox_values[0] > 360:
bbox_geometry.srid = 2056
else:
bbox_geometry.srid = 4326

if not bbox_geometry.valid:
raise ValueError(f'{bbox_geometry.valid_reason} for bbox with {bbox_geometry.wkt}')
Expand Down
38 changes: 35 additions & 3 deletions app/stac_api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def validate_text_to_geometry(text_geometry):
# is the input WKT
try:
geos_geometry = GEOSGeometry(text_geometry)
validate_geometry(geos_geometry)
validate_geometry(geos_geometry, apply_transform=True)
return geos_geometry
except (ValueError, ValidationError, IndexError, GDALException, GEOSException) as error:
message = "The text as WKT could not be transformed into a geometry: %(error)s"
Expand All @@ -328,7 +328,7 @@ def validate_text_to_geometry(text_geometry):
try:
text_geometry = text_geometry.replace('(', '')
text_geometry = text_geometry.replace(')', '')
return validate_geometry(geometry_from_bbox(text_geometry))
return validate_geometry(geometry_from_bbox(text_geometry), apply_transform=True)
except (ValueError, ValidationError, IndexError, GDALException) as error:
message = "The text as bbox could not be transformed into a geometry: %(error)s"
params = {'error': error}
Expand All @@ -337,7 +337,7 @@ def validate_text_to_geometry(text_geometry):
raise ValidationError(errors) from None


def validate_geometry(geometry):
def validate_geometry(geometry, apply_transform=False):
'''
A validator function that ensures, that only valid
geometries are stored.
Expand All @@ -351,6 +351,7 @@ def validate_geometry(geometry):
ValidateionError: About that the geometry is not valid
'''
geos_geometry = GEOSGeometry(geometry)
bbox_ch = GEOSGeometry("POLYGON ((3 44,3 50,14 50,14 44,3 44))")
if geos_geometry.empty:
message = "The geometry is empty: %(error)s"
params = {'error': geos_geometry.wkt}
Expand All @@ -361,6 +362,37 @@ def validate_geometry(geometry):
params = {'error': geos_geometry.valid_reason}
logger.error(message, params)
raise ValidationError(_(message), params=params, code='invalid')
if not geos_geometry.srid:
message = "No projection provided: SRID=%(error)s"
params = {'error': geos_geometry.srid}
logger.error(message, params)
raise ValidationError(_(message), params=params, code='invalid')

# transform geometry from textfield input if necessary
if apply_transform and geos_geometry.srid != 4326:
geos_geometry.transform(4326)
elif geos_geometry.srid != 4326:
message = 'Non permitted Projection. Projection must be wgs84 (SRID=4326) instead of ' \
'SRID=%(error)s'
params = {'error': geos_geometry.srid}
logger.error(message, params)
raise ValidationError(_(message), params=params, code='invalid')

extent = geos_geometry.extent
if abs(extent[1]) > 90 or abs(extent[-1]) > 90:
message = "Latitude exceeds permitted value: %(error)s"
params = {'error': (extent[1], extent[-1])}
logger.error(message, params)
raise ValidationError(_(message), params=params, code='invalid')
if abs(extent[0]) > 180 or abs(extent[-2]) > 180:
message = "Longitude exceeds usual value range: %(warning)s"
params = {'warning': (extent[0], extent[-2])}
logger.warning(message, params)

if not geos_geometry.within(bbox_ch):
message = "Location of asset is (partially) outside of Switzerland"
params = {'warning': geos_geometry.wkt}
logger.warning(message, params)
return geometry


Expand Down
51 changes: 37 additions & 14 deletions app/tests/test_admin_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,24 +118,27 @@ def _create_collection(

return collection, data, link, provider

def _create_item(self, collection, with_link=False, extra=None):
def _create_item(self, collection, with_link=False, extra=None, data=None):

# Post data to create a new item
# Note: the *-*_FORMS fields are necessary management form fields
# originating from the AdminInline and must be present
data = {
"collection": collection.id,
"name": "test_item",
"geometry":
"SRID=4326;POLYGON((5.96 45.82, 5.96 47.81, 10.49 47.81, 10.49 45.82, 5.96 45.82))",
"text_geometry":
"SRID=4326;POLYGON((5.96 45.82, 5.96 47.81, 10.49 47.81, 10.49 45.82, 5.96 45.82))",
"properties_datetime_0": "2020-12-01",
"properties_datetime_1": "13:15:39",
"properties_title": "test",
"links-TOTAL_FORMS": "0",
"links-INITIAL_FORMS": "0",
}
if not data:
data = {
"collection": collection.id,
"name": "test_item",
"geometry":
"SRID=4326;POLYGON((5.96 45.82, 5.96 47.81, 10.49 47.81, 10.49 45.82, "\
"5.96 45.82))",
"text_geometry":
"SRID=4326;POLYGON((5.96 45.82, 5.96 47.81, 10.49 47.81, 10.49 45.82, "\
"5.96 45.82))",
"properties_datetime_0": "2020-12-01",
"properties_datetime_1": "13:15:39",
"properties_title": "test",
"links-TOTAL_FORMS": "0",
"links-INITIAL_FORMS": "0",
}
if with_link:
data.update({
"links-TOTAL_FORMS": "1",
Expand Down Expand Up @@ -672,6 +675,26 @@ def test_add_update_item(self):
msg="Admin page item properties_title update did not work"
)

def test_add_item_with_non_standard_projection(self):
geometry = "SRID=4326;POLYGON ((6.146799690987942 46.04410910398307, "\
"7.438647976247294 46.05153158188484, 7.438632420871813 46.951082771871064, "\
"6.125143650928986 46.94353699772178, 6.146799690987942 46.04410910398307))"
text_geometry = "SRID=2056;POLYGON ((2500000 1100000, 2600000 1100000, 2600000 1200000, "\
"2500000 1200000, 2500000 1100000))"
post_data = {
"collection": self.collection.id,
"name": "test_item",
"geometry": geometry,
"text_geometry": text_geometry,
"properties_datetime_0": "2020-12-01",
"properties_datetime_1": "13:15:39",
"properties_title": "test",
"links-TOTAL_FORMS": "0",
"links-INITIAL_FORMS": "0",
}
#if transformed text_geometry does not match the geometry provided the creation will fail
self._create_item(self.collection, data=post_data)[:2] # pylint: disable=expression-not-assigned

def test_add_update_item_remove_title(self):
item, data = self._create_item(self.collection)[:2]

Expand Down
42 changes: 42 additions & 0 deletions app/tests/tests_09/test_item_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,36 @@ def test_item_create_model_invalid_geometry(self):
item.full_clean()
item.save()

def test_item_create_model_invalid_projection(self):
# a geometry with a projection other than wgs84 should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry(
'SRID=2056;POLYGON ((2500000 1100000, 2600000 1100000, 2600000 1200000, ' \
'2500000 1200000, 2500000 1100000))'
)
)
item.full_clean()
item.save()

def test_item_create_model_invalid_latitude(self):
# a geometry with self-intersection should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry(
'SRID=4326;POLYGON '
'((5.96 45.82, 5.96 97.81, 10.49 97.81, 10.49 45.82, 5.96 45.82))'
)
)
item.full_clean()
item.save()

def test_item_create_model_empty_geometry(self):
# empty geometry should not be allowed
with self.assertRaises(ValidationError):
Expand Down Expand Up @@ -162,6 +192,18 @@ def test_item_create_model_valid_point_geometry(self):
item.full_clean()
item.save()

def test_item_create_model_point_geometry_invalid_latitude(self):
# a geometry with self-intersection should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry('SRID=4326;POINT (5.96 95.82)')
)
item.full_clean()
item.save()

def test_item_create_model_valid_linestring_geometry(self):
# a correct geometry should not pose any problems
item = Item(
Expand Down
42 changes: 42 additions & 0 deletions app/tests/tests_10/test_item_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,36 @@ def test_item_create_model_invalid_geometry(self):
item.full_clean()
item.save()

def test_item_create_model_invalid_projection(self):
# a geometry with a projection other than wgs84 should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry(
'SRID=2056;POLYGON ((2500000 1100000, 2600000 1100000, 2600000 1200000, ' \
'2500000 1200000, 2500000 1100000))'
)
)
item.full_clean()
item.save()

def test_item_create_model_invalid_latitude(self):
# a geometry with self-intersection should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry(
'SRID=4326;POLYGON '
'((5.96 45.82, 5.96 97.81, 10.49 97.81, 10.49 45.82, 5.96 45.82))'
)
)
item.full_clean()
item.save()

def test_item_create_model_empty_geometry(self):
# empty geometry should not be allowed
with self.assertRaises(ValidationError):
Expand Down Expand Up @@ -203,6 +233,18 @@ def test_item_create_model_valid_point_geometry(self):
item.full_clean()
item.save()

def test_item_create_model_point_geometry_invalid_latitude(self):
# a geometry with self-intersection should not be allowed
with self.assertRaises(ValidationError):
item = Item(
collection=self.collection,
properties_datetime=utc_aware(datetime.utcnow()),
name='item-1',
geometry=GEOSGeometry('SRID=4326;POINT (5.96 95.82)')
)
item.full_clean()
item.save()

def test_item_create_model_valid_linestring_geometry(self):
# a correct geometry should not pose any problems
item = Item(
Expand Down