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/output/csv.py
CHANGED
|
@@ -9,20 +9,14 @@ from io import StringIO
|
|
|
9
9
|
from django.db import models
|
|
10
10
|
|
|
11
11
|
from gisserver import conf
|
|
12
|
-
from gisserver.db import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
get_db_annotation,
|
|
16
|
-
get_db_geometry_selects,
|
|
17
|
-
)
|
|
18
|
-
from gisserver.geometries import CRS
|
|
19
|
-
from gisserver.queries import FeatureProjection
|
|
20
|
-
from gisserver.types import XsdElement
|
|
12
|
+
from gisserver.db import AsEWKT, get_db_rendered_geometry, replace_queryset_geometries
|
|
13
|
+
from gisserver.projection import FeatureProjection, FeatureRelation
|
|
14
|
+
from gisserver.types import GeometryXsdElement, XsdElement, XsdTypes
|
|
21
15
|
|
|
22
|
-
from .base import
|
|
16
|
+
from .base import CollectionOutputRenderer
|
|
23
17
|
|
|
24
18
|
|
|
25
|
-
class CSVRenderer(
|
|
19
|
+
class CSVRenderer(CollectionOutputRenderer):
|
|
26
20
|
"""Fast CSV renderer, using a stream response.
|
|
27
21
|
|
|
28
22
|
The complex encoding bits are handled by the "csv" library.
|
|
@@ -37,32 +31,22 @@ class CSVRenderer(OutputRenderer):
|
|
|
37
31
|
#: or one of the registered names like: "unix", "excel", "excel-tab"
|
|
38
32
|
dialect = "unix"
|
|
39
33
|
|
|
40
|
-
@classmethod
|
|
41
34
|
def decorate_queryset(
|
|
42
|
-
|
|
43
|
-
projection: FeatureProjection,
|
|
44
|
-
queryset: models.QuerySet,
|
|
45
|
-
output_crs: CRS,
|
|
46
|
-
**params,
|
|
35
|
+
self, projection: FeatureProjection, queryset: models.QuerySet
|
|
47
36
|
) -> models.QuerySet:
|
|
48
37
|
"""Make sure relations are included with select-related to avoid N-queries.
|
|
49
38
|
Using prefetch_related() isn't possible with .iterator().
|
|
50
39
|
"""
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
if related:
|
|
63
|
-
queryset = queryset.select_related(*related)
|
|
64
|
-
|
|
65
|
-
return queryset
|
|
40
|
+
# First, make sure no array or m2m elements exist,
|
|
41
|
+
# as these are not possible to render in CSV.
|
|
42
|
+
projection.remove_fields(
|
|
43
|
+
lambda e: e.is_many
|
|
44
|
+
or e.type == XsdTypes.gmlCodeType # gml:name
|
|
45
|
+
or e.type == XsdTypes.gmlBoundingShapeType # gml:boundedBy
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# All database optimizations
|
|
49
|
+
return super().decorate_queryset(projection, queryset)
|
|
66
50
|
|
|
67
51
|
def render_stream(self):
|
|
68
52
|
self.output = output = StringIO()
|
|
@@ -78,18 +62,12 @@ class CSVRenderer(OutputRenderer):
|
|
|
78
62
|
output.write("\n\n")
|
|
79
63
|
|
|
80
64
|
# Write the header
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# By using .iterator(), the results are streamed with as little memory as
|
|
89
|
-
# possible. Doing prefetch_related() is not possible now. That could only
|
|
90
|
-
# be implemented with cursor pagination for large sets for 1000+ results.
|
|
91
|
-
for instance in sub_collection.iterator():
|
|
92
|
-
writer.writerow(self.get_row(instance, projection, fields))
|
|
65
|
+
xsd_elements = projection.xsd_root_elements
|
|
66
|
+
writer.writerow(self.get_header(projection, xsd_elements))
|
|
67
|
+
|
|
68
|
+
# Write all rows
|
|
69
|
+
for instance in sub_collection:
|
|
70
|
+
writer.writerow(self.get_row(instance, projection, xsd_elements))
|
|
93
71
|
|
|
94
72
|
# Only perform a 'yield' every once in a while,
|
|
95
73
|
# as it goes back-and-forth for writing it to the client.
|
|
@@ -108,18 +86,22 @@ class CSVRenderer(OutputRenderer):
|
|
|
108
86
|
return f"{buffer}\n\n{message}\n"
|
|
109
87
|
|
|
110
88
|
def get_header(
|
|
111
|
-
self, projection: FeatureProjection, xsd_elements: list[XsdElement]
|
|
89
|
+
self, projection: FeatureProjection, xsd_elements: list[XsdElement], prefix=""
|
|
112
90
|
) -> list[str]:
|
|
113
91
|
"""Return all field names."""
|
|
114
92
|
names = []
|
|
115
93
|
append = names.append
|
|
116
94
|
for xsd_element in xsd_elements:
|
|
117
95
|
if xsd_element.type.is_complex_type:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
96
|
+
names.extend(
|
|
97
|
+
self.get_header(
|
|
98
|
+
projection,
|
|
99
|
+
xsd_elements=projection.xsd_child_nodes[xsd_element],
|
|
100
|
+
prefix=f"{prefix}{xsd_element.name}.",
|
|
101
|
+
)
|
|
102
|
+
)
|
|
121
103
|
else:
|
|
122
|
-
append(xsd_element.name)
|
|
104
|
+
append(f"{prefix}{xsd_element.name}")
|
|
123
105
|
|
|
124
106
|
return names
|
|
125
107
|
|
|
@@ -130,14 +112,19 @@ class CSVRenderer(OutputRenderer):
|
|
|
130
112
|
values = []
|
|
131
113
|
append = values.append
|
|
132
114
|
for xsd_element in xsd_elements:
|
|
133
|
-
if xsd_element.is_geometry:
|
|
115
|
+
if xsd_element.type.is_geometry:
|
|
134
116
|
append(self.render_geometry(instance, xsd_element))
|
|
135
117
|
continue
|
|
136
118
|
|
|
137
119
|
value = xsd_element.get_value(instance)
|
|
138
120
|
if xsd_element.type.is_complex_type:
|
|
139
|
-
|
|
140
|
-
|
|
121
|
+
values.extend(
|
|
122
|
+
self.get_row(
|
|
123
|
+
instance=value,
|
|
124
|
+
projection=projection,
|
|
125
|
+
xsd_elements=projection.xsd_child_nodes[xsd_element],
|
|
126
|
+
)
|
|
127
|
+
)
|
|
141
128
|
elif isinstance(value, list):
|
|
142
129
|
# Array field
|
|
143
130
|
append(",".join(map(str, value)))
|
|
@@ -147,9 +134,9 @@ class CSVRenderer(OutputRenderer):
|
|
|
147
134
|
append(value)
|
|
148
135
|
return values
|
|
149
136
|
|
|
150
|
-
def render_geometry(self, instance: models.Model,
|
|
137
|
+
def render_geometry(self, instance: models.Model, geo_element: GeometryXsdElement):
|
|
151
138
|
"""Render the contents of a geometry value."""
|
|
152
|
-
return
|
|
139
|
+
return geo_element.get_value(instance)
|
|
153
140
|
|
|
154
141
|
|
|
155
142
|
class DBCSVRenderer(CSVRenderer):
|
|
@@ -157,25 +144,29 @@ class DBCSVRenderer(CSVRenderer):
|
|
|
157
144
|
This is about 40% faster than calling the GEOS C-API from python.
|
|
158
145
|
"""
|
|
159
146
|
|
|
160
|
-
@classmethod
|
|
161
147
|
def decorate_queryset(
|
|
162
|
-
|
|
163
|
-
projection: FeatureProjection,
|
|
164
|
-
queryset: models.QuerySet,
|
|
165
|
-
output_crs: CRS,
|
|
166
|
-
**params,
|
|
148
|
+
self, projection: FeatureProjection, queryset: models.QuerySet
|
|
167
149
|
) -> models.QuerySet:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
150
|
+
# Instead of reading the binary geometry data, let the database generate EWKT data.
|
|
151
|
+
# As annotations can't be done for select_related() objects, prefetches are used instead.
|
|
152
|
+
queryset = super().decorate_queryset(projection, queryset)
|
|
153
|
+
return replace_queryset_geometries(
|
|
154
|
+
queryset, projection.geometry_elements, projection.output_crs, AsEWKT
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def get_prefetch_queryset(
|
|
158
|
+
self, projection: FeatureProjection, feature_relation: FeatureRelation
|
|
159
|
+
) -> models.QuerySet | None:
|
|
160
|
+
"""Perform DB annotations for prefetched relations too."""
|
|
161
|
+
queryset = super().get_prefetch_queryset(projection, feature_relation)
|
|
162
|
+
if queryset is None:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
# Find which fields are GML elements, annotate these too.
|
|
166
|
+
return replace_queryset_geometries(
|
|
167
|
+
queryset, feature_relation.geometry_elements, projection.output_crs, AsEWKT
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def render_geometry(self, instance: models.Model, geo_element: GeometryXsdElement):
|
|
171
|
+
"""Render the geometry using a database-rendered version."""
|
|
172
|
+
return get_db_rendered_geometry(instance, geo_element, AsEWKT)
|
gisserver/output/geojson.py
CHANGED
|
@@ -11,11 +11,11 @@ from django.utils.functional import Promise
|
|
|
11
11
|
|
|
12
12
|
from gisserver import conf
|
|
13
13
|
from gisserver.db import get_db_geometry_target
|
|
14
|
-
from gisserver.geometries import
|
|
15
|
-
from gisserver.
|
|
14
|
+
from gisserver.geometries import CRS84, WGS84
|
|
15
|
+
from gisserver.projection import FeatureProjection
|
|
16
16
|
from gisserver.types import XsdElement
|
|
17
17
|
|
|
18
|
-
from .base import
|
|
18
|
+
from .base import CollectionOutputRenderer
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _json_default(obj):
|
|
@@ -25,7 +25,7 @@ def _json_default(obj):
|
|
|
25
25
|
raise TypeError(f"Unable to serialize {obj.__class__.__name__} to JSON")
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
class GeoJsonRenderer(
|
|
28
|
+
class GeoJsonRenderer(CollectionOutputRenderer):
|
|
29
29
|
"""Fast GeoJSON renderer, using a stream response.
|
|
30
30
|
|
|
31
31
|
The complex encoding bits are handled by the C-library "orjson"
|
|
@@ -41,29 +41,26 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
41
41
|
max_page_size = conf.GISSERVER_GEOJSON_MAX_PAGE_SIZE
|
|
42
42
|
chunk_size = 40_000
|
|
43
43
|
|
|
44
|
-
@classmethod
|
|
45
44
|
def decorate_queryset(
|
|
46
|
-
|
|
45
|
+
self,
|
|
47
46
|
projection: FeatureProjection,
|
|
48
47
|
queryset: models.QuerySet,
|
|
49
|
-
output_crs: CRS,
|
|
50
|
-
**params,
|
|
51
48
|
):
|
|
52
49
|
"""Redefine which fields to query, always include geometry, but remove all others"""
|
|
53
|
-
|
|
54
|
-
projection.
|
|
55
|
-
|
|
50
|
+
# make sure output CRS matches the coordinate ordering that GEOSGeometry.json returns
|
|
51
|
+
if projection.output_crs == WGS84:
|
|
52
|
+
projection.output_crs = CRS84
|
|
56
53
|
|
|
54
|
+
# Make sure geometry is always queried.
|
|
57
55
|
# Other geometries can be excluded as these are not rendered by 'properties'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if other_geometries:
|
|
64
|
-
queryset = queryset.defer(*other_geometries)
|
|
56
|
+
main_geo_element = projection.feature_type.main_geometry_element
|
|
57
|
+
projection.add_field(main_geo_element)
|
|
58
|
+
projection.remove_fields(
|
|
59
|
+
lambda element: element.type.is_geometry and element is not main_geo_element
|
|
60
|
+
)
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
# Apply the normal optimizations with this altered projection
|
|
63
|
+
return super().decorate_queryset(projection, queryset)
|
|
67
64
|
|
|
68
65
|
def render_stream(self):
|
|
69
66
|
self.output = output = BytesIO()
|
|
@@ -117,7 +114,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
117
114
|
def render_exception(self, exception: Exception):
|
|
118
115
|
"""Render the exception in a format that fits with the output."""
|
|
119
116
|
message = super().render_exception(exception)
|
|
120
|
-
buffer = self.output.getvalue()
|
|
117
|
+
buffer = self.output.getvalue().decode()
|
|
121
118
|
return f"{buffer}/* {message} */\n"
|
|
122
119
|
|
|
123
120
|
def render_feature(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
@@ -137,7 +134,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
137
134
|
return b' {"type":"Feature","id":%b,%b"geometry":%b,"properties":%b}' % (
|
|
138
135
|
orjson.dumps(f"{feature_type.name}.{instance.pk}"),
|
|
139
136
|
json_geometry_name,
|
|
140
|
-
self.render_geometry(
|
|
137
|
+
self.render_geometry(projection, instance),
|
|
141
138
|
orjson.dumps(properties, default=_json_default),
|
|
142
139
|
)
|
|
143
140
|
|
|
@@ -150,15 +147,17 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
150
147
|
else:
|
|
151
148
|
return value
|
|
152
149
|
|
|
153
|
-
def render_geometry(self,
|
|
150
|
+
def render_geometry(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
154
151
|
"""Generate the proper GeoJSON notation for a geometry.
|
|
155
152
|
This calls the GDAL C-API rendering found in 'GEOSGeometry.json'
|
|
156
153
|
"""
|
|
157
|
-
geometry =
|
|
154
|
+
geometry = projection.get_main_geometry_value(instance)
|
|
158
155
|
if geometry is None:
|
|
159
156
|
return b"null"
|
|
160
157
|
|
|
161
|
-
|
|
158
|
+
# The .json property always outputs coordinates as x,y (longitude,latitude),
|
|
159
|
+
# which the GeoJSON spec requires.
|
|
160
|
+
projection.output_crs.apply_to(geometry)
|
|
162
161
|
return geometry.json.encode()
|
|
163
162
|
|
|
164
163
|
def get_header(self) -> dict:
|
|
@@ -167,11 +166,12 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
167
166
|
The format is based on the WFS 3.0 DRAFT. The count fields are moved
|
|
168
167
|
to the footer allowing them to be calculated without performing queries.
|
|
169
168
|
"""
|
|
169
|
+
output_crs = self.collection.results[0].projection.output_crs
|
|
170
170
|
return {
|
|
171
171
|
"type": "FeatureCollection",
|
|
172
172
|
"timeStamp": self._format_geojson_value(self.collection.timestamp),
|
|
173
173
|
# "numberReturned": is written at the end for better query performance.
|
|
174
|
-
"crs": {"type": "name", "properties": {"name": str(
|
|
174
|
+
"crs": {"type": "name", "properties": {"name": str(output_crs)}},
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
def get_footer(self) -> dict:
|
|
@@ -227,7 +227,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
227
227
|
"""
|
|
228
228
|
props = {}
|
|
229
229
|
for xsd_element in xsd_elements:
|
|
230
|
-
if not xsd_element.is_geometry:
|
|
230
|
+
if not xsd_element.type.is_geometry:
|
|
231
231
|
value = xsd_element.get_value(instance)
|
|
232
232
|
if xsd_element.type.is_complex_type:
|
|
233
233
|
# Nested object data
|
|
@@ -258,30 +258,29 @@ class DBGeoJsonRenderer(GeoJsonRenderer):
|
|
|
258
258
|
This is even more efficient than calling the C-API for each feature.
|
|
259
259
|
"""
|
|
260
260
|
|
|
261
|
-
|
|
262
|
-
def decorate_queryset(self, projection: FeatureProjection, queryset, output_crs, **params):
|
|
261
|
+
def decorate_queryset(self, projection: FeatureProjection, queryset):
|
|
263
262
|
"""Update the queryset to let the database render the GML output.
|
|
264
263
|
This is far more efficient than GeoDjango's logic, which performs a
|
|
265
264
|
C-API call for every single coordinate of a geometry.
|
|
266
265
|
"""
|
|
267
|
-
queryset = super().decorate_queryset(projection, queryset
|
|
266
|
+
queryset = super().decorate_queryset(projection, queryset)
|
|
268
267
|
# If desired, the entire FeatureCollection could be rendered
|
|
269
268
|
# in PostgreSQL as well: https://postgis.net/docs/ST_AsGeoJSON.html
|
|
270
|
-
|
|
271
|
-
if
|
|
272
|
-
queryset = queryset.defer(
|
|
269
|
+
main_geo_element = projection.feature_type.main_geometry_element
|
|
270
|
+
if main_geo_element is not None:
|
|
271
|
+
queryset = queryset.defer(main_geo_element.orm_path).annotate(
|
|
273
272
|
_as_db_geojson=AsGeoJSON(
|
|
274
|
-
get_db_geometry_target(
|
|
273
|
+
get_db_geometry_target(main_geo_element, projection.output_crs),
|
|
275
274
|
precision=conf.GISSERVER_DB_PRECISION,
|
|
276
275
|
)
|
|
277
276
|
)
|
|
278
277
|
|
|
279
278
|
return queryset
|
|
280
279
|
|
|
281
|
-
def render_geometry(self,
|
|
280
|
+
def render_geometry(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
282
281
|
"""Generate the proper GeoJSON notation for a geometry"""
|
|
283
282
|
# Database server rendering
|
|
284
|
-
if
|
|
283
|
+
if projection.main_geometry_element is None:
|
|
285
284
|
return b"null"
|
|
286
285
|
|
|
287
286
|
geojson = instance._as_db_geojson
|