-
Notifications
You must be signed in to change notification settings - Fork 275
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
Add POST support to WFS GetFeature (Issue #439) #706
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
ce030f7
adds quick workaround to make filter post request
f-PLT 1343e57
adds working draft of getPOSTGetFeatureRequest
f-PLT 87dead0
Adds documentation and unimplemented args
f-PLT c5241d1
Adds post for wfs 1.1.0 + cleanup
f-PLT dbd0b78
Refactors __init__ for PostRequest
f-PLT 8e4660a
removes print command
f-PLT 4c4ae39
adds unit tests for PostRequest_1_1_0
f-PLT e6c7e1c
adds unit test for PostRequest_2_0_0
f-PLT f618a7c
add fixtures to postrequest tests
f-PLT 942129c
reformat of __init__ and typo fix
f-PLT 48c24b4
Formatting
f-PLT b771823
fix set_featureid according to standard
f-PLT 2f10b50
Add bbox formating function for post
f-PLT b339ef5
fix list args of getPOSTGetFeatureRequest
f-PLT d612439
Removes repetitive condition
f-PLT b56a1c1
Merge branch 'master' of github.com:geopython/OWSLib into wfs-post-ge…
f-PLT 2e33184
Removes unused imports
f-PLT 6a7735e
Add docstring to postrequest.py
f-PLT a688027
Add featureversion to postrequest.py
f-PLT e9bd373
Add storedquery to postrequest.py + refactor
f-PLT f96da4e
Remove spaces from tag names
f-PLT 91fc15f
add test for stored query
f-PLT 17e9e5d
add check to stored query for version
f-PLT 4943782
Modified docstring for storedQueries
f-PLT c64de7c
Moved propertyname='*' from def to inside method
f-PLT 9ba9b1f
Removed uncessary conditional check
f-PLT 8fb987b
Change docstring for better wording
f-PLT File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# owslib imports: | ||
from owslib import util | ||
from owslib.etree import etree | ||
from owslib.namespaces import Namespaces | ||
|
||
n = Namespaces() | ||
FES_NAMESPACE = n.get_namespace("fes") | ||
GML_NAMESPACE = n.get_namespace("gml") | ||
GML32_NAMESPACE = n.get_namespace("gml32") | ||
OGC_NAMESPACE = n.get_namespace("ogc") | ||
WFS_NAMESPACE = n.get_namespace("wfs") | ||
WFS20_NAMESPACE = n.get_namespace("wfs20") | ||
|
||
|
||
class PostRequest(): | ||
"""Superclass for POST request building""" | ||
|
||
def __init__(self, version=None, namespace=None): | ||
self._root = etree.Element(util.nspath('GetFeature', namespace)) | ||
self._root.set("service", "WFS") | ||
self._root.set("version", version) | ||
self._wfsnamespace = namespace | ||
self._query = None | ||
|
||
def _create_query(self, typename): | ||
self._query = etree.SubElement(self._root, util.nspath('Query', self._wfsnamespace)) | ||
|
||
def set_featureversion(self, version): | ||
self._query.set("featureVersion", version) | ||
|
||
def set_propertyname(self, propertyname): | ||
"""Set which feature properties will be returned. | ||
|
||
If not set, will return all properties.""" | ||
for pn in propertyname: | ||
etree.SubElement(self._query, "PropertyName").text = pn | ||
|
||
def set_startindex(self, startindex): | ||
"""Set the starting index value for the request""" | ||
self._root.set("startIndex", str(startindex)) | ||
|
||
def to_string(self): | ||
"""Returns the xml request in string format. | ||
|
||
Required in order to use the request with getfeature()""" | ||
return etree.tostring(self._root) | ||
|
||
|
||
class PostRequest_1_1_0(PostRequest): | ||
"""XML Post request payload builder for WFS version 1.1.0""" | ||
|
||
def __init__(self): | ||
super().__init__(version='1.1.0', namespace=WFS_NAMESPACE) | ||
|
||
def create_query(self, typename): | ||
"""Creates the query tag with the corresponding typenames. | ||
Required element for each request.""" | ||
super()._create_query(typename) | ||
self._query.set("typeName", typename) | ||
|
||
def set_bbox(self, bbox): | ||
"""Set a bbox filter. | ||
|
||
Cannot be used with set_featureid() or set_filter(). | ||
""" | ||
filter_tree = etree.SubElement(self._query, util.nspath('Filter', OGC_NAMESPACE)) | ||
bbox_tree = etree.SubElement(filter_tree, util.nspath('BBOX', OGC_NAMESPACE)) | ||
coords = etree.SubElement(bbox_tree, util.nspath('Envelope', GML_NAMESPACE)) | ||
if len(bbox) > 4: | ||
coords.set('srsName', bbox[4]) | ||
lower = etree.SubElement(coords, util.nspath('lowerCorner', GML_NAMESPACE)) | ||
lower.text = '{} {}'.format(bbox[0], bbox[1]) | ||
|
||
upper = etree.SubElement(coords, util.nspath('upperCorner', GML_NAMESPACE)) | ||
upper.text = '{} {}'.format(bbox[2], bbox[3]) | ||
|
||
def set_featureid(self, featureid): | ||
"""Set filter by feature id. | ||
|
||
Cannot be used with set_bbox() or set_filter(). | ||
""" | ||
feature_tree = etree.SubElement(self._query, util.nspath('Filter', OGC_NAMESPACE)) | ||
|
||
for ft in featureid: | ||
prop_id = etree.Element(util.nspath('GmlObjectId', OGC_NAMESPACE)) | ||
prop_id.set(util.nspath('id', GML_NAMESPACE), ft) | ||
feature_tree.append(prop_id) | ||
|
||
def set_filter(self, filter): | ||
"""Set filter from existing filter. | ||
|
||
Will integrate the filter tag of a provided xml filter to the query being built. | ||
|
||
Cannot be used with set_bbox() or set_featureid(). | ||
""" | ||
f = etree.fromstring(filter) | ||
sub_elem = f.find(util.nspath("Filter", OGC_NAMESPACE)) | ||
self._query.append(sub_elem) | ||
|
||
def set_maxfeatures(self, maxfeatures): | ||
"""Set the maximum number of features to be returned.""" | ||
self._root.set("maxFeatures", str(maxfeatures)) | ||
|
||
def set_outputformat(self, outputFormat): | ||
"""Set the output format. | ||
|
||
Verify the available formats with a GetCapabilites request.""" | ||
self._root.set("outputFormat", outputFormat) | ||
|
||
def set_sortby(self, sortby): | ||
"""Set the properties by which the response will be sorted.""" | ||
sort_tree = etree.SubElement(self._query, util.nspath("SortBy", OGC_NAMESPACE)) | ||
for s in sortby: | ||
prop_elem = etree.SubElement(sort_tree, util.nspath("SortProperty", OGC_NAMESPACE)) | ||
prop_name = etree.SubElement(prop_elem, util.nspath('PropertyName', OGC_NAMESPACE)) | ||
prop_name.text = s | ||
|
||
|
||
class PostRequest_2_0_0(PostRequest): | ||
"""XML Post request payload builder for WFS version 2.0.0.""" | ||
|
||
def __init__(self): | ||
super().__init__(version='2.0.0', namespace=WFS20_NAMESPACE) | ||
|
||
def create_query(self, typename): | ||
"""Creates the query tag with the corresponding typenames. | ||
Required element for each request ecept for stored queries.""" | ||
super()._create_query(typename) | ||
self._query.set("typenames", typename) | ||
|
||
def create_storedquery(self, stored_id, parameters): | ||
"""Create the storedQuery tag and configure it's sub elements and attributes.""" | ||
storedquery = etree.SubElement(self._root, util.nspath('StoredQuery', self._wfsnamespace)) | ||
storedquery.set("id", str(stored_id)) | ||
for param in parameters: | ||
p = etree.SubElement(storedquery, util.nspath('Parameter', self._wfsnamespace)) | ||
p.set("name", param) | ||
p.text = parameters[param] | ||
|
||
def set_bbox(self, bbox): | ||
"""Set a bbox filter. | ||
|
||
Cannot be used with set_featureid() or set_filter(). | ||
""" | ||
filter_tree = etree.SubElement(self._query, util.nspath('Filter', FES_NAMESPACE)) | ||
bbox_tree = etree.SubElement(filter_tree, util.nspath('BBOX', FES_NAMESPACE)) | ||
coords = etree.SubElement(bbox_tree, util.nspath('Envelope', GML32_NAMESPACE)) | ||
if len(bbox) > 4: | ||
coords.set('srsName', bbox[4]) | ||
|
||
lower = etree.SubElement(coords, util.nspath('lowerCorner', GML32_NAMESPACE)) | ||
lower.text = '{} {}'.format(bbox[0], bbox[1]) | ||
|
||
upper = etree.SubElement(coords, util.nspath('upperCorner', GML32_NAMESPACE)) | ||
upper.text = '{} {}'.format(bbox[2], bbox[3]) | ||
|
||
def set_featureid(self, featureid): | ||
"""Set filter by feature id. | ||
|
||
Cannot be used with set_bbox() or set_filter(). | ||
""" | ||
feature_tree = etree.SubElement(self._query, util.nspath('Filter', FES_NAMESPACE)) | ||
for ft in featureid: | ||
prop_id = etree.Element(util.nspath('ResourceId', FES_NAMESPACE)) | ||
prop_id.set('rid', ft) | ||
feature_tree.append(prop_id) | ||
|
||
def set_filter(self, filter): | ||
"""Set filter from existing filter. | ||
|
||
Will integrate the filter tag of a provided xml filter to the current one | ||
being built. | ||
|
||
Cannot be used with set_bbox() or set_featureid(). | ||
""" | ||
f = etree.fromstring(filter) | ||
sub_elem = f.find(util.nspath("Filter", FES_NAMESPACE)) | ||
self._query.append(sub_elem) | ||
|
||
def set_maxfeatures(self, maxfeatures): | ||
"""Set the maximum number of features to be returned.""" | ||
self._root.set("count", str(maxfeatures)) | ||
|
||
def set_outputformat(self, outputFormat): | ||
"""Set the output format. | ||
|
||
Verify the available formats with a GetCapabilites request. | ||
""" | ||
self._root.set("outputformat", outputFormat) | ||
|
||
def set_sortby(self, sortby): | ||
"""Set the properties by which the response will be sorted.""" | ||
sort_tree = etree.SubElement(self._query, util.nspath("SortBy", FES_NAMESPACE)) | ||
for s in sortby: | ||
prop_elem = etree.SubElement(sort_tree, util.nspath("SortProperty", FES_NAMESPACE)) | ||
value = etree.SubElement(prop_elem, util.nspath('ValueReference', FES_NAMESPACE)) | ||
value.text = s |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not understand why using a create_post_request function instead of directly inserting the version-dependent Postrequest object creation. Is this function intended to be used anywhere else ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, my aim here was to make the post request creation more testable. It being separated makes it testable by itself.
I chose to make a factory with create_post_request(), so
getPOSTGetFeatureRequest()
can be used for wfs1.1.0 and 2.0.0 without needing another function that does almost the same thing but not quite, which also seems to be whatgetGetGetFeatureRequest()
is aiming to become.So this way, getFeature(), whether it's with 1.1.0 or 2.0.0, calls the same function, which checks the WFS version, and builds the appropriate request format. It will also make it easier (I hope) to integrate the next WFS version by simply extending the
postrequest.py
module so it can format post requests for that new version. All without needing to changegetPOSTGetFeatureRequest()
or creating a new function.On the plus side,
postrequest.py
can also be used by itself to build a request and use it with resquests.post(), which could be useful in identifying if an error is caused by the request itself or if the problem is somewhere else in OWSLib.