django-gisserver 1.5.0__py3-none-any.whl → 2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/METADATA +14 -4
- django_gisserver-2.0.dist-info/RECORD +66 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/compat.py +23 -0
- gisserver/conf.py +7 -0
- gisserver/db.py +56 -47
- gisserver/exceptions.py +26 -2
- gisserver/extensions/__init__.py +4 -0
- gisserver/{parsers/fes20 → extensions}/functions.py +10 -4
- gisserver/extensions/queries.py +261 -0
- gisserver/features.py +220 -156
- gisserver/geometries.py +32 -37
- gisserver/management/__init__.py +0 -0
- gisserver/management/commands/__init__.py +0 -0
- gisserver/management/commands/loadgeojson.py +291 -0
- gisserver/operations/base.py +122 -308
- gisserver/operations/wfs20.py +423 -337
- gisserver/output/__init__.py +9 -48
- gisserver/output/base.py +178 -139
- gisserver/output/csv.py +65 -74
- gisserver/output/geojson.py +34 -35
- gisserver/output/gml32.py +254 -246
- gisserver/output/iters.py +207 -0
- gisserver/output/results.py +52 -26
- gisserver/output/stored.py +143 -0
- gisserver/output/utils.py +75 -170
- gisserver/output/xmlschema.py +85 -46
- gisserver/parsers/__init__.py +10 -10
- gisserver/parsers/ast.py +320 -0
- gisserver/parsers/fes20/__init__.py +13 -27
- gisserver/parsers/fes20/expressions.py +82 -38
- gisserver/parsers/fes20/filters.py +111 -43
- gisserver/parsers/fes20/identifiers.py +44 -26
- gisserver/parsers/fes20/lookups.py +144 -0
- gisserver/parsers/fes20/operators.py +331 -127
- gisserver/parsers/fes20/sorting.py +104 -33
- gisserver/parsers/gml/__init__.py +12 -11
- gisserver/parsers/gml/base.py +5 -2
- gisserver/parsers/gml/geometries.py +69 -35
- gisserver/parsers/ows/__init__.py +25 -0
- gisserver/parsers/ows/kvp.py +190 -0
- gisserver/parsers/ows/requests.py +158 -0
- gisserver/parsers/query.py +175 -0
- gisserver/parsers/values.py +26 -0
- gisserver/parsers/wfs20/__init__.py +37 -0
- gisserver/parsers/wfs20/adhoc.py +245 -0
- gisserver/parsers/wfs20/base.py +143 -0
- gisserver/parsers/wfs20/projection.py +103 -0
- gisserver/parsers/wfs20/requests.py +482 -0
- gisserver/parsers/wfs20/stored.py +192 -0
- gisserver/parsers/xml.py +249 -0
- gisserver/projection.py +357 -0
- gisserver/static/gisserver/index.css +12 -1
- gisserver/templates/gisserver/index.html +1 -1
- gisserver/templates/gisserver/service_description.html +2 -2
- gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
- gisserver/templates/gisserver/wfs/feature_field.html +2 -2
- gisserver/templatetags/gisserver_tags.py +20 -0
- gisserver/types.py +322 -259
- gisserver/views.py +198 -56
- django_gisserver-1.5.0.dist-info/RECORD +0 -54
- gisserver/parsers/base.py +0 -149
- gisserver/parsers/fes20/query.py +0 -285
- gisserver/parsers/tags.py +0 -102
- gisserver/queries/__init__.py +0 -37
- gisserver/queries/adhoc.py +0 -185
- gisserver/queries/base.py +0 -186
- gisserver/queries/projection.py +0 -240
- gisserver/queries/stored.py +0 -206
- gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
- gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.0.dist-info}/top_level.txt +0 -0
gisserver/operations/wfs20.py
CHANGED
|
@@ -8,34 +8,34 @@ Useful docs:
|
|
|
8
8
|
* https://enonline.supermap.com/iExpress9D/API/WFS/WFS200/WFS_2.0.0_introduction.htm
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
11
13
|
import logging
|
|
12
14
|
import math
|
|
13
15
|
import re
|
|
16
|
+
import typing
|
|
14
17
|
from urllib.parse import urlencode
|
|
15
18
|
|
|
16
|
-
from django.core.exceptions import FieldError, ValidationError
|
|
19
|
+
from django.core.exceptions import FieldError, ImproperlyConfigured, ValidationError
|
|
17
20
|
from django.db import InternalError, ProgrammingError
|
|
21
|
+
from django.db.models import QuerySet
|
|
22
|
+
from django.utils.module_loading import import_string
|
|
18
23
|
|
|
19
|
-
from gisserver import conf, output
|
|
24
|
+
from gisserver import conf, output
|
|
20
25
|
from gisserver.exceptions import (
|
|
21
26
|
ExternalParsingError,
|
|
22
27
|
ExternalValueError,
|
|
23
28
|
InvalidParameterValue,
|
|
24
|
-
MissingParameterValue,
|
|
25
29
|
OperationParsingFailed,
|
|
26
30
|
VersionNegotiationFailed,
|
|
27
31
|
)
|
|
28
|
-
from gisserver.
|
|
29
|
-
from gisserver.
|
|
30
|
-
from gisserver.
|
|
31
|
-
|
|
32
|
-
from .
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
UnsupportedParameter,
|
|
36
|
-
WFSMethod,
|
|
37
|
-
WFSTypeNamesMethod,
|
|
38
|
-
)
|
|
32
|
+
from gisserver.extensions.functions import function_registry
|
|
33
|
+
from gisserver.extensions.queries import stored_query_registry
|
|
34
|
+
from gisserver.features import FeatureType
|
|
35
|
+
from gisserver.output import CollectionOutputRenderer
|
|
36
|
+
from gisserver.parsers import ows, wfs20
|
|
37
|
+
|
|
38
|
+
from .base import OutputFormat, OutputFormatMixin, Parameter, WFSOperation, XmlTemplateMixin
|
|
39
39
|
|
|
40
40
|
logger = logging.getLogger(__name__)
|
|
41
41
|
|
|
@@ -43,65 +43,53 @@ SAFE_VERSION = re.compile(r"\A[0-9.]+\Z")
|
|
|
43
43
|
RE_SAFE_FILENAME = re.compile(r"\A[A-Za-z0-9]+[A-Za-z0-9.]*") # no dot at the start.
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class GetCapabilities(
|
|
46
|
+
class GetCapabilities(XmlTemplateMixin, OutputFormatMixin, WFSOperation):
|
|
47
47
|
"""This operation returns map features, and available operations this WFS server supports."""
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
OutputFormat(
|
|
52
|
-
# for FME (Feature Manipulation Engine)
|
|
53
|
-
"application/gml+xml",
|
|
54
|
-
version="3.2",
|
|
55
|
-
title="GML",
|
|
56
|
-
in_capabilities=False,
|
|
57
|
-
),
|
|
58
|
-
]
|
|
49
|
+
ows_request: wfs20.GetCapabilities
|
|
50
|
+
|
|
59
51
|
xml_template_name = "get_capabilities.xml"
|
|
60
52
|
|
|
61
|
-
def
|
|
62
|
-
# Not calling super, as this differs slightly (e.g. no output formats)
|
|
53
|
+
def get_output_formats(self):
|
|
63
54
|
return [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
OutputFormat("text/xml", renderer_class=None),
|
|
56
|
+
OutputFormat(
|
|
57
|
+
# for FME (Feature Manipulation Engine)
|
|
58
|
+
"application/gml+xml",
|
|
59
|
+
renderer_class=None,
|
|
60
|
+
version="3.2",
|
|
61
|
+
title="GML",
|
|
62
|
+
in_capabilities=False,
|
|
69
63
|
),
|
|
70
|
-
|
|
71
|
-
# Version parameter can still be sent,
|
|
72
|
-
# but not mandatory for GetCapabilities
|
|
73
|
-
"version",
|
|
74
|
-
allowed_values=self.view.accept_versions,
|
|
75
|
-
),
|
|
76
|
-
Parameter(
|
|
77
|
-
"AcceptVersions",
|
|
78
|
-
in_capabilities=True,
|
|
79
|
-
allowed_values=self.view.accept_versions,
|
|
80
|
-
parser=self._parse_accept_versions,
|
|
81
|
-
),
|
|
82
|
-
Parameter(
|
|
83
|
-
"AcceptFormats",
|
|
84
|
-
in_capabilities=True,
|
|
85
|
-
allowed_values=self.output_formats,
|
|
86
|
-
parser=self._parse_output_format,
|
|
87
|
-
default=self.output_formats[0],
|
|
88
|
-
),
|
|
89
|
-
] + self.parameters
|
|
64
|
+
]
|
|
90
65
|
|
|
91
|
-
def
|
|
66
|
+
def get_parameters(self):
|
|
67
|
+
# Not calling super, as this differs slightly (not requiring VERSION)
|
|
68
|
+
return [
|
|
69
|
+
Parameter("service", allowed_values=list(self.view.accept_operations.keys())),
|
|
70
|
+
Parameter("AcceptVersions", allowed_values=self.view.accept_versions),
|
|
71
|
+
Parameter("AcceptFormats", allowed_values=[str(o) for o in self.get_output_formats()]),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def validate_request(self, ows_request: wfs20.GetCapabilities):
|
|
75
|
+
# Validate AcceptVersions
|
|
76
|
+
self._parse_accept_versions(ows_request)
|
|
77
|
+
|
|
78
|
+
# Validate AcceptFormats
|
|
79
|
+
if ows_request.acceptFormats:
|
|
80
|
+
for output_format in ows_request.acceptFormats:
|
|
81
|
+
self.resolve_output_format(output_format, locator="AcceptFormats")
|
|
82
|
+
|
|
83
|
+
def _parse_accept_versions(self, ows_request: wfs20.GetCapabilities) -> str | None:
|
|
92
84
|
"""Special parsing for the ACCEPTVERSIONS parameter."""
|
|
93
|
-
if
|
|
94
|
-
|
|
95
|
-
raise InvalidParameterValue(
|
|
96
|
-
"Can't provide both ACCEPTVERSIONS and VERSION",
|
|
97
|
-
locator="AcceptVersions",
|
|
98
|
-
)
|
|
85
|
+
if not ows_request.acceptVersions:
|
|
86
|
+
return None
|
|
99
87
|
|
|
100
|
-
matched_versions = set(
|
|
88
|
+
matched_versions = set(ows_request.acceptVersions).intersection(self.view.accept_versions)
|
|
101
89
|
if not matched_versions:
|
|
102
90
|
allowed = ", ".join(self.view.accept_versions)
|
|
103
91
|
raise VersionNegotiationFailed(
|
|
104
|
-
f"'{
|
|
92
|
+
f"'{','.join(ows_request.acceptVersions)}' does not contain supported versions, "
|
|
105
93
|
f"supported are: {allowed}.",
|
|
106
94
|
locator="acceptversions",
|
|
107
95
|
)
|
|
@@ -110,269 +98,275 @@ class GetCapabilities(WFSMethod):
|
|
|
110
98
|
requested_version = sorted(matched_versions, reverse=True)[0]
|
|
111
99
|
|
|
112
100
|
# Make sure the views+exceptions continue to operate in this version
|
|
113
|
-
self.view.set_version(requested_version)
|
|
114
|
-
|
|
101
|
+
self.view.set_version(ows_request.service, requested_version)
|
|
115
102
|
return requested_version
|
|
116
103
|
|
|
117
|
-
def
|
|
118
|
-
"""GetCapabilities only supports XML output"""
|
|
119
|
-
context = self.get_context_data(**params)
|
|
120
|
-
return self.render_xml(context, **params)
|
|
121
|
-
|
|
122
|
-
def get_context_data(self, **params):
|
|
123
|
-
view = self.view
|
|
124
|
-
|
|
104
|
+
def get_context_data(self) -> dict:
|
|
125
105
|
# The 'service' is not read from 'params' to avoid dependency on get_parameters()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
106
|
+
service_operations = self.view.accept_operations[self.ows_request.service]
|
|
107
|
+
app_namespaces = self.view.get_xml_namespaces_to_prefixes()
|
|
108
|
+
|
|
109
|
+
get_feature_op_cls = service_operations["GetFeature"]
|
|
110
|
+
feature_output_formats = get_feature_op_cls(self.view, None).get_output_formats()
|
|
129
111
|
|
|
130
112
|
return {
|
|
131
|
-
"
|
|
113
|
+
"xml_namespaces": app_namespaces,
|
|
114
|
+
"service_description": self.view.get_service_description(self.ows_request.service),
|
|
132
115
|
"accept_operations": {
|
|
133
|
-
name:
|
|
116
|
+
name: operation_class(self.view, None).get_parameters()
|
|
117
|
+
for name, operation_class in service_operations.items()
|
|
134
118
|
},
|
|
135
119
|
"service_constraints": self.view.wfs_service_constraints,
|
|
136
120
|
"filter_capabilities": self.view.wfs_filter_capabilities,
|
|
137
|
-
"function_registry":
|
|
121
|
+
"function_registry": function_registry,
|
|
138
122
|
"accept_versions": self.view.accept_versions,
|
|
139
|
-
"feature_types": self.view.
|
|
123
|
+
"feature_types": self.view.get_bound_feature_types(),
|
|
140
124
|
"feature_output_formats": feature_output_formats,
|
|
141
125
|
"default_max_features": self.view.max_page_size,
|
|
142
126
|
"BOUNDING_BOX": conf.GISSERVER_CAPABILITIES_BOUNDING_BOX,
|
|
143
127
|
}
|
|
144
128
|
|
|
145
129
|
|
|
146
|
-
class DescribeFeatureType(
|
|
130
|
+
class DescribeFeatureType(OutputFormatMixin, WFSOperation):
|
|
147
131
|
"""This returns an XML Schema for the provided objects.
|
|
148
132
|
Each feature is exposed as an XSD definition with its fields.
|
|
149
133
|
"""
|
|
150
134
|
|
|
151
|
-
|
|
152
|
-
OutputFormat("XMLSCHEMA", renderer_class=output.XMLSchemaRenderer),
|
|
153
|
-
# At least one version of FME (Feature Manipulation Engine) seems to
|
|
154
|
-
# send a DescribeFeatureType request with this GML as output format.
|
|
155
|
-
# Do what mapserver does and just send it XML Schema.
|
|
156
|
-
OutputFormat(
|
|
157
|
-
"application/gml+xml",
|
|
158
|
-
version="3.2",
|
|
159
|
-
renderer_class=output.XMLSchemaRenderer,
|
|
160
|
-
in_capabilities=False,
|
|
161
|
-
),
|
|
162
|
-
# OutputFormat("text/xml", subtype="gml/3.1.1"),
|
|
163
|
-
]
|
|
164
|
-
|
|
165
|
-
def get_context_data(self, typeNames, **params):
|
|
166
|
-
if self.view.KVP.get("TYPENAMES") == "" or self.view.KVP.get("TYPENAME") == "":
|
|
167
|
-
# Using TYPENAMES= does result in an error.
|
|
168
|
-
raise MissingParameterValue("Empty TYPENAMES parameter", locator="typeNames")
|
|
169
|
-
elif typeNames is None:
|
|
170
|
-
# Not given, all types are returned
|
|
171
|
-
typeNames = self.all_feature_types
|
|
172
|
-
|
|
173
|
-
return {"feature_types": typeNames}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
class ListStoredQueries(WFSMethod):
|
|
177
|
-
"""This describes the available queries"""
|
|
135
|
+
ows_request: wfs20.DescribeFeatureType
|
|
178
136
|
|
|
179
|
-
|
|
137
|
+
def get_output_formats(self):
|
|
138
|
+
return [
|
|
139
|
+
OutputFormat("XMLSCHEMA", renderer_class=output.XmlSchemaRenderer),
|
|
140
|
+
# At least one version of FME (Feature Manipulation Engine) seems to
|
|
141
|
+
# send a DescribeFeatureType request with this GML as output format.
|
|
142
|
+
# Do what mapserver does and just send it XML Schema.
|
|
143
|
+
# This output format also seems to be used in the WFS 2.0.2 spec!
|
|
144
|
+
OutputFormat(
|
|
145
|
+
"application/gml+xml",
|
|
146
|
+
version="3.2",
|
|
147
|
+
renderer_class=output.XmlSchemaRenderer,
|
|
148
|
+
in_capabilities=False,
|
|
149
|
+
),
|
|
150
|
+
# OutputFormat("text/xml", subtype="gml/3.1.1"),
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
def validate_request(self, ows_request: wfs20.DescribeFeatureType):
|
|
154
|
+
if not ows_request.typeNames:
|
|
155
|
+
self.feature_types = self.view.get_bound_feature_types()
|
|
156
|
+
else:
|
|
157
|
+
self.feature_types = [
|
|
158
|
+
self.resolve_feature_type(type_name) for type_name in ows_request.typeNames
|
|
159
|
+
]
|
|
160
|
+
self.output_format = self.resolve_output_format(ows_request.outputFormat)
|
|
180
161
|
|
|
181
|
-
def
|
|
182
|
-
|
|
162
|
+
def process_request(self, ows_request: wfs20.DescribeFeatureType):
|
|
163
|
+
renderer_class = self.output_format.renderer_class or output.XmlSchemaRenderer
|
|
164
|
+
renderer = renderer_class(self, self.feature_types)
|
|
165
|
+
return renderer.get_response()
|
|
183
166
|
|
|
184
167
|
|
|
185
|
-
class
|
|
168
|
+
class ListStoredQueries(WFSOperation):
|
|
186
169
|
"""This describes the available queries"""
|
|
187
170
|
|
|
188
|
-
|
|
171
|
+
ows_request: wfs20.ListStoredQueries
|
|
189
172
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
stored_query_registry.resolve_query(name) for name in value.split(",")
|
|
195
|
-
],
|
|
173
|
+
def process_request(self, ows_request: ows.BaseOwsRequest):
|
|
174
|
+
renderer = output.ListStoredQueriesRenderer(
|
|
175
|
+
self,
|
|
176
|
+
query_descriptions=stored_query_registry.get_queries(),
|
|
196
177
|
)
|
|
197
|
-
|
|
178
|
+
return renderer.get_response()
|
|
198
179
|
|
|
199
|
-
def get_context_data(self, **params):
|
|
200
|
-
queries = params["STOREDQUERY_ID"] or list(stored_query_registry)
|
|
201
|
-
return {
|
|
202
|
-
"feature_types": self.view.get_feature_types(),
|
|
203
|
-
"stored_queries": [q.meta for q in queries],
|
|
204
|
-
}
|
|
205
180
|
|
|
181
|
+
class DescribeStoredQueries(WFSOperation):
|
|
182
|
+
"""This describes the available queries"""
|
|
206
183
|
|
|
207
|
-
|
|
184
|
+
ows_request: wfs20.DescribeStoredQueries
|
|
185
|
+
|
|
186
|
+
def validate_request(self, ows_request: wfs20.DescribeStoredQueries):
|
|
187
|
+
if ows_request.storedQueryId is None:
|
|
188
|
+
self.query_descriptions = stored_query_registry.get_queries()
|
|
189
|
+
else:
|
|
190
|
+
self.query_descriptions = [
|
|
191
|
+
stored_query_registry.resolve_query(query_id)
|
|
192
|
+
for query_id in ows_request.storedQueryId
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def process_request(self, ows_request: ows.BaseOwsRequest):
|
|
196
|
+
renderer = output.DescribeStoredQueriesRenderer(self, self.query_descriptions)
|
|
197
|
+
return renderer.get_response()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class BaseWFSGetDataOperation(OutputFormatMixin, WFSOperation):
|
|
208
201
|
"""Base class for GetFeature / GetPropertyValue"""
|
|
209
202
|
|
|
210
|
-
|
|
211
|
-
# StandardPresentationParameters
|
|
212
|
-
Parameter(
|
|
213
|
-
"resultType",
|
|
214
|
-
in_capabilities=True,
|
|
215
|
-
parser=lambda x: x.upper(),
|
|
216
|
-
allowed_values=["RESULTS", "HITS"],
|
|
217
|
-
default="RESULTS",
|
|
218
|
-
),
|
|
219
|
-
Parameter("startIndex", parser=int, default=0),
|
|
220
|
-
Parameter("count", alias="maxFeatures", parser=int), # maxFeatures is WFS 1.x
|
|
221
|
-
# outputFormat will be added by the base class.
|
|
222
|
-
# StandardResolveParameters
|
|
223
|
-
Parameter("resolve", allowed_values=["local"], in_capabilities=True),
|
|
224
|
-
UnsupportedParameter("resolveDepth"), # subresource settings
|
|
225
|
-
UnsupportedParameter("resolveTimeout"),
|
|
226
|
-
# StandardInputParameters
|
|
227
|
-
Parameter("srsName", parser=CRS.from_string),
|
|
228
|
-
# Projection clause parameters
|
|
229
|
-
Parameter("propertyName", parser=fes20.parse_property_name),
|
|
230
|
-
# AdHoc Query parameters
|
|
231
|
-
Parameter("bbox", parser=BoundingBox.from_string),
|
|
232
|
-
Parameter(
|
|
233
|
-
"filter_language",
|
|
234
|
-
default=fes20.Filter.query_language,
|
|
235
|
-
allowed_values=[fes20.Filter.query_language],
|
|
236
|
-
),
|
|
237
|
-
Parameter("filter", parser=fes20.Filter.from_string),
|
|
238
|
-
Parameter("sortBy", parser=fes20.SortBy.from_string),
|
|
239
|
-
Parameter("resourceID", parser=fes20.parse_resource_id_kvp),
|
|
240
|
-
UnsupportedParameter("aliases"),
|
|
241
|
-
queries.StoredQueryParameter(),
|
|
242
|
-
]
|
|
243
|
-
|
|
244
|
-
def get_context_data(self, resultType, **params): # noqa: C901
|
|
245
|
-
query = self.get_query(**params)
|
|
246
|
-
query.check_permissions(self.view.request)
|
|
203
|
+
ows_request: wfs20.GetFeature | wfs20.GetPropertyValue
|
|
247
204
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
collection = query.get_hits()
|
|
251
|
-
elif resultType == "RESULTS":
|
|
252
|
-
# Validate StandardPresentationParameters
|
|
253
|
-
collection = self.get_paginated_results(query, **params)
|
|
254
|
-
else:
|
|
255
|
-
raise NotImplementedError()
|
|
256
|
-
except ExternalParsingError as e:
|
|
257
|
-
# Bad input data
|
|
258
|
-
self._log_filter_error(query, logging.ERROR, e)
|
|
259
|
-
raise OperationParsingFailed(str(e), locator=self._get_locator(**params)) from e
|
|
260
|
-
except ExternalValueError as e:
|
|
261
|
-
# Bad input data
|
|
262
|
-
self._log_filter_error(query, logging.ERROR, e)
|
|
263
|
-
raise InvalidParameterValue(str(e), locator=self._get_locator(**params)) from e
|
|
264
|
-
except ValidationError as e:
|
|
265
|
-
# Bad input data
|
|
266
|
-
self._log_filter_error(query, logging.ERROR, e)
|
|
267
|
-
raise OperationParsingFailed(
|
|
268
|
-
"\n".join(map(str, e.messages)),
|
|
269
|
-
locator=self._get_locator(**params),
|
|
270
|
-
) from e
|
|
271
|
-
except FieldError as e:
|
|
272
|
-
# e.g. doing a LIKE on a foreign key, or requesting an unknown field.
|
|
273
|
-
if not conf.GISSERVER_WRAP_FILTER_DB_ERRORS:
|
|
274
|
-
raise
|
|
275
|
-
self._log_filter_error(query, logging.ERROR, e)
|
|
276
|
-
raise InvalidParameterValue(
|
|
277
|
-
"Internal error when processing filter",
|
|
278
|
-
locator=self._get_locator(**params),
|
|
279
|
-
) from e
|
|
280
|
-
except (InternalError, ProgrammingError) as e:
|
|
281
|
-
# e.g. comparing datetime against integer
|
|
282
|
-
if not conf.GISSERVER_WRAP_FILTER_DB_ERRORS:
|
|
283
|
-
raise
|
|
284
|
-
logger.exception("WFS request failed: %s\nParams: %r", str(e), params)
|
|
285
|
-
msg = str(e)
|
|
286
|
-
locator = "srsName" if "Cannot find SRID" in msg else self._get_locator(**params)
|
|
287
|
-
raise InvalidParameterValue(f"Invalid request: {msg}", locator=locator) from e
|
|
288
|
-
except (TypeError, ValueError) as e:
|
|
289
|
-
# TypeError/ValueError could reference a datatype mismatch in an
|
|
290
|
-
# ORM query, but it could also be an internal bug. In most cases,
|
|
291
|
-
# this is already caught by XsdElement.validate_comparison().
|
|
292
|
-
if self._is_orm_error(e):
|
|
293
|
-
raise InvalidParameterValue(
|
|
294
|
-
f"Invalid filter query: {e}",
|
|
295
|
-
locator=self._get_locator(**params),
|
|
296
|
-
) from e
|
|
297
|
-
raise
|
|
205
|
+
if typing.TYPE_CHECKING:
|
|
206
|
+
# Tell the type checker subclasses will only work with CollectionOutputRenderer rendering.
|
|
298
207
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
208
|
+
def resolve_output_format(
|
|
209
|
+
self, value, locator="outputFormat"
|
|
210
|
+
) -> OutputFormat[CollectionOutputRenderer]:
|
|
211
|
+
return super().resolve_output_format(value, locator=locator)
|
|
302
212
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
"
|
|
308
|
-
|
|
213
|
+
def get_parameters(self) -> list[Parameter]:
|
|
214
|
+
"""Parameters to advertise in the capabilities for this method."""
|
|
215
|
+
return [
|
|
216
|
+
# Advertise parameters in GetCapabilities
|
|
217
|
+
Parameter("resultType", allowed_values=["RESULTS", "HITS"]),
|
|
218
|
+
Parameter("resolve", allowed_values=["local"]),
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
def validate_request(self, ows_request: wfs20.GetFeature | wfs20.GetPropertyValue):
|
|
222
|
+
"""Validate the incoming data before execution."""
|
|
223
|
+
self.output_format = self.resolve_output_format(ows_request.outputFormat)
|
|
224
|
+
|
|
225
|
+
# Resolve the feature types
|
|
226
|
+
# Allow these to skip, e.g. when the query needs to return 0 results (GetFeatureById).
|
|
227
|
+
for query in ows_request.queries:
|
|
228
|
+
type_names = query.get_type_names()
|
|
229
|
+
feature_types = [
|
|
230
|
+
self.resolve_feature_type(
|
|
231
|
+
type_name,
|
|
232
|
+
locator=("resourceId" if query.query_locator == "resourceId" else "typeNames"),
|
|
233
|
+
)
|
|
234
|
+
for type_name in type_names
|
|
235
|
+
]
|
|
236
|
+
self.bind_query(query, feature_types)
|
|
237
|
+
|
|
238
|
+
# Allow both the view and feature-type to check for access.
|
|
239
|
+
# Before 2.0 only FeatureType classes offered this, which required subclassing
|
|
240
|
+
# FeatureType to access view.request.user. The direct view check avoids that need.
|
|
241
|
+
for feature_type in feature_types:
|
|
242
|
+
self.view.check_permissions(feature_type)
|
|
243
|
+
feature_type.check_permissions(self.view.request)
|
|
244
|
+
|
|
245
|
+
def bind_query(self, query: wfs20.QueryExpression, feature_types: list[FeatureType]):
|
|
246
|
+
"""Allow to be overwritten in GetFeatureValue"""
|
|
247
|
+
query.bind(feature_types)
|
|
248
|
+
|
|
249
|
+
def process_request(self, ows_request: wfs20.GetFeature | wfs20.GetPropertyValue):
|
|
250
|
+
"""Process the query, and generate the output."""
|
|
251
|
+
# Initialize the collection, which constructs the ORM querysets.
|
|
252
|
+
if ows_request.resultType == wfs20.ResultType.hits:
|
|
253
|
+
collection = self.get_hits()
|
|
254
|
+
elif self.ows_request.resultType == wfs20.ResultType.results:
|
|
255
|
+
collection = self.get_results()
|
|
256
|
+
else:
|
|
257
|
+
raise NotImplementedError()
|
|
258
|
+
|
|
259
|
+
# Initialize the renderer.
|
|
260
|
+
# This can also decorate the querysets with projection information,
|
|
261
|
+
# such as converting geometries to the correct CRS or add prefetch_related logic.
|
|
262
|
+
renderer = self.output_format.renderer_class(operation=self, collection=collection)
|
|
309
263
|
|
|
310
|
-
|
|
311
|
-
|
|
264
|
+
# Fixing pagination will invoke the query,
|
|
265
|
+
# hence this is done at the end
|
|
266
|
+
self.set_pagination_links(collection)
|
|
312
267
|
|
|
313
|
-
|
|
314
|
-
|
|
268
|
+
# Render it!
|
|
269
|
+
return renderer.get_response()
|
|
270
|
+
|
|
271
|
+
def get_hits(self) -> output.FeatureCollection:
|
|
272
|
+
"""Handle the resultType=hits query.
|
|
273
|
+
This creates the QuerySet and counts the number of results.
|
|
315
274
|
"""
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
275
|
+
start, count = self.get_pagination()
|
|
276
|
+
results = []
|
|
277
|
+
for query in self.ows_request.queries:
|
|
278
|
+
queryset = self._get_queryset(query)
|
|
279
|
+
results.append(
|
|
280
|
+
output.SimpleFeatureCollection(
|
|
281
|
+
source_query=query,
|
|
282
|
+
feature_types=query.feature_types,
|
|
283
|
+
queryset=queryset.none(),
|
|
284
|
+
start=start,
|
|
285
|
+
stop=start + count, # yes, count can be passed for hits
|
|
286
|
+
number_matched=queryset.count(),
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return output.FeatureCollection(
|
|
291
|
+
results=results,
|
|
292
|
+
number_matched=sum(r.number_matched for r in results),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def get_results(self) -> output.FeatureCollection:
|
|
296
|
+
"""Handle the resultType=results query.
|
|
297
|
+
This creates the queryset, allowing to read over all results.
|
|
298
|
+
"""
|
|
299
|
+
start, count = self.get_pagination()
|
|
300
|
+
results = []
|
|
301
|
+
for query in self.ows_request.queries:
|
|
302
|
+
# The querysets are not executed yet, until the output is reading them.
|
|
303
|
+
queryset = self._get_queryset(query)
|
|
304
|
+
results.append(
|
|
305
|
+
output.SimpleFeatureCollection(
|
|
306
|
+
source_query=query,
|
|
307
|
+
feature_types=query.feature_types,
|
|
308
|
+
queryset=queryset,
|
|
309
|
+
start=start,
|
|
310
|
+
stop=start + count,
|
|
311
|
+
)
|
|
325
312
|
)
|
|
326
|
-
else:
|
|
327
|
-
query = queries.AdhocQuery.from_kvp_request(index=0, **params)
|
|
328
|
-
query.bind(all_feature_types=self.all_feature_types_by_name)
|
|
329
313
|
|
|
330
|
-
|
|
314
|
+
# number_matched is not given here, so some rendering formats can count it instead.
|
|
315
|
+
# For GML it need to be printed at the start, but for GeoJSON it can be rendered
|
|
316
|
+
# as the last bit of the response. That avoids performing an expensive COUNT query.
|
|
317
|
+
return output.FeatureCollection(results=results)
|
|
331
318
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
"""Handle pagination settings."""
|
|
336
|
-
start = max(0, params["startIndex"])
|
|
319
|
+
def get_pagination(self) -> tuple[int, int]:
|
|
320
|
+
"""Tell what the requested page size is."""
|
|
321
|
+
start = max(0, self.ows_request.startIndex)
|
|
337
322
|
|
|
338
323
|
# outputFormat.max_page_size can be math.inf to enable endless scrolling.
|
|
339
324
|
# this only works when the COUNT parameter is not given.
|
|
340
|
-
max_page_size =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# Allow presentation-layer to add extra logic.
|
|
348
|
-
if outputFormat.renderer_class is not None:
|
|
349
|
-
output_crs = params["srsName"]
|
|
350
|
-
if not output_crs and collection.results:
|
|
351
|
-
output_crs = collection.results[0].feature_type.crs
|
|
325
|
+
max_page_size = self.output_format.max_page_size or self.view.max_page_size
|
|
326
|
+
default_page_size = (
|
|
327
|
+
0 if self.ows_request.resultType == wfs20.ResultType.hits else max_page_size
|
|
328
|
+
)
|
|
329
|
+
count = min(max_page_size, self.ows_request.count or default_page_size)
|
|
330
|
+
return start, count
|
|
352
331
|
|
|
353
|
-
|
|
332
|
+
def set_pagination_links(self, collection: output.FeatureCollection):
|
|
333
|
+
"""Assign the pagination links to the collection.
|
|
334
|
+
This happens within the operation logic, as it can access the original GET request.
|
|
335
|
+
"""
|
|
336
|
+
if self.ows_request.resultType == wfs20.ResultType.hits and not self.ows_request.count:
|
|
337
|
+
return
|
|
354
338
|
|
|
339
|
+
start, count = self.get_pagination()
|
|
340
|
+
stop = start + count
|
|
355
341
|
if stop != math.inf:
|
|
356
342
|
if start > 0:
|
|
357
343
|
collection.previous = self._replace_url_params(
|
|
358
|
-
STARTINDEX=max(0, start -
|
|
359
|
-
COUNT=
|
|
344
|
+
STARTINDEX=max(0, start - count),
|
|
345
|
+
COUNT=count,
|
|
360
346
|
)
|
|
347
|
+
|
|
348
|
+
# Note that reading collection.has_next will invoke the query!
|
|
361
349
|
if collection.has_next:
|
|
362
350
|
# TODO: fix this when returning multiple typeNames:
|
|
363
351
|
collection.next = self._replace_url_params(
|
|
364
|
-
STARTINDEX=start +
|
|
365
|
-
COUNT=
|
|
352
|
+
STARTINDEX=start + count,
|
|
353
|
+
COUNT=count,
|
|
366
354
|
)
|
|
367
355
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
def _replace_url_params(self, **updates) -> str:
|
|
356
|
+
def _replace_url_params(self, **updates) -> str | None:
|
|
371
357
|
"""Replace a query parameter in the URL"""
|
|
372
358
|
new_params = self.view.request.GET.copy() # preserve lowercase fields too
|
|
359
|
+
if self.view.request.method != "GET":
|
|
360
|
+
# CITE compliance testing wants to see a 'next' link for POST requests too.
|
|
361
|
+
try:
|
|
362
|
+
new_params.update(self.ows_request.as_kvp())
|
|
363
|
+
except NotImplementedError:
|
|
364
|
+
# Various POST requests can't be translated back to KVP
|
|
365
|
+
# mapserver omits the 'next' link in these cases too.
|
|
366
|
+
return None
|
|
373
367
|
|
|
374
368
|
# Replace any lower/mixed case variants of the previous names:
|
|
375
|
-
for name in
|
|
369
|
+
for name in new_params:
|
|
376
370
|
upper = name.upper()
|
|
377
371
|
if upper in updates:
|
|
378
372
|
new_params[name] = updates.pop(upper)
|
|
@@ -381,6 +375,58 @@ class BaseWFSGetDataMethod(WFSTypeNamesMethod):
|
|
|
381
375
|
new_params.update(updates)
|
|
382
376
|
return f"{self.view.server_url}?{urlencode(new_params)}"
|
|
383
377
|
|
|
378
|
+
def _get_queryset(self, query: wfs20.QueryExpression) -> QuerySet: # noqa:C901
|
|
379
|
+
"""Generate the queryset, and trap many parser errors in the making of it."""
|
|
380
|
+
try:
|
|
381
|
+
return query.get_queryset()
|
|
382
|
+
except ExternalParsingError as e:
|
|
383
|
+
# Bad input data
|
|
384
|
+
self._log_filter_error(query, logging.ERROR, e)
|
|
385
|
+
raise OperationParsingFailed(str(e), locator=query.query_locator) from e
|
|
386
|
+
except ExternalValueError as e:
|
|
387
|
+
# Bad input data
|
|
388
|
+
self._log_filter_error(query, logging.ERROR, e)
|
|
389
|
+
raise InvalidParameterValue(str(e), locator=query.query_locator) from e
|
|
390
|
+
except ValidationError as e:
|
|
391
|
+
# Bad input data
|
|
392
|
+
self._log_filter_error(query, logging.ERROR, e)
|
|
393
|
+
raise OperationParsingFailed(
|
|
394
|
+
"\n".join(map(str, e.messages)),
|
|
395
|
+
locator=query.query_locator,
|
|
396
|
+
) from e
|
|
397
|
+
except FieldError as e:
|
|
398
|
+
# e.g. doing a LIKE on a foreign key, or requesting an unknown field.
|
|
399
|
+
if not conf.GISSERVER_WRAP_FILTER_DB_ERRORS:
|
|
400
|
+
raise
|
|
401
|
+
self._log_filter_error(query, logging.ERROR, e)
|
|
402
|
+
raise InvalidParameterValue(
|
|
403
|
+
"Internal error when processing filter",
|
|
404
|
+
locator=query.query_locator,
|
|
405
|
+
) from e
|
|
406
|
+
except (InternalError, ProgrammingError) as e:
|
|
407
|
+
# e.g. comparing datetime against integer
|
|
408
|
+
if not conf.GISSERVER_WRAP_FILTER_DB_ERRORS:
|
|
409
|
+
raise
|
|
410
|
+
logger.exception("WFS request failed: %s\nRequest: %r", str(e), self.ows_request)
|
|
411
|
+
msg = str(e)
|
|
412
|
+
locator = "srsName" if "Cannot find SRID" in msg else query.query_locator
|
|
413
|
+
raise InvalidParameterValue(f"Invalid request: {msg}", locator=locator) from e
|
|
414
|
+
except (TypeError, ValueError) as e:
|
|
415
|
+
# TypeError/ValueError could reference a datatype mismatch in an
|
|
416
|
+
# ORM query, but it could also be an internal bug. In most cases,
|
|
417
|
+
# this is already caught by XsdElement.validate_comparison().
|
|
418
|
+
if self._is_orm_error(e):
|
|
419
|
+
if query.query_locator == "STOREDQUERY_ID":
|
|
420
|
+
# This is a fallback, ideally the stored query performs its own validation.
|
|
421
|
+
raise InvalidParameterValue(
|
|
422
|
+
f"Invalid stored query parameter: {e}", locator=query.query_locator
|
|
423
|
+
) from e
|
|
424
|
+
else:
|
|
425
|
+
raise InvalidParameterValue(
|
|
426
|
+
f"Invalid filter query: {e}", locator=query.query_locator
|
|
427
|
+
) from e
|
|
428
|
+
raise
|
|
429
|
+
|
|
384
430
|
def _is_orm_error(self, exception: Exception):
|
|
385
431
|
traceback = exception.__traceback__
|
|
386
432
|
while traceback.tb_next is not None:
|
|
@@ -406,87 +452,127 @@ class BaseWFSGetDataMethod(WFSTypeNamesMethod):
|
|
|
406
452
|
fes_xml,
|
|
407
453
|
)
|
|
408
454
|
|
|
409
|
-
def _get_locator(self, **params):
|
|
410
|
-
"""Tell which field is likely causing the query error"""
|
|
411
|
-
if params["resourceID"]:
|
|
412
|
-
return "resourceId"
|
|
413
|
-
elif params["STOREDQUERY_ID"]:
|
|
414
|
-
return "STOREDQUERY_ID"
|
|
415
|
-
else:
|
|
416
|
-
return "filter"
|
|
417
455
|
|
|
418
|
-
|
|
419
|
-
class GetFeature(BaseWFSGetDataMethod):
|
|
456
|
+
class GetFeature(BaseWFSGetDataOperation):
|
|
420
457
|
"""This returns all properties of the feature.
|
|
421
458
|
|
|
422
459
|
Various query parameters allow limiting the data.
|
|
423
460
|
"""
|
|
424
461
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
462
|
+
def get_output_formats(self) -> list[OutputFormat]:
|
|
463
|
+
"""Return the default output formats.
|
|
464
|
+
This selects a different rendering depending on the ``GISSERVER_USE_DB_RENDERING`` setting.
|
|
465
|
+
"""
|
|
466
|
+
if conf.GISSERVER_GET_FEATURE_OUTPUT_FORMATS:
|
|
467
|
+
return self.get_custom_output_formats(
|
|
468
|
+
conf.GISSERVER_GET_FEATURE_OUTPUT_FORMATS | conf.GISSERVER_EXTRA_OUTPUT_FORMATS
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if conf.GISSERVER_USE_DB_RENDERING:
|
|
472
|
+
csv_renderer = output.DBCSVRenderer
|
|
473
|
+
gml32_renderer = output.DBGML32Renderer
|
|
474
|
+
geojson_renderer = output.DBGeoJsonRenderer
|
|
475
|
+
else:
|
|
476
|
+
csv_renderer = output.CSVRenderer
|
|
477
|
+
gml32_renderer = output.GML32Renderer
|
|
478
|
+
geojson_renderer = output.GeoJsonRenderer
|
|
479
|
+
|
|
480
|
+
return [
|
|
481
|
+
OutputFormat(
|
|
482
|
+
# Needed for cite compliance tests
|
|
483
|
+
"application/gml+xml",
|
|
484
|
+
version="3.2",
|
|
485
|
+
renderer_class=gml32_renderer,
|
|
486
|
+
title="GML",
|
|
487
|
+
),
|
|
488
|
+
OutputFormat(
|
|
489
|
+
"text/xml",
|
|
490
|
+
subtype="gml/3.2.1",
|
|
491
|
+
renderer_class=gml32_renderer,
|
|
492
|
+
title="GML 3.2.1",
|
|
493
|
+
),
|
|
494
|
+
# OutputFormat("gml"),
|
|
495
|
+
OutputFormat(
|
|
496
|
+
# identical to mapserver:
|
|
497
|
+
"application/json",
|
|
498
|
+
subtype="geojson",
|
|
499
|
+
charset="utf-8",
|
|
500
|
+
renderer_class=geojson_renderer,
|
|
501
|
+
title="GeoJSON",
|
|
502
|
+
),
|
|
503
|
+
OutputFormat(
|
|
504
|
+
# Alias needed to make ESRI ArcGIS online accept the WFS.
|
|
505
|
+
# It does not recognize the "subtype" as an alias.
|
|
506
|
+
"geojson",
|
|
507
|
+
renderer_class=geojson_renderer,
|
|
508
|
+
),
|
|
509
|
+
OutputFormat(
|
|
510
|
+
"text/csv",
|
|
511
|
+
subtype="csv",
|
|
512
|
+
charset="utf-8",
|
|
513
|
+
renderer_class=csv_renderer,
|
|
514
|
+
title="CSV",
|
|
515
|
+
),
|
|
516
|
+
# OutputFormat("shapezip"),
|
|
517
|
+
# OutputFormat("application/zip"),
|
|
518
|
+
] + self.get_custom_output_formats(conf.GISSERVER_EXTRA_OUTPUT_FORMATS)
|
|
519
|
+
|
|
520
|
+
def get_custom_output_formats(self, output_formats_setting: dict) -> list[OutputFormat]:
|
|
521
|
+
"""Add custom output formats defined in the settings."""
|
|
522
|
+
result = []
|
|
523
|
+
for content_type, format_kwargs in output_formats_setting.items():
|
|
524
|
+
renderer_class = format_kwargs["renderer_class"]
|
|
525
|
+
if isinstance(renderer_class, str):
|
|
526
|
+
format_kwargs["renderer_class"] = import_string(renderer_class)
|
|
527
|
+
|
|
528
|
+
if not issubclass(renderer_class, CollectionOutputRenderer):
|
|
529
|
+
raise ImproperlyConfigured(
|
|
530
|
+
f"The 'renderer_class' of output format {content_type!r},"
|
|
531
|
+
f" should be a subclass of CollectionOutputRenderer."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
result.append(OutputFormat(content_type, **format_kwargs))
|
|
535
|
+
|
|
536
|
+
return result
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
class GetPropertyValue(BaseWFSGetDataOperation):
|
|
467
540
|
"""This returns a limited set of properties of the feature.
|
|
468
541
|
It works almost identical to GetFeature, except that it returns a single field.
|
|
469
542
|
"""
|
|
470
543
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
544
|
+
ows_request: wfs20.GetPropertyValue
|
|
545
|
+
|
|
546
|
+
def get_output_formats(self) -> list[OutputFormat]:
|
|
547
|
+
"""Define the output format for GetPropertyValue."""
|
|
548
|
+
gml32_value_renderer = (
|
|
549
|
+
output.DBGML32ValueRenderer
|
|
550
|
+
if conf.GISSERVER_USE_DB_RENDERING
|
|
551
|
+
else output.GML32ValueRenderer
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
return [
|
|
555
|
+
OutputFormat(
|
|
556
|
+
"application/gml+xml",
|
|
557
|
+
version="3.2",
|
|
558
|
+
renderer_class=gml32_value_renderer,
|
|
559
|
+
title="GML",
|
|
560
|
+
),
|
|
561
|
+
OutputFormat("text/xml", subtype="gml/3.2", renderer_class=gml32_value_renderer),
|
|
562
|
+
]
|
|
563
|
+
|
|
564
|
+
def validate_request(self, ows_request: wfs20.GetPropertyValue):
|
|
565
|
+
super().validate_request(ows_request)
|
|
566
|
+
|
|
567
|
+
if ows_request.resolvePath:
|
|
568
|
+
raise InvalidParameterValue(
|
|
569
|
+
"Support for resolvePath is not implemented!", locator="resolvePath"
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def bind_query(self, query: wfs20.QueryExpression, feature_types: list[FeatureType]):
|
|
573
|
+
"""Allow to be overwritten in GetFeatureValue"""
|
|
574
|
+
# Pass valueReference to the query, which will include it in the FeatureProjection.
|
|
489
575
|
# In the WFS-spec, the valueReference is only a presentation layer change.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
576
|
+
# However, in our case AdhocQuery object also performs internal processing,
|
|
577
|
+
# so the query performs a "SELECT id, <fieldname>" as well.
|
|
578
|
+
query.bind(feature_types, value_reference=self.ows_request.valueReference)
|