django-gisserver 1.3.0__py3-none-any.whl → 1.4.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.3.0.dist-info → django_gisserver-1.4.1.dist-info}/METADATA +16 -17
- django_gisserver-1.4.1.dist-info/RECORD +53 -0
- {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.1.dist-info}/WHEEL +1 -1
- gisserver/__init__.py +1 -1
- gisserver/conf.py +9 -12
- gisserver/db.py +6 -10
- gisserver/exceptions.py +1 -0
- gisserver/features.py +21 -31
- gisserver/geometries.py +11 -25
- gisserver/operations/base.py +19 -40
- gisserver/operations/wfs20.py +8 -20
- gisserver/output/__init__.py +7 -2
- gisserver/output/base.py +4 -13
- gisserver/output/csv.py +13 -16
- gisserver/output/geojson.py +14 -17
- gisserver/output/gml32.py +309 -281
- gisserver/output/results.py +105 -22
- gisserver/output/utils.py +15 -5
- gisserver/output/xmlschema.py +7 -8
- gisserver/parsers/base.py +2 -4
- gisserver/parsers/fes20/expressions.py +6 -13
- gisserver/parsers/fes20/filters.py +6 -5
- gisserver/parsers/fes20/functions.py +4 -4
- gisserver/parsers/fes20/identifiers.py +1 -0
- gisserver/parsers/fes20/operators.py +16 -43
- gisserver/parsers/fes20/query.py +1 -3
- gisserver/parsers/fes20/sorting.py +1 -3
- gisserver/parsers/gml/__init__.py +1 -0
- gisserver/parsers/gml/base.py +1 -0
- gisserver/parsers/values.py +1 -3
- gisserver/queries/__init__.py +1 -0
- gisserver/queries/adhoc.py +3 -6
- gisserver/queries/base.py +2 -6
- gisserver/queries/stored.py +3 -6
- gisserver/types.py +59 -46
- gisserver/views.py +7 -10
- django_gisserver-1.3.0.dist-info/RECORD +0 -54
- gisserver/output/buffer.py +0 -64
- {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.1.dist-info}/LICENSE +0 -0
- {django_gisserver-1.3.0.dist-info → django_gisserver-1.4.1.dist-info}/top_level.txt +0 -0
gisserver/operations/wfs20.py
CHANGED
|
@@ -7,6 +7,7 @@ Useful docs:
|
|
|
7
7
|
* https://mapserver.org/development/rfc/ms-rfc-105.html
|
|
8
8
|
* https://enonline.supermap.com/iExpress9D/API/WFS/WFS200/WFS_2.0.0_introduction.htm
|
|
9
9
|
"""
|
|
10
|
+
|
|
10
11
|
import logging
|
|
11
12
|
import math
|
|
12
13
|
import re
|
|
@@ -46,12 +47,7 @@ class GetCapabilities(WFSMethod):
|
|
|
46
47
|
""" "This operation returns map features, and available operations this WFS server supports."""
|
|
47
48
|
|
|
48
49
|
output_formats = [
|
|
49
|
-
OutputFormat(
|
|
50
|
-
"application/gml+xml",
|
|
51
|
-
version="3.2",
|
|
52
|
-
renderer_class=output.gml32_value_renderer,
|
|
53
|
-
title="GML",
|
|
54
|
-
),
|
|
50
|
+
OutputFormat("application/gml+xml", version="3.2", title="GML"), # for FME
|
|
55
51
|
OutputFormat("text/xml"),
|
|
56
52
|
]
|
|
57
53
|
xml_template_name = "get_capabilities.xml"
|
|
@@ -94,9 +90,7 @@ class GetCapabilities(WFSMethod):
|
|
|
94
90
|
"AcceptVersions", "Can't provide both ACCEPTVERSIONS and VERSION"
|
|
95
91
|
)
|
|
96
92
|
|
|
97
|
-
matched_versions = set(accept_versions.split(",")).intersection(
|
|
98
|
-
self.view.accept_versions
|
|
99
|
-
)
|
|
93
|
+
matched_versions = set(accept_versions.split(",")).intersection(self.view.accept_versions)
|
|
100
94
|
if not matched_versions:
|
|
101
95
|
allowed = ", ".join(self.view.accept_versions)
|
|
102
96
|
raise VersionNegotiationFailed(
|
|
@@ -156,7 +150,7 @@ class DescribeFeatureType(WFSTypeNamesMethod):
|
|
|
156
150
|
"application/gml+xml",
|
|
157
151
|
version="3.2",
|
|
158
152
|
renderer_class=output.XMLSchemaRenderer,
|
|
159
|
-
)
|
|
153
|
+
),
|
|
160
154
|
# OutputFormat("text/xml", subtype="gml/3.1.1"),
|
|
161
155
|
]
|
|
162
156
|
|
|
@@ -279,9 +273,7 @@ class BaseWFSGetDataMethod(WFSTypeNamesMethod):
|
|
|
279
273
|
raise
|
|
280
274
|
logger.exception("WFS request failed: %s\nParams: %r", str(e), params)
|
|
281
275
|
msg = str(e)
|
|
282
|
-
locator = (
|
|
283
|
-
"srsName" if "Cannot find SRID" in msg else self._get_locator(**params)
|
|
284
|
-
)
|
|
276
|
+
locator = "srsName" if "Cannot find SRID" in msg else self._get_locator(**params)
|
|
285
277
|
raise InvalidParameterValue(locator, f"Invalid request: {msg}") from e
|
|
286
278
|
except (TypeError, ValueError) as e:
|
|
287
279
|
# TypeError/ValueError could reference a datatype mismatch in an
|
|
@@ -352,9 +344,7 @@ class BaseWFSGetDataMethod(WFSTypeNamesMethod):
|
|
|
352
344
|
if not output_crs and collection.results:
|
|
353
345
|
output_crs = collection.results[0].feature_type.crs
|
|
354
346
|
|
|
355
|
-
outputFormat.renderer_class.decorate_collection(
|
|
356
|
-
collection, output_crs, **params
|
|
357
|
-
)
|
|
347
|
+
outputFormat.renderer_class.decorate_collection(collection, output_crs, **params)
|
|
358
348
|
|
|
359
349
|
if stop != math.inf:
|
|
360
350
|
if start > 0:
|
|
@@ -362,7 +352,7 @@ class BaseWFSGetDataMethod(WFSTypeNamesMethod):
|
|
|
362
352
|
STARTINDEX=max(0, start - page_size),
|
|
363
353
|
COUNT=page_size,
|
|
364
354
|
)
|
|
365
|
-
if
|
|
355
|
+
if collection.has_next:
|
|
366
356
|
# TODO: fix this when returning multiple typeNames:
|
|
367
357
|
collection.next = self._replace_url_params(
|
|
368
358
|
STARTINDEX=start + page_size,
|
|
@@ -479,9 +469,7 @@ class GetPropertyValue(BaseWFSGetDataMethod):
|
|
|
479
469
|
renderer_class=output.gml32_value_renderer,
|
|
480
470
|
title="GML",
|
|
481
471
|
),
|
|
482
|
-
OutputFormat(
|
|
483
|
-
"text/xml", subtype="gml/3.2", renderer_class=output.gml32_value_renderer
|
|
484
|
-
),
|
|
472
|
+
OutputFormat("text/xml", subtype="gml/3.2", renderer_class=output.gml32_value_renderer),
|
|
485
473
|
]
|
|
486
474
|
|
|
487
475
|
parameters = BaseWFSGetDataMethod.parameters + [
|
gisserver/output/__init__.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"""All supported output formats"""
|
|
2
|
+
|
|
3
|
+
from django.utils.functional import classproperty
|
|
4
|
+
|
|
2
5
|
from gisserver import conf
|
|
3
6
|
|
|
4
7
|
from .base import OutputRenderer
|
|
@@ -13,8 +16,6 @@ from .gml32 import (
|
|
|
13
16
|
from .results import FeatureCollection, SimpleFeatureCollection
|
|
14
17
|
from .xmlschema import XMLSchemaRenderer
|
|
15
18
|
|
|
16
|
-
from django.utils.functional import classproperty
|
|
17
|
-
|
|
18
19
|
__all__ = [
|
|
19
20
|
"OutputRenderer",
|
|
20
21
|
"FeatureCollection",
|
|
@@ -40,6 +41,10 @@ def select_renderer(native_renderer_class, db_renderer_class):
|
|
|
40
41
|
"""
|
|
41
42
|
|
|
42
43
|
class SelectRenderer:
|
|
44
|
+
"""A proxy to the actual rendering class.
|
|
45
|
+
Its structure still allows accessing class-properties of the actual class.
|
|
46
|
+
"""
|
|
47
|
+
|
|
43
48
|
@classproperty
|
|
44
49
|
def real_class(self):
|
|
45
50
|
if conf.GISSERVER_USE_DB_RENDERING:
|
gisserver/output/base.py
CHANGED
|
@@ -59,9 +59,7 @@ class OutputRenderer:
|
|
|
59
59
|
self.app_xml_namespace = method.view.xml_namespace
|
|
60
60
|
|
|
61
61
|
@classmethod
|
|
62
|
-
def decorate_collection(
|
|
63
|
-
cls, collection: FeatureCollection, output_crs: CRS, **params
|
|
64
|
-
):
|
|
62
|
+
def decorate_collection(cls, collection: FeatureCollection, output_crs: CRS, **params):
|
|
65
63
|
"""Perform presentation-layer logic enhancements on the queryset."""
|
|
66
64
|
for sub_collection in collection.results:
|
|
67
65
|
# Validate the presentation-level parameters for this feature:
|
|
@@ -125,9 +123,7 @@ class OutputRenderer:
|
|
|
125
123
|
return [
|
|
126
124
|
models.Prefetch(
|
|
127
125
|
orm_relation.orm_path,
|
|
128
|
-
queryset=cls.get_prefetch_queryset(
|
|
129
|
-
feature_type, orm_relation, output_crs
|
|
130
|
-
),
|
|
126
|
+
queryset=cls.get_prefetch_queryset(feature_type, orm_relation, output_crs),
|
|
131
127
|
)
|
|
132
128
|
for orm_relation in feature_type.orm_relations
|
|
133
129
|
]
|
|
@@ -173,10 +169,7 @@ class OutputRenderer:
|
|
|
173
169
|
# Offer a common quick content-disposition logic that works for all possible queries.
|
|
174
170
|
sub_collection = self.collection.results[0]
|
|
175
171
|
if sub_collection.stop == math.inf:
|
|
176
|
-
if sub_collection.start
|
|
177
|
-
page = f"{sub_collection.start}-end"
|
|
178
|
-
else:
|
|
179
|
-
page = "all"
|
|
172
|
+
page = f"{sub_collection.start}-end" if sub_collection.start else "all"
|
|
180
173
|
elif sub_collection.stop:
|
|
181
174
|
page = f"{sub_collection.start}-{sub_collection.stop - 1}"
|
|
182
175
|
else:
|
|
@@ -184,9 +177,7 @@ class OutputRenderer:
|
|
|
184
177
|
|
|
185
178
|
return {
|
|
186
179
|
"Content-Disposition": self.content_disposition.format(
|
|
187
|
-
typenames="+".join(
|
|
188
|
-
sub.feature_type.name for sub in self.collection.results
|
|
189
|
-
),
|
|
180
|
+
typenames="+".join(sub.feature_type.name for sub in self.collection.results),
|
|
190
181
|
page=page,
|
|
191
182
|
date=self.collection.date.strftime("%Y-%m-%d %H.%M.%S%z"),
|
|
192
183
|
timestamp=self.collection.timestamp,
|
gisserver/output/csv.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Output rendering logic for GeoJSON."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import csv
|
|
5
6
|
from datetime import datetime, timezone
|
|
7
|
+
from io import StringIO
|
|
6
8
|
|
|
7
9
|
from django.conf import settings
|
|
8
10
|
from django.db import models
|
|
@@ -19,7 +21,6 @@ from gisserver.geometries import CRS
|
|
|
19
21
|
from gisserver.types import XsdComplexType, XsdElement
|
|
20
22
|
|
|
21
23
|
from .base import OutputRenderer
|
|
22
|
-
from .buffer import StringBuffer
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class CSVRenderer(OutputRenderer):
|
|
@@ -31,6 +32,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
31
32
|
content_type = "text/csv; charset=utf-8"
|
|
32
33
|
content_disposition = 'attachment; filename="{typenames} {page} {date}.csv"'
|
|
33
34
|
max_page_size = conf.GISSERVER_CSV_MAX_PAGE_SIZE
|
|
35
|
+
chunk_size = 40_000
|
|
34
36
|
|
|
35
37
|
#: The outputted CSV dialect. This can be a csv.Dialect subclass
|
|
36
38
|
#: or one of the registered names like: "unix", "excel", "excel-tab"
|
|
@@ -66,7 +68,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
66
68
|
return queryset
|
|
67
69
|
|
|
68
70
|
def render_stream(self):
|
|
69
|
-
output =
|
|
71
|
+
output = StringIO()
|
|
70
72
|
writer = csv.writer(output, dialect=self.dialect)
|
|
71
73
|
|
|
72
74
|
is_first_collection = True
|
|
@@ -78,11 +80,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
78
80
|
output.write("\n\n")
|
|
79
81
|
|
|
80
82
|
# Write the header
|
|
81
|
-
fields = [
|
|
82
|
-
f
|
|
83
|
-
for f in sub_collection.feature_type.xsd_type.elements
|
|
84
|
-
if not f.is_many
|
|
85
|
-
]
|
|
83
|
+
fields = [f for f in sub_collection.feature_type.xsd_type.elements if not f.is_many]
|
|
86
84
|
writer.writerow(self.get_header(fields))
|
|
87
85
|
|
|
88
86
|
# By using .iterator(), the results are streamed with as little memory as
|
|
@@ -93,10 +91,13 @@ class CSVRenderer(OutputRenderer):
|
|
|
93
91
|
|
|
94
92
|
# Only perform a 'yield' every once in a while,
|
|
95
93
|
# as it goes back-and-forth for writing it to the client.
|
|
96
|
-
if output.
|
|
97
|
-
|
|
94
|
+
if output.tell() > self.chunk_size:
|
|
95
|
+
csv_chunk = output.getvalue()
|
|
96
|
+
output.seek(0)
|
|
97
|
+
output.truncate(0)
|
|
98
|
+
yield csv_chunk
|
|
98
99
|
|
|
99
|
-
yield output.
|
|
100
|
+
yield output.getvalue()
|
|
100
101
|
|
|
101
102
|
def render_exception(self, exception: Exception):
|
|
102
103
|
"""Render the exception in a format that fits with the output."""
|
|
@@ -159,15 +160,11 @@ class DBCSVRenderer(CSVRenderer):
|
|
|
159
160
|
output_crs: CRS,
|
|
160
161
|
**params,
|
|
161
162
|
) -> models.QuerySet:
|
|
162
|
-
queryset = super().decorate_queryset(
|
|
163
|
-
feature_type, queryset, output_crs, **params
|
|
164
|
-
)
|
|
163
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
165
164
|
|
|
166
165
|
# Instead of reading the binary geometry data,
|
|
167
166
|
# ask the database to generate EWKT data directly.
|
|
168
|
-
geo_selects = get_db_geometry_selects(
|
|
169
|
-
feature_type.xsd_type.geometry_elements, output_crs
|
|
170
|
-
)
|
|
167
|
+
geo_selects = get_db_geometry_selects(feature_type.xsd_type.geometry_elements, output_crs)
|
|
171
168
|
if geo_selects:
|
|
172
169
|
queryset = queryset.defer(*geo_selects.keys()).annotate(
|
|
173
170
|
**build_db_annotations(geo_selects, "_as_ewkt_{name}", AsEWKT)
|
gisserver/output/geojson.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Output rendering logic for GeoJSON."""
|
|
2
|
+
|
|
2
3
|
from datetime import datetime, timezone
|
|
3
4
|
from decimal import Decimal
|
|
5
|
+
from io import BytesIO
|
|
4
6
|
from typing import cast
|
|
5
7
|
|
|
6
8
|
import orjson
|
|
@@ -16,7 +18,6 @@ from gisserver.geometries import CRS
|
|
|
16
18
|
from gisserver.types import XsdComplexType
|
|
17
19
|
|
|
18
20
|
from .base import OutputRenderer
|
|
19
|
-
from .buffer import BytesBuffer
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _json_default(obj):
|
|
@@ -40,6 +41,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
40
41
|
content_type = "application/geo+json; charset=utf-8"
|
|
41
42
|
content_disposition = 'inline; filename="{typenames} {page} {date}.geojson"'
|
|
42
43
|
max_page_size = conf.GISSERVER_GEOJSON_MAX_PAGE_SIZE
|
|
44
|
+
chunk_size = 40_000
|
|
43
45
|
|
|
44
46
|
@classmethod
|
|
45
47
|
def decorate_queryset(
|
|
@@ -49,9 +51,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
49
51
|
output_crs: CRS,
|
|
50
52
|
**params,
|
|
51
53
|
):
|
|
52
|
-
queryset = super().decorate_queryset(
|
|
53
|
-
feature_type, queryset, output_crs, **params
|
|
54
|
-
)
|
|
54
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
55
55
|
|
|
56
56
|
# Other geometries can be excluded as these are not rendered by 'properties'
|
|
57
57
|
other_geometries = [
|
|
@@ -65,7 +65,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
65
65
|
return queryset
|
|
66
66
|
|
|
67
67
|
def render_stream(self):
|
|
68
|
-
output =
|
|
68
|
+
output = BytesIO()
|
|
69
69
|
|
|
70
70
|
# Generate the header from a Python dict,
|
|
71
71
|
# but replace the last "}" into a comma, to allow writing more
|
|
@@ -96,8 +96,11 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
96
96
|
|
|
97
97
|
# Only perform a 'yield' every once in a while,
|
|
98
98
|
# as it goes back-and-forth for writing it to the client.
|
|
99
|
-
if output.
|
|
100
|
-
|
|
99
|
+
if output.tell() > self.chunk_size:
|
|
100
|
+
json_chunk = output.getvalue()
|
|
101
|
+
output.seek(0)
|
|
102
|
+
output.truncate(0)
|
|
103
|
+
yield json_chunk
|
|
101
104
|
|
|
102
105
|
# Instead of performing an expensive .count() on the start of the page,
|
|
103
106
|
# write this as a last field at the end of the response.
|
|
@@ -106,7 +109,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
106
109
|
footer = self.get_footer()
|
|
107
110
|
output.write(orjson.dumps(footer)[1:])
|
|
108
111
|
output.write(b"\n")
|
|
109
|
-
yield output.
|
|
112
|
+
yield output.getvalue()
|
|
110
113
|
|
|
111
114
|
def render_exception(self, exception: Exception):
|
|
112
115
|
"""Render the exception in a format that fits with the output."""
|
|
@@ -115,9 +118,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
115
118
|
else:
|
|
116
119
|
return f"/* {exception.__class__.__name__} during rendering! */\n"
|
|
117
120
|
|
|
118
|
-
def render_feature(
|
|
119
|
-
self, feature_type: FeatureType, instance: models.Model
|
|
120
|
-
) -> bytes:
|
|
121
|
+
def render_feature(self, feature_type: FeatureType, instance: models.Model) -> bytes:
|
|
121
122
|
"""Render the output of a single feature"""
|
|
122
123
|
|
|
123
124
|
# Get all instance attributes:
|
|
@@ -266,16 +267,12 @@ class DBGeoJsonRenderer(GeoJsonRenderer):
|
|
|
266
267
|
"""
|
|
267
268
|
|
|
268
269
|
@classmethod
|
|
269
|
-
def decorate_queryset(
|
|
270
|
-
self, feature_type: FeatureType, queryset, output_crs, **params
|
|
271
|
-
):
|
|
270
|
+
def decorate_queryset(self, feature_type: FeatureType, queryset, output_crs, **params):
|
|
272
271
|
"""Update the queryset to let the database render the GML output.
|
|
273
272
|
This is far more efficient then GeoDjango's logic, which performs a
|
|
274
273
|
C-API call for every single coordinate of a geometry.
|
|
275
274
|
"""
|
|
276
|
-
queryset = super().decorate_queryset(
|
|
277
|
-
feature_type, queryset, output_crs, **params
|
|
278
|
-
)
|
|
275
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
279
276
|
# If desired, the entire FeatureCollection could be rendered
|
|
280
277
|
# in PostgreSQL as well: https://postgis.net/docs/ST_AsGeoJSON.html
|
|
281
278
|
if feature_type.geometry_fields:
|