django-gisserver 1.5.0__py3-none-any.whl → 2.1__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.1.dist-info}/METADATA +34 -8
- django_gisserver-2.1.dist-info/RECORD +68 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/compat.py +23 -0
- gisserver/conf.py +7 -0
- gisserver/crs.py +401 -0
- gisserver/db.py +126 -51
- gisserver/exceptions.py +132 -4
- gisserver/extensions/__init__.py +4 -0
- gisserver/{parsers/fes20 → extensions}/functions.py +131 -31
- gisserver/extensions/queries.py +266 -0
- gisserver/features.py +253 -181
- gisserver/geometries.py +64 -311
- gisserver/management/__init__.py +0 -0
- gisserver/management/commands/__init__.py +0 -0
- gisserver/management/commands/loadgeojson.py +311 -0
- gisserver/operations/base.py +130 -312
- gisserver/operations/wfs20.py +399 -375
- gisserver/output/__init__.py +14 -49
- gisserver/output/base.py +198 -144
- gisserver/output/csv.py +78 -75
- gisserver/output/geojson.py +37 -37
- gisserver/output/gml32.py +287 -259
- gisserver/output/iters.py +207 -0
- gisserver/output/results.py +73 -61
- gisserver/output/stored.py +143 -0
- gisserver/output/utils.py +81 -169
- gisserver/output/xmlschema.py +85 -46
- gisserver/parsers/__init__.py +10 -10
- gisserver/parsers/ast.py +426 -0
- gisserver/parsers/fes20/__init__.py +89 -31
- gisserver/parsers/fes20/expressions.py +172 -58
- gisserver/parsers/fes20/filters.py +116 -45
- gisserver/parsers/fes20/identifiers.py +66 -28
- gisserver/parsers/fes20/lookups.py +146 -0
- gisserver/parsers/fes20/operators.py +417 -161
- gisserver/parsers/fes20/sorting.py +113 -34
- gisserver/parsers/gml/__init__.py +17 -25
- gisserver/parsers/gml/base.py +36 -15
- gisserver/parsers/gml/geometries.py +105 -44
- gisserver/parsers/ows/__init__.py +25 -0
- gisserver/parsers/ows/kvp.py +198 -0
- gisserver/parsers/ows/requests.py +160 -0
- gisserver/parsers/query.py +179 -0
- gisserver/parsers/values.py +87 -4
- gisserver/parsers/wfs20/__init__.py +39 -0
- gisserver/parsers/wfs20/adhoc.py +253 -0
- gisserver/parsers/wfs20/base.py +148 -0
- gisserver/parsers/wfs20/projection.py +103 -0
- gisserver/parsers/wfs20/requests.py +483 -0
- gisserver/parsers/wfs20/stored.py +193 -0
- gisserver/parsers/xml.py +261 -0
- gisserver/projection.py +367 -0
- gisserver/static/gisserver/index.css +20 -4
- gisserver/templates/gisserver/base.html +12 -0
- gisserver/templates/gisserver/index.html +9 -15
- gisserver/templates/gisserver/service_description.html +12 -6
- gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
- gisserver/templates/gisserver/wfs/feature_field.html +3 -3
- gisserver/templates/gisserver/wfs/feature_type.html +35 -13
- gisserver/templatetags/gisserver_tags.py +20 -0
- gisserver/types.py +445 -313
- gisserver/views.py +227 -62
- 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.1.dist-info/licenses}/LICENSE +0 -0
- {django_gisserver-1.5.0.dist-info → django_gisserver-2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,18 +1,31 @@
|
|
|
1
|
+
"""The FES elements that handle sorting."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
from dataclasses import dataclass
|
|
4
6
|
from enum import Enum
|
|
5
7
|
|
|
6
8
|
from gisserver.exceptions import InvalidParameterValue
|
|
9
|
+
from gisserver.parsers.ast import AstNode, expect_children, expect_tag, tag_registry
|
|
7
10
|
from gisserver.parsers.fes20 import ValueReference
|
|
11
|
+
from gisserver.parsers.ows import KVPRequest
|
|
12
|
+
from gisserver.parsers.query import CompiledQuery
|
|
13
|
+
from gisserver.parsers.xml import NSElement, xmlns
|
|
14
|
+
|
|
15
|
+
FES_SORT_ORDER = xmlns.fes20.qname("SortOrder")
|
|
8
16
|
|
|
9
17
|
|
|
10
18
|
class SortOrder(Enum):
|
|
19
|
+
#: Ascending order
|
|
11
20
|
ASC = ""
|
|
21
|
+
#: Descrending order
|
|
12
22
|
DESC = "-"
|
|
13
23
|
|
|
14
|
-
# Support WFS 1 names for clients that still use this
|
|
24
|
+
# Support WFS 1 names for clients that still use this:
|
|
25
|
+
|
|
26
|
+
#: WFS 1 name that clients still use for ascending.
|
|
15
27
|
A = ASC
|
|
28
|
+
#: WFS 1 name that clients still use for descending.
|
|
16
29
|
D = DESC
|
|
17
30
|
|
|
18
31
|
@classmethod
|
|
@@ -24,52 +37,118 @@ class SortOrder(Enum):
|
|
|
24
37
|
"Expect ASC/DESC ordering direction", locator="sortby"
|
|
25
38
|
) from None
|
|
26
39
|
|
|
40
|
+
@classmethod
|
|
41
|
+
@expect_tag(xmlns.fes20, "SortOrder")
|
|
42
|
+
def from_xml(cls, element: NSElement):
|
|
43
|
+
return SortOrder.from_string(element.text)
|
|
44
|
+
|
|
27
45
|
|
|
28
46
|
@dataclass
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
@tag_registry.register("SortProperty", xmlns.fes20)
|
|
48
|
+
class SortProperty(AstNode):
|
|
49
|
+
"""This class name is based on the WFS spec.
|
|
50
|
+
|
|
51
|
+
This parses and handles the syntax::
|
|
52
|
+
|
|
53
|
+
<fes:SortProperty>
|
|
54
|
+
<fes:ValueReference>name</fes:ValueReference>
|
|
55
|
+
<fes:SortOrder>ASC</fes:SortOrder>
|
|
56
|
+
</fes:SortProperty>
|
|
57
|
+
"""
|
|
31
58
|
|
|
32
59
|
value_reference: ValueReference
|
|
33
60
|
sort_order: SortOrder = SortOrder.ASC
|
|
34
61
|
|
|
62
|
+
def __post_init__(self):
|
|
63
|
+
if "[" in self.value_reference.xpath:
|
|
64
|
+
raise InvalidParameterValue(
|
|
65
|
+
"Sorting with XPath attribute selectors is not supported.", locator="sortby"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
@expect_tag(xmlns.fes20, "SortProperty")
|
|
70
|
+
@expect_children(1, ValueReference, FES_SORT_ORDER)
|
|
71
|
+
def from_xml(cls, element: NSElement) -> SortProperty:
|
|
72
|
+
"""Parse the incoming XML"""
|
|
73
|
+
sort_order = element.find(FES_SORT_ORDER)
|
|
74
|
+
return cls(
|
|
75
|
+
value_reference=ValueReference.from_xml(element[0]),
|
|
76
|
+
sort_order=(
|
|
77
|
+
SortOrder.from_xml(sort_order) if sort_order is not None else SortOrder.ASC
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_string(cls, value: str, ns_aliases: dict[str, str]) -> SortProperty:
|
|
83
|
+
"""Parse the incoming GET parameter."""
|
|
84
|
+
xpath, _, direction = value.partition(" ")
|
|
85
|
+
return SortProperty(
|
|
86
|
+
value_reference=ValueReference(xpath, ns_aliases),
|
|
87
|
+
sort_order=SortOrder.from_string(direction) if direction else SortOrder.ASC,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def as_kvp(self):
|
|
91
|
+
"""Translate the POST request into KVP GET parameters. This is needed for pagination."""
|
|
92
|
+
if self.sort_order == SortOrder.ASC:
|
|
93
|
+
return self.value_reference.xpath
|
|
94
|
+
else:
|
|
95
|
+
return f"{self.value_reference.xpath} {self.sort_order.name}"
|
|
96
|
+
|
|
35
97
|
|
|
36
98
|
@dataclass
|
|
37
|
-
|
|
38
|
-
|
|
99
|
+
@tag_registry.register("SortBy", xmlns.fes20)
|
|
100
|
+
class SortBy(AstNode):
|
|
101
|
+
"""The sortBy clause.
|
|
102
|
+
|
|
103
|
+
This parses and handles the syntax::
|
|
104
|
+
|
|
105
|
+
<fes:SortBy>
|
|
106
|
+
<fes:SortProperty>
|
|
107
|
+
<fes:ValueReference>name</fes:ValueReference>
|
|
108
|
+
<fes:SortOrder>ASC</fes:SortOrder>
|
|
109
|
+
</fes:SortProperty>
|
|
110
|
+
</fes:SortBy>
|
|
39
111
|
|
|
112
|
+
It also supports the SORTBY parameter for GET requests.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
#: The ``<fes:SortProperty>`` elements.
|
|
40
116
|
sort_properties: list[SortProperty]
|
|
41
117
|
|
|
42
118
|
@classmethod
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
119
|
+
@expect_children(1, SortProperty)
|
|
120
|
+
def from_xml(cls, element: NSElement) -> SortBy:
|
|
121
|
+
"""Parse the XML tag."""
|
|
122
|
+
return cls(
|
|
123
|
+
sort_properties=[
|
|
124
|
+
# The from_xml() validates that the child node is a <fes:SortProperty>
|
|
125
|
+
SortProperty.from_xml(child)
|
|
126
|
+
for child in element
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def from_kvp_request(cls, kvp: KVPRequest) -> SortBy | None:
|
|
132
|
+
"""Construct the SortBy object from a KVP "SORTBY" parameter, and considering NAMESPACES."""
|
|
133
|
+
value = kvp.get_str("SortBy", default=None)
|
|
134
|
+
if value is None:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
return cls(
|
|
138
|
+
sort_properties=[
|
|
139
|
+
SortProperty.from_string(field, kvp.ns_aliases) for field in value.split(",")
|
|
140
|
+
]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def as_kvp(self) -> str:
|
|
144
|
+
"""Translate the POST request into KVP GET parameters. This is needed for pagination."""
|
|
145
|
+
return ",".join(sort_property.as_kvp() for sort_property in self.sort_properties)
|
|
146
|
+
|
|
147
|
+
def build_ordering(self, compiler: CompiledQuery):
|
|
66
148
|
"""Build the ordering for the Django ORM call."""
|
|
67
149
|
ordering = []
|
|
68
150
|
for prop in self.sort_properties:
|
|
69
|
-
|
|
70
|
-
orm_path = prop.value_reference.parse_xpath(feature_type).orm_path
|
|
71
|
-
else:
|
|
72
|
-
orm_path = prop.value_reference.xpath.replace("/", "__")
|
|
73
|
-
|
|
151
|
+
orm_path = prop.value_reference.parse_xpath(compiler.feature_types).orm_path
|
|
74
152
|
ordering.append(f"{prop.sort_order.value}{orm_path}")
|
|
75
|
-
|
|
153
|
+
|
|
154
|
+
compiler.add_ordering(ordering)
|
|
@@ -1,54 +1,46 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Additional parsing logic for GML values.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Most GML elements are parsed through GeoDjango by using the GEOSGeometry element.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from xml.etree.ElementTree import Element
|
|
9
|
-
|
|
10
|
-
from defusedxml.ElementTree import fromstring
|
|
11
|
-
|
|
12
8
|
from gisserver.exceptions import ExternalParsingError
|
|
13
|
-
from gisserver.parsers.
|
|
9
|
+
from gisserver.parsers.ast import tag_registry
|
|
10
|
+
from gisserver.parsers.xml import NSElement, parse_xml_from_string
|
|
14
11
|
|
|
15
12
|
from .base import AbstractGeometry, GM_Envelope, GM_Object, TM_Object
|
|
16
|
-
from .geometries import is_gml_element # also do tag registration
|
|
17
|
-
|
|
18
|
-
# All known root nodes as GML object:
|
|
19
|
-
FES_GML_NODES = (GM_Object, GM_Envelope, TM_Object)
|
|
13
|
+
from .geometries import GEOSGMLGeometry, is_gml_element # also do tag registration
|
|
20
14
|
|
|
21
15
|
__all__ = [
|
|
22
16
|
"GM_Object",
|
|
23
17
|
"GM_Envelope",
|
|
24
18
|
"TM_Object",
|
|
25
19
|
"AbstractGeometry",
|
|
20
|
+
"GEOSGMLGeometry",
|
|
26
21
|
"parse_gml",
|
|
27
22
|
"parse_gml_node",
|
|
28
23
|
"find_gml_nodes",
|
|
29
24
|
]
|
|
30
25
|
|
|
31
26
|
|
|
32
|
-
def parse_gml(text: str | bytes) ->
|
|
27
|
+
def parse_gml(text: str | bytes) -> GM_Object | GM_Envelope | TM_Object:
|
|
33
28
|
"""Parse an XML <gml:...> string."""
|
|
34
|
-
root_element =
|
|
29
|
+
root_element = parse_xml_from_string(text)
|
|
35
30
|
return parse_gml_node(root_element)
|
|
36
31
|
|
|
37
32
|
|
|
38
|
-
def parse_gml_node(element:
|
|
39
|
-
"""Parse the element"""
|
|
33
|
+
def parse_gml_node(element: NSElement) -> GM_Object | GM_Envelope | TM_Object:
|
|
34
|
+
"""Parse the GML element."""
|
|
40
35
|
if not is_gml_element(element):
|
|
41
36
|
raise ExternalParsingError(f"Expected GML namespace for {element.tag}")
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
# All known root nodes as GML object:
|
|
39
|
+
return tag_registry.node_from_xml(element, allowed_types=(GM_Object, GM_Envelope, TM_Object))
|
|
45
40
|
|
|
46
|
-
def find_gml_nodes(element: Element) -> list[Element]:
|
|
47
|
-
"""Find all gml elements in a node"""
|
|
48
|
-
result = []
|
|
49
|
-
for child in element:
|
|
50
|
-
# This selects all GML elements, including 2.1
|
|
51
|
-
if is_gml_element(child):
|
|
52
|
-
result.append(child)
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
def find_gml_nodes(element: NSElement) -> list[NSElement]:
|
|
43
|
+
"""Find all ``<gml:...>`` elements in a node.
|
|
44
|
+
This selects all GML elements, including GML 2.1 tags.
|
|
45
|
+
"""
|
|
46
|
+
return [child for child in element if is_gml_element(child)]
|
gisserver/parsers/gml/base.py
CHANGED
|
@@ -4,28 +4,37 @@ See "Table D.2" in the GML 3.2.1 spec, showing how the UML names
|
|
|
4
4
|
map to the GML implementations. These names are referenced by the FES spec.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
from gisserver.parsers.ast import AstNode
|
|
10
|
+
from gisserver.parsers.query import CompiledQuery
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
""
|
|
12
|
+
__all__ = (
|
|
13
|
+
"GM_Object",
|
|
14
|
+
"GM_Envelope",
|
|
15
|
+
"TM_Object",
|
|
16
|
+
"AbstractTimeObject",
|
|
17
|
+
"AbstractTimePrimitive",
|
|
18
|
+
"Envelope",
|
|
19
|
+
"AbstractGeometry",
|
|
20
|
+
)
|
|
12
21
|
|
|
13
|
-
<gml:AbstractGeometry> implements the ISO 19107 GM_Object.
|
|
14
|
-
"""
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
class GM_Object(AstNode):
|
|
24
|
+
"""Abstract base classes for all GML objects, regardless of their version."""
|
|
25
|
+
|
|
26
|
+
def build_rhs(self, compiler: CompiledQuery):
|
|
27
|
+
"""Required function to implement.
|
|
28
|
+
This allows using the value to be used in a binary operator.
|
|
29
|
+
"""
|
|
18
30
|
raise NotImplementedError()
|
|
19
31
|
|
|
20
32
|
|
|
21
|
-
class
|
|
22
|
-
"""Abstract base classes for
|
|
33
|
+
class GM_Envelope(AstNode):
|
|
34
|
+
"""Abstract base classes for the GML envelope, regardless of their version."""
|
|
23
35
|
|
|
24
|
-
<gml:Envelope> implements ISO 19107 GM_Envelope (see D.2.3.4 and ISO 19107:2003, 6.4.3).
|
|
25
|
-
"""
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
class TM_Object(BaseNode):
|
|
37
|
+
class TM_Object(AstNode):
|
|
29
38
|
"""Abstract base classes for temporal GML objects, regardless of their version.
|
|
30
39
|
|
|
31
40
|
See ISO 19108 TM_Object (see D.2.5.2 and ISO 19108:2002, 5.2.2)
|
|
@@ -33,5 +42,17 @@ class TM_Object(BaseNode):
|
|
|
33
42
|
|
|
34
43
|
|
|
35
44
|
# Instead of polluting the MRO with unneeded base classes, create aliases:
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
|
|
46
|
+
#: The ``<gml:AbstractGeometry>`` definition implements the ISO 19107 GM_Object.
|
|
47
|
+
AbstractGeometry = GM_Object
|
|
48
|
+
|
|
49
|
+
#: The ``<gml:Envelope>`` implements ISO 19107 GM_Envelope (see D.2.3.4 and ISO 19107:2003, 6.4.3).
|
|
50
|
+
Envelope = GM_Envelope
|
|
51
|
+
|
|
52
|
+
# See https://www.mediamaps.ch/ogc/schemas-xsdoc/sld/1.1.0/temporal_xsd.html
|
|
53
|
+
|
|
54
|
+
#: The base class for all time objects
|
|
55
|
+
AbstractTimeObject = TM_Object
|
|
56
|
+
|
|
57
|
+
#: The base classes for time primitives.
|
|
58
|
+
AbstractTimePrimitive = AbstractTimeObject
|
|
@@ -4,16 +4,18 @@ Overview of GML 3.2 changes: https://mapserver.org/el/development/rfc/ms-rfc-105
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from xml.etree.ElementTree import
|
|
7
|
+
from xml.etree.ElementTree import tostring
|
|
8
8
|
|
|
9
|
-
from django.contrib.gis.
|
|
9
|
+
from django.contrib.gis.gdal import AxisOrder
|
|
10
|
+
from django.contrib.gis.geos import GEOSGeometry, Polygon
|
|
10
11
|
|
|
11
|
-
from gisserver.
|
|
12
|
-
from gisserver.
|
|
13
|
-
from gisserver.parsers.
|
|
14
|
-
from gisserver.
|
|
12
|
+
from gisserver.crs import CRS
|
|
13
|
+
from gisserver.exceptions import ExternalParsingError
|
|
14
|
+
from gisserver.parsers.ast import tag_registry
|
|
15
|
+
from gisserver.parsers.query import CompiledQuery
|
|
16
|
+
from gisserver.parsers.xml import NSElement, xmlns
|
|
15
17
|
|
|
16
|
-
from .base import AbstractGeometry,
|
|
18
|
+
from .base import AbstractGeometry, AbstractTimePrimitive
|
|
17
19
|
|
|
18
20
|
_ANY_GML_NS = "{http://www.opengis.net/gml/"
|
|
19
21
|
|
|
@@ -24,18 +26,20 @@ def is_gml_element(element) -> bool:
|
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
@dataclass(repr=False)
|
|
27
|
-
@tag_registry.register("Polygon",
|
|
28
|
-
@tag_registry.register("LineString",
|
|
29
|
-
@tag_registry.register("LinearRing",
|
|
30
|
-
@tag_registry.register("MultiLineString",
|
|
31
|
-
@tag_registry.register("MultiPoint",
|
|
32
|
-
@tag_registry.register("MultiPolygon",
|
|
33
|
-
@tag_registry.register("MultiSurface",
|
|
34
|
-
@tag_registry.register("Point",
|
|
35
|
-
@tag_registry.register("Polygon",
|
|
36
|
-
@tag_registry.register("Envelope",
|
|
29
|
+
@tag_registry.register("Polygon", xmlns.gml21)
|
|
30
|
+
@tag_registry.register("LineString", xmlns.gml32)
|
|
31
|
+
@tag_registry.register("LinearRing", xmlns.gml32)
|
|
32
|
+
@tag_registry.register("MultiLineString", xmlns.gml32)
|
|
33
|
+
@tag_registry.register("MultiPoint", xmlns.gml32)
|
|
34
|
+
@tag_registry.register("MultiPolygon", xmlns.gml32)
|
|
35
|
+
@tag_registry.register("MultiSurface", xmlns.gml32)
|
|
36
|
+
@tag_registry.register("Point", xmlns.gml32)
|
|
37
|
+
@tag_registry.register("Polygon", xmlns.gml32)
|
|
38
|
+
@tag_registry.register("Envelope", xmlns.gml32)
|
|
37
39
|
class GEOSGMLGeometry(AbstractGeometry):
|
|
38
|
-
"""Convert the incoming GML into a Django GEOSGeometry
|
|
40
|
+
"""Convert the incoming GML into a Django GEOSGeometry.
|
|
41
|
+
This tag parses all ``<gml:...>`` geometry elements within the query.
|
|
42
|
+
"""
|
|
39
43
|
|
|
40
44
|
# Not implemented:
|
|
41
45
|
# - Curve
|
|
@@ -43,30 +47,54 @@ class GEOSGMLGeometry(AbstractGeometry):
|
|
|
43
47
|
# - MultiGeometry
|
|
44
48
|
# - Surface
|
|
45
49
|
|
|
46
|
-
xml_ns = ...
|
|
47
|
-
|
|
48
50
|
srs: CRS
|
|
49
51
|
geos_data: GEOSGeometry
|
|
50
52
|
|
|
51
53
|
@classmethod
|
|
52
|
-
def
|
|
54
|
+
def from_bbox(cls, bbox_value: str):
|
|
55
|
+
"""Parse the bounding box from an input string.
|
|
56
|
+
|
|
57
|
+
It can either be 4 coordinates, or 4 coordinates with a special reference system.
|
|
58
|
+
"""
|
|
59
|
+
bbox = bbox_value.split(",")
|
|
60
|
+
if not (4 <= len(bbox) <= 5):
|
|
61
|
+
raise ExternalParsingError(
|
|
62
|
+
f"Input does not contain bounding box, expected 4 or 5 values, not {bbox}."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
polygon = Polygon.from_bbox(
|
|
66
|
+
(float(bbox[0]), float(bbox[1]), float(bbox[2]), float(bbox[3]))
|
|
67
|
+
)
|
|
68
|
+
if len(bbox) == 5:
|
|
69
|
+
crs = CRS.from_string(bbox[4])
|
|
70
|
+
polygon.srid = crs.srid
|
|
71
|
+
else:
|
|
72
|
+
crs = None # will be resolved
|
|
73
|
+
|
|
74
|
+
# Wrap in an element that the filter can use.
|
|
75
|
+
CRS.tag_geometry(polygon, axis_order=AxisOrder.AUTHORITY)
|
|
76
|
+
return cls(srs=crs, geos_data=polygon)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_xml(cls, element: NSElement):
|
|
53
80
|
"""Push the whole <gml:...> element into the GEOS parser.
|
|
54
81
|
This avoids having to support the whole GEOS logic.
|
|
55
82
|
|
|
56
83
|
GML is a complex beast with many different forms for the same thing:
|
|
57
84
|
http://erouault.blogspot.com/2014/04/gml-madness.html
|
|
58
85
|
"""
|
|
59
|
-
srs = CRS.from_string(
|
|
86
|
+
srs = CRS.from_string(element.get_str_attribute("srsName"))
|
|
60
87
|
|
|
61
88
|
# Push the whole <gml:...> element into the GEOS parser.
|
|
62
89
|
# This avoids having to support the whole GEOS logic.
|
|
63
90
|
geos_data = GEOSGeometry.from_gml(tostring(element))
|
|
64
91
|
geos_data.srid = srs.srid
|
|
92
|
+
CRS.tag_geometry(geos_data, axis_order=AxisOrder.AUTHORITY)
|
|
65
93
|
return cls(srs=srs, geos_data=geos_data)
|
|
66
94
|
|
|
67
95
|
def __repr__(self):
|
|
68
96
|
# Better rendering for unit test debugging
|
|
69
|
-
return f"
|
|
97
|
+
return f"{self.__class__.__name__}(srs={self.srs!r}, geos_data=GEOSGeometry({self.geos_data.wkt!r}))"
|
|
70
98
|
|
|
71
99
|
@property
|
|
72
100
|
def wkt(self) -> str:
|
|
@@ -77,24 +105,57 @@ class GEOSGMLGeometry(AbstractGeometry):
|
|
|
77
105
|
def json(self):
|
|
78
106
|
return self.geos_data.json
|
|
79
107
|
|
|
80
|
-
def build_rhs(self, compiler):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
def build_rhs(self, compiler: CompiledQuery):
|
|
109
|
+
# Perform final validation during the construction of the query.
|
|
110
|
+
if self.srs is None:
|
|
111
|
+
# When the feature type is known, apply its default CRS.
|
|
112
|
+
# This is not possible in XML parsing, but may happen for BBOX parsing.
|
|
113
|
+
self.srs = compiler.feature_types[0].crs
|
|
114
|
+
self.geos_data.srid = self.srs.srid # assign default CRS to geometry
|
|
115
|
+
elif compiler.feature_types: # for unit tests
|
|
116
|
+
self.srs = compiler.feature_types[0].resolve_crs(self.srs, locator="bbox")
|
|
117
|
+
|
|
118
|
+
# Make sure the data is suitable for processing by the ORM.
|
|
119
|
+
# The database needs the geometry in traditional (x/y) ordering.
|
|
120
|
+
if self.srs.is_north_east_order:
|
|
121
|
+
return self.srs.apply_to(self.geos_data, clone=True, axis_order=AxisOrder.TRADITIONAL)
|
|
122
|
+
else:
|
|
123
|
+
return self.geos_data
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@tag_registry.register("TimeInstant", hidden=True)
|
|
127
|
+
@tag_registry.register("TimePeriod", hidden=True)
|
|
128
|
+
class AbstractTimeGeometricPrimitive(AbstractTimePrimitive):
|
|
129
|
+
"""Not implemented: the whole GML temporal logic.
|
|
130
|
+
|
|
131
|
+
Examples for GML time elements include::
|
|
132
|
+
|
|
133
|
+
<gml:TimeInstant gml:id="TI1">
|
|
134
|
+
<gml:timePosition>2005-05-19T09:28:40Z</gml:timePosition>
|
|
135
|
+
</gml:TimeInstant>
|
|
136
|
+
|
|
137
|
+
and::
|
|
138
|
+
|
|
139
|
+
<gml:TimePeriod gml:id="TP1">
|
|
140
|
+
<gml:begin>
|
|
141
|
+
<gml:TimeInstant gml:id="TI1">
|
|
142
|
+
<gml:timePosition>2005-05-17T00:00:00Z</gml:timePosition>
|
|
143
|
+
</gml:TimeInstant>
|
|
144
|
+
</gml:begin>
|
|
145
|
+
<gml:end>
|
|
146
|
+
<gml:TimeInstant gml:id="TI2">
|
|
147
|
+
<gml:timePosition>2005-05-23T00:00:00Z</gml:timePosition>
|
|
148
|
+
</gml:TimeInstant>
|
|
149
|
+
</gml:end>
|
|
150
|
+
</gml:TimePeriod>
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
xml_ns = xmlns.gml32
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@tag_registry.register("TimeNode", hidden=True)
|
|
157
|
+
@tag_registry.register("TimeEdge", hidden=True)
|
|
158
|
+
class AbstractTimeTopologyPrimitiveType(AbstractTimePrimitive):
|
|
159
|
+
"""Not implemented: GML temporal logic for TimeNode/TimeEdge."""
|
|
160
|
+
|
|
161
|
+
xml_ns = xmlns.gml32
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Generic Open Web Services (OWS) protocol bits to handle incoming requests.
|
|
2
|
+
|
|
3
|
+
This is the common logic between WFS, WMS and other protocols.
|
|
4
|
+
|
|
5
|
+
These translate both request-syntax formats in the same internal objects
|
|
6
|
+
that the rest of the controller/view logic can use.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .kvp import KVPRequest
|
|
10
|
+
from .requests import (
|
|
11
|
+
BaseOwsRequest,
|
|
12
|
+
parse_get_request,
|
|
13
|
+
parse_post_request,
|
|
14
|
+
resolve_kvp_parser_class,
|
|
15
|
+
resolve_xml_parser_class,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = (
|
|
19
|
+
"KVPRequest",
|
|
20
|
+
"BaseOwsRequest",
|
|
21
|
+
"resolve_kvp_parser_class",
|
|
22
|
+
"resolve_xml_parser_class",
|
|
23
|
+
"parse_get_request",
|
|
24
|
+
"parse_post_request",
|
|
25
|
+
)
|