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

Generate stable and unique GML ids for Box and Point locations. #388

Merged
merged 1 commit into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions pydov/util/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@ class AbstractLocation(object):

"""

def _get_id_seed(self):
"""Get the seed for generating a random but stable GML ID for this
location.

Should return the same value for locations considered equal.
"""
raise NotImplementedError('This should be implemented in a subclass.')

def _get_id(self):
random.seed(self._get_id_seed())
random_id = ''.join(random.choice(
string.ascii_letters + string.digits) for x in range(8))
return f'pydov.{random_id}'
Expand Down Expand Up @@ -184,6 +193,7 @@ def __init__(self, minx, miny, maxx, maxy, epsg=31370):
self.miny = miny
self.maxx = maxx
self.maxy = maxy
self.epsg = epsg

self.element = etree.Element(
'{http://www.opengis.net/gml/3.2}Envelope')
Expand All @@ -203,6 +213,11 @@ def __init__(self, minx, miny, maxx, maxy, epsg=31370):
upper_corner.text = '{:.06f} {:.06f}'.format(self.maxx, self.maxy)
self.element.append(upper_corner)

def _get_id_seed(self):
return ','.join(str(i) for i in [
self.minx, self.miny, self.maxx, self.miny, self.epsg
])

def get_element(self):
return self.element

Expand Down Expand Up @@ -230,6 +245,7 @@ def __init__(self, x, y, epsg=31370):
"""
self.x = x
self.y = y
self.epsg = epsg

self.element = etree.Element('{http://www.opengis.net/gml/3.2}Point')
self.element.set('srsDimension', '2')
Expand All @@ -242,6 +258,11 @@ def __init__(self, x, y, epsg=31370):
coordinates.text = '{:.06f} {:.06f}'.format(self.x, self.y)
self.element.append(coordinates)

def _get_id_seed(self):
return ','.join(str(i) for i in [
self.x, self.y, self.epsg
])

def get_element(self):
return self.element

Expand Down
23 changes: 23 additions & 0 deletions pydov/util/owsutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,28 @@ def set_geometry_column(location, geometry_column):
return location.toXML()


def unique_gml_ids(location):
"""Make sure the location query has unique GML id's for all features.
Parameters
----------
location : etree.ElementTree
XML tree of the location filter.
Returns
-------
etree.ElementTree
XML tree of the location filter with unique GML ids.
"""
gml_items = location.findall('.//*[@{http://www.opengis.net/gml/3.2}id]')
gml_ids = [i.get('{http://www.opengis.net/gml/3.2}id') for i in gml_items]

if len(gml_ids) == len(set(gml_ids)):
return location
else:
for ix, item in enumerate(gml_items):
item.set('{http://www.opengis.net/gml/3.2}id', f'pydov.{ix}')
return location


def wfs_build_getfeature_request(typename, geometry_column=None, location=None,
filter=None, sort_by=None, propertyname=None,
max_features=None, start_index=0,
Expand Down Expand Up @@ -439,6 +461,7 @@ def wfs_build_getfeature_request(typename, geometry_column=None, location=None,

if location is not None:
location = set_geometry_column(location, geometry_column)
location = unique_gml_ids(location)
filter_parent.append(location)

if filter is not None or location is not None:
Expand Down
15 changes: 14 additions & 1 deletion tests/test_util_location.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
class TestLocation(object):
"""Class grouping tests for the AbstractLocation subtypes."""

def test_gml_id(self):
def test_gml_id_unique(self):
"""Test whether GML id's for two different locations are unique."""
box1 = Box(94720, 186910, 112220, 202870)
id1 = box1.get_element().get('{http://www.opengis.net/gml/3.2}id')

Expand All @@ -36,6 +37,18 @@ def test_gml_id(self):
assert id2.startswith('pydov')
assert id1 != id2

def test_gml_id_stable(self):
"""Test whether GML id's for two equal locations are the same."""
box1 = Box(94720, 186910, 112220, 202870)
id1 = box1.get_element().get('{http://www.opengis.net/gml/3.2}id')

box2 = Box(94720, 186910, 112220, 202870)
id2 = box2.get_element().get('{http://www.opengis.net/gml/3.2}id')

assert id1.startswith('pydov')
assert id2.startswith('pydov')
assert id1 == id2

def test_box(self, mp_gml_id):
"""Test the default Box type.

Expand Down
39 changes: 37 additions & 2 deletions tests/test_util_owsutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import pytest
from owslib.etree import etree
from owslib.fes2 import FilterRequest, PropertyIsEqualTo, SortBy, SortProperty
from owslib.fes2 import FilterRequest, PropertyIsEqualTo, SortBy, SortProperty, Or
from owslib.iso import MD_Metadata
from owslib.util import nspath_eval

from pydov.util import owsutil
from pydov.util.dovutil import build_dov_url
from pydov.util.location import Box, Within
from pydov.util.location import Box, Within, WithinDistance
from tests.abstract import clean_xml

location_md_metadata = 'tests/data/types/boring/md_metadata.xml'
Expand Down Expand Up @@ -406,6 +406,41 @@ def test_wfs_build_getfeature_request_bbox(self, mp_gml_id):
'214775.000000</gml:upperCorner></gml:Envelope></fes:Within></fes'
':Filter></wfs:Query></wfs:GetFeature>')

def test_wfs_build_getfeature_request_gml_id_stable(self):
"""Test the owsutil.wfs_build_getfeature_request method with a
typename, box and geometry_column.
Test whether the XML of the WFS GetFeature call is stable.
"""
xml1 = owsutil.wfs_build_getfeature_request(
'dov-pub:Boringen',
location=Within(Box(151650, 214675, 151750, 214775)),
geometry_column='geom')

xml2 = owsutil.wfs_build_getfeature_request(
'dov-pub:Boringen',
location=Within(Box(151650, 214675, 151750, 214775)),
geometry_column='geom')

assert etree.tostring(xml1) == etree.tostring(xml2)

def test_wfs_build_getfeature_request_gml_id_unique(self):
"""Test the owsutil.wfs_build_getfeature_request method with a
typename, two boxes and geometry_column.
Test whether the GML ids in the XML of the WFS GetFeature are unique.
"""
xml = owsutil.wfs_build_getfeature_request(
'dov-pub:Boringen',
location=Or([
Within(Box(100000, 120000, 200000, 220000)),
WithinDistance(Box(100000, 120000, 200000, 220000), 10)
]),
geometry_column='geom')

gml_items = xml.findall('.//*[@{http://www.opengis.net/gml/3.2}id]')
gml_ids = [i.get('{http://www.opengis.net/gml/3.2}id') for i in gml_items]

assert len(gml_ids) == len(set(gml_ids))

def test_wfs_build_getfeature_request_propertyname(self):
"""Test the owsutil.wfs_build_getfeature_request method with a list
of propertynames.
Expand Down