django-gisserver 1.2.7__py3-none-any.whl → 1.4.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.2.7.dist-info → django_gisserver-1.4.0.dist-info}/METADATA +12 -12
- django_gisserver-1.4.0.dist-info/RECORD +54 -0
- {django_gisserver-1.2.7.dist-info → django_gisserver-1.4.0.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 +18 -29
- 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 +15 -19
- gisserver/output/geojson.py +16 -20
- gisserver/output/gml32.py +310 -283
- gisserver/output/gml32_lxml.py +612 -0
- gisserver/output/results.py +107 -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.2.7.dist-info/RECORD +0 -54
- gisserver/output/buffer.py +0 -64
- {django_gisserver-1.2.7.dist-info → django_gisserver-1.4.0.dist-info}/LICENSE +0 -0
- {django_gisserver-1.2.7.dist-info → django_gisserver-1.4.0.dist-info}/top_level.txt +0 -0
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,12 +1,13 @@
|
|
|
1
1
|
"""Output rendering logic for GeoJSON."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import csv
|
|
5
|
-
from datetime import datetime
|
|
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
|
|
9
|
-
from django.utils.timezone import utc
|
|
10
11
|
|
|
11
12
|
from gisserver import conf
|
|
12
13
|
from gisserver.db import (
|
|
@@ -20,7 +21,6 @@ from gisserver.geometries import CRS
|
|
|
20
21
|
from gisserver.types import XsdComplexType, XsdElement
|
|
21
22
|
|
|
22
23
|
from .base import OutputRenderer
|
|
23
|
-
from .buffer import StringBuffer
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class CSVRenderer(OutputRenderer):
|
|
@@ -32,6 +32,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
32
32
|
content_type = "text/csv; charset=utf-8"
|
|
33
33
|
content_disposition = 'attachment; filename="{typenames} {page} {date}.csv"'
|
|
34
34
|
max_page_size = conf.GISSERVER_CSV_MAX_PAGE_SIZE
|
|
35
|
+
chunk_size = 40_000
|
|
35
36
|
|
|
36
37
|
#: The outputted CSV dialect. This can be a csv.Dialect subclass
|
|
37
38
|
#: or one of the registered names like: "unix", "excel", "excel-tab"
|
|
@@ -67,7 +68,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
67
68
|
return queryset
|
|
68
69
|
|
|
69
70
|
def render_stream(self):
|
|
70
|
-
output =
|
|
71
|
+
output = StringIO()
|
|
71
72
|
writer = csv.writer(output, dialect=self.dialect)
|
|
72
73
|
|
|
73
74
|
is_first_collection = True
|
|
@@ -79,11 +80,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
79
80
|
output.write("\n\n")
|
|
80
81
|
|
|
81
82
|
# Write the header
|
|
82
|
-
fields = [
|
|
83
|
-
f
|
|
84
|
-
for f in sub_collection.feature_type.xsd_type.elements
|
|
85
|
-
if not f.is_many
|
|
86
|
-
]
|
|
83
|
+
fields = [f for f in sub_collection.feature_type.xsd_type.elements if not f.is_many]
|
|
87
84
|
writer.writerow(self.get_header(fields))
|
|
88
85
|
|
|
89
86
|
# By using .iterator(), the results are streamed with as little memory as
|
|
@@ -94,10 +91,13 @@ class CSVRenderer(OutputRenderer):
|
|
|
94
91
|
|
|
95
92
|
# Only perform a 'yield' every once in a while,
|
|
96
93
|
# as it goes back-and-forth for writing it to the client.
|
|
97
|
-
if output.
|
|
98
|
-
|
|
94
|
+
if output.tell() > self.chunk_size:
|
|
95
|
+
csv_chunk = output.getvalue()
|
|
96
|
+
output.seek(0)
|
|
97
|
+
output.truncate(0)
|
|
98
|
+
yield csv_chunk
|
|
99
99
|
|
|
100
|
-
yield output.
|
|
100
|
+
yield output.getvalue()
|
|
101
101
|
|
|
102
102
|
def render_exception(self, exception: Exception):
|
|
103
103
|
"""Render the exception in a format that fits with the output."""
|
|
@@ -137,7 +137,7 @@ class CSVRenderer(OutputRenderer):
|
|
|
137
137
|
# Array field
|
|
138
138
|
append(",".join(map(str, value)))
|
|
139
139
|
elif isinstance(value, datetime):
|
|
140
|
-
append(str(value.astimezone(utc)))
|
|
140
|
+
append(str(value.astimezone(timezone.utc)))
|
|
141
141
|
else:
|
|
142
142
|
append(value)
|
|
143
143
|
return values
|
|
@@ -160,15 +160,11 @@ class DBCSVRenderer(CSVRenderer):
|
|
|
160
160
|
output_crs: CRS,
|
|
161
161
|
**params,
|
|
162
162
|
) -> models.QuerySet:
|
|
163
|
-
queryset = super().decorate_queryset(
|
|
164
|
-
feature_type, queryset, output_crs, **params
|
|
165
|
-
)
|
|
163
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
166
164
|
|
|
167
165
|
# Instead of reading the binary geometry data,
|
|
168
166
|
# ask the database to generate EWKT data directly.
|
|
169
|
-
geo_selects = get_db_geometry_selects(
|
|
170
|
-
feature_type.xsd_type.geometry_elements, output_crs
|
|
171
|
-
)
|
|
167
|
+
geo_selects = get_db_geometry_selects(feature_type.xsd_type.geometry_elements, output_crs)
|
|
172
168
|
if geo_selects:
|
|
173
169
|
queryset = queryset.defer(*geo_selects.keys()).annotate(
|
|
174
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
|
|
@@ -8,7 +10,6 @@ from django.conf import settings
|
|
|
8
10
|
from django.contrib.gis.db.models.functions import AsGeoJSON
|
|
9
11
|
from django.db import models
|
|
10
12
|
from django.utils.functional import Promise
|
|
11
|
-
from django.utils.timezone import utc
|
|
12
13
|
|
|
13
14
|
from gisserver import conf
|
|
14
15
|
from gisserver.db import get_db_geometry_target
|
|
@@ -17,7 +18,6 @@ from gisserver.geometries import CRS
|
|
|
17
18
|
from gisserver.types import XsdComplexType
|
|
18
19
|
|
|
19
20
|
from .base import OutputRenderer
|
|
20
|
-
from .buffer import BytesBuffer
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def _json_default(obj):
|
|
@@ -41,6 +41,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
41
41
|
content_type = "application/geo+json; charset=utf-8"
|
|
42
42
|
content_disposition = 'inline; filename="{typenames} {page} {date}.geojson"'
|
|
43
43
|
max_page_size = conf.GISSERVER_GEOJSON_MAX_PAGE_SIZE
|
|
44
|
+
chunk_size = 40_000
|
|
44
45
|
|
|
45
46
|
@classmethod
|
|
46
47
|
def decorate_queryset(
|
|
@@ -50,9 +51,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
50
51
|
output_crs: CRS,
|
|
51
52
|
**params,
|
|
52
53
|
):
|
|
53
|
-
queryset = super().decorate_queryset(
|
|
54
|
-
feature_type, queryset, output_crs, **params
|
|
55
|
-
)
|
|
54
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
56
55
|
|
|
57
56
|
# Other geometries can be excluded as these are not rendered by 'properties'
|
|
58
57
|
other_geometries = [
|
|
@@ -66,7 +65,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
66
65
|
return queryset
|
|
67
66
|
|
|
68
67
|
def render_stream(self):
|
|
69
|
-
output =
|
|
68
|
+
output = BytesIO()
|
|
70
69
|
|
|
71
70
|
# Generate the header from a Python dict,
|
|
72
71
|
# but replace the last "}" into a comma, to allow writing more
|
|
@@ -97,8 +96,11 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
97
96
|
|
|
98
97
|
# Only perform a 'yield' every once in a while,
|
|
99
98
|
# as it goes back-and-forth for writing it to the client.
|
|
100
|
-
if output.
|
|
101
|
-
|
|
99
|
+
if output.tell() > self.chunk_size:
|
|
100
|
+
json_chunk = output.getvalue()
|
|
101
|
+
output.seek(0)
|
|
102
|
+
output.truncate(0)
|
|
103
|
+
yield json_chunk
|
|
102
104
|
|
|
103
105
|
# Instead of performing an expensive .count() on the start of the page,
|
|
104
106
|
# write this as a last field at the end of the response.
|
|
@@ -107,7 +109,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
107
109
|
footer = self.get_footer()
|
|
108
110
|
output.write(orjson.dumps(footer)[1:])
|
|
109
111
|
output.write(b"\n")
|
|
110
|
-
yield output.
|
|
112
|
+
yield output.getvalue()
|
|
111
113
|
|
|
112
114
|
def render_exception(self, exception: Exception):
|
|
113
115
|
"""Render the exception in a format that fits with the output."""
|
|
@@ -116,9 +118,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
116
118
|
else:
|
|
117
119
|
return f"/* {exception.__class__.__name__} during rendering! */\n"
|
|
118
120
|
|
|
119
|
-
def render_feature(
|
|
120
|
-
self, feature_type: FeatureType, instance: models.Model
|
|
121
|
-
) -> bytes:
|
|
121
|
+
def render_feature(self, feature_type: FeatureType, instance: models.Model) -> bytes:
|
|
122
122
|
"""Render the output of a single feature"""
|
|
123
123
|
|
|
124
124
|
# Get all instance attributes:
|
|
@@ -147,7 +147,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
147
147
|
|
|
148
148
|
def _format_geojson_value(self, value):
|
|
149
149
|
if isinstance(value, datetime):
|
|
150
|
-
return value.astimezone(utc)
|
|
150
|
+
return value.astimezone(timezone.utc)
|
|
151
151
|
elif isinstance(value, models.Model):
|
|
152
152
|
# ForeignKey, not defined as complex type.
|
|
153
153
|
return str(value)
|
|
@@ -267,16 +267,12 @@ class DBGeoJsonRenderer(GeoJsonRenderer):
|
|
|
267
267
|
"""
|
|
268
268
|
|
|
269
269
|
@classmethod
|
|
270
|
-
def decorate_queryset(
|
|
271
|
-
self, feature_type: FeatureType, queryset, output_crs, **params
|
|
272
|
-
):
|
|
270
|
+
def decorate_queryset(self, feature_type: FeatureType, queryset, output_crs, **params):
|
|
273
271
|
"""Update the queryset to let the database render the GML output.
|
|
274
272
|
This is far more efficient then GeoDjango's logic, which performs a
|
|
275
273
|
C-API call for every single coordinate of a geometry.
|
|
276
274
|
"""
|
|
277
|
-
queryset = super().decorate_queryset(
|
|
278
|
-
feature_type, queryset, output_crs, **params
|
|
279
|
-
)
|
|
275
|
+
queryset = super().decorate_queryset(feature_type, queryset, output_crs, **params)
|
|
280
276
|
# If desired, the entire FeatureCollection could be rendered
|
|
281
277
|
# in PostgreSQL as well: https://postgis.net/docs/ST_AsGeoJSON.html
|
|
282
278
|
if feature_type.geometry_fields:
|