django-gisserver 1.4.1__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.4.1.dist-info → django_gisserver-2.0.dist-info}/METADATA +23 -13
- django_gisserver-2.0.dist-info/RECORD +66 -0
- {django_gisserver-1.4.1.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 +63 -60
- gisserver/exceptions.py +47 -9
- gisserver/extensions/__init__.py +4 -0
- gisserver/{parsers/fes20 → extensions}/functions.py +11 -5
- gisserver/extensions/queries.py +261 -0
- gisserver/features.py +267 -240
- gisserver/geometries.py +34 -39
- gisserver/management/__init__.py +0 -0
- gisserver/management/commands/__init__.py +0 -0
- gisserver/management/commands/loadgeojson.py +291 -0
- gisserver/operations/base.py +129 -305
- gisserver/operations/wfs20.py +428 -336
- gisserver/output/__init__.py +10 -48
- gisserver/output/base.py +198 -143
- gisserver/output/csv.py +81 -85
- gisserver/output/geojson.py +63 -72
- gisserver/output/gml32.py +310 -281
- gisserver/output/iters.py +207 -0
- gisserver/output/results.py +71 -30
- gisserver/output/stored.py +143 -0
- gisserver/output/utils.py +75 -154
- gisserver/output/xmlschema.py +86 -47
- gisserver/parsers/__init__.py +10 -10
- gisserver/parsers/ast.py +320 -0
- gisserver/parsers/fes20/__init__.py +15 -11
- gisserver/parsers/fes20/expressions.py +89 -50
- 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 +336 -128
- gisserver/parsers/fes20/sorting.py +107 -34
- gisserver/parsers/gml/__init__.py +12 -11
- gisserver/parsers/gml/base.py +6 -3
- 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 +11 -11
- gisserver/templates/gisserver/wfs/feature_field.html +2 -2
- gisserver/templatetags/gisserver_tags.py +20 -0
- gisserver/types.py +375 -258
- gisserver/views.py +206 -75
- django_gisserver-1.4.1.dist-info/RECORD +0 -53
- gisserver/parsers/base.py +0 -149
- gisserver/parsers/fes20/query.py +0 -275
- gisserver/parsers/tags.py +0 -102
- gisserver/queries/__init__.py +0 -34
- gisserver/queries/adhoc.py +0 -181
- gisserver/queries/base.py +0 -146
- gisserver/queries/stored.py +0 -205
- 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.4.1.dist-info → django_gisserver-2.0.dist-info}/LICENSE +0 -0
- {django_gisserver-1.4.1.dist-info → django_gisserver-2.0.dist-info}/top_level.txt +0 -0
gisserver/output/csv.py
CHANGED
|
@@ -6,24 +6,17 @@ import csv
|
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
7
|
from io import StringIO
|
|
8
8
|
|
|
9
|
-
from django.conf import settings
|
|
10
9
|
from django.db import models
|
|
11
10
|
|
|
12
11
|
from gisserver import conf
|
|
13
|
-
from gisserver.db import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
get_db_annotation,
|
|
17
|
-
get_db_geometry_selects,
|
|
18
|
-
)
|
|
19
|
-
from gisserver.features import FeatureType
|
|
20
|
-
from gisserver.geometries import CRS
|
|
21
|
-
from gisserver.types import XsdComplexType, 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
|
|
22
15
|
|
|
23
|
-
from .base import
|
|
16
|
+
from .base import CollectionOutputRenderer
|
|
24
17
|
|
|
25
18
|
|
|
26
|
-
class CSVRenderer(
|
|
19
|
+
class CSVRenderer(CollectionOutputRenderer):
|
|
27
20
|
"""Fast CSV renderer, using a stream response.
|
|
28
21
|
|
|
29
22
|
The complex encoding bits are handled by the "csv" library.
|
|
@@ -38,41 +31,30 @@ class CSVRenderer(OutputRenderer):
|
|
|
38
31
|
#: or one of the registered names like: "unix", "excel", "excel-tab"
|
|
39
32
|
dialect = "unix"
|
|
40
33
|
|
|
41
|
-
@classmethod
|
|
42
34
|
def decorate_queryset(
|
|
43
|
-
|
|
44
|
-
feature_type: FeatureType,
|
|
45
|
-
queryset: models.QuerySet,
|
|
46
|
-
output_crs: CRS,
|
|
47
|
-
**params,
|
|
35
|
+
self, projection: FeatureProjection, queryset: models.QuerySet
|
|
48
36
|
) -> models.QuerySet:
|
|
49
37
|
"""Make sure relations are included with select-related to avoid N-queries.
|
|
50
38
|
Using prefetch_related() isn't possible with .iterator().
|
|
51
39
|
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
for xsd_element in xsd_type.flattened_elements
|
|
63
|
-
if not xsd_element.is_many
|
|
64
|
-
}
|
|
65
|
-
if related:
|
|
66
|
-
queryset = queryset.select_related(*related)
|
|
67
|
-
|
|
68
|
-
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)
|
|
69
50
|
|
|
70
51
|
def render_stream(self):
|
|
71
|
-
output = StringIO()
|
|
52
|
+
self.output = output = StringIO()
|
|
72
53
|
writer = csv.writer(output, dialect=self.dialect)
|
|
73
54
|
|
|
74
55
|
is_first_collection = True
|
|
75
56
|
for sub_collection in self.collection.results:
|
|
57
|
+
projection = sub_collection.projection
|
|
76
58
|
if is_first_collection:
|
|
77
59
|
is_first_collection = False
|
|
78
60
|
else:
|
|
@@ -80,14 +62,12 @@ class CSVRenderer(OutputRenderer):
|
|
|
80
62
|
output.write("\n\n")
|
|
81
63
|
|
|
82
64
|
# Write the header
|
|
83
|
-
|
|
84
|
-
writer.writerow(self.get_header(
|
|
65
|
+
xsd_elements = projection.xsd_root_elements
|
|
66
|
+
writer.writerow(self.get_header(projection, xsd_elements))
|
|
85
67
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for instance in sub_collection.iterator():
|
|
90
|
-
writer.writerow(self.get_row(instance, fields))
|
|
68
|
+
# Write all rows
|
|
69
|
+
for instance in sub_collection:
|
|
70
|
+
writer.writerow(self.get_row(instance, projection, xsd_elements))
|
|
91
71
|
|
|
92
72
|
# Only perform a 'yield' every once in a while,
|
|
93
73
|
# as it goes back-and-forth for writing it to the client.
|
|
@@ -101,38 +81,50 @@ class CSVRenderer(OutputRenderer):
|
|
|
101
81
|
|
|
102
82
|
def render_exception(self, exception: Exception):
|
|
103
83
|
"""Render the exception in a format that fits with the output."""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return f"\n\n{exception.__class__.__name__} during rendering!\n"
|
|
84
|
+
message = super().render_exception(exception)
|
|
85
|
+
buffer = self.output.getvalue()
|
|
86
|
+
return f"{buffer}\n\n{message}\n"
|
|
108
87
|
|
|
109
|
-
def get_header(
|
|
88
|
+
def get_header(
|
|
89
|
+
self, projection: FeatureProjection, xsd_elements: list[XsdElement], prefix=""
|
|
90
|
+
) -> list[str]:
|
|
110
91
|
"""Return all field names."""
|
|
111
92
|
names = []
|
|
112
93
|
append = names.append
|
|
113
|
-
for
|
|
114
|
-
if
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
94
|
+
for xsd_element in xsd_elements:
|
|
95
|
+
if xsd_element.type.is_complex_type:
|
|
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
|
+
)
|
|
118
103
|
else:
|
|
119
|
-
append(
|
|
104
|
+
append(f"{prefix}{xsd_element.name}")
|
|
120
105
|
|
|
121
106
|
return names
|
|
122
107
|
|
|
123
|
-
def get_row(
|
|
108
|
+
def get_row(
|
|
109
|
+
self, instance: models.Model, projection: FeatureProjection, xsd_elements: list[XsdElement]
|
|
110
|
+
):
|
|
124
111
|
"""Return all field values for a single row."""
|
|
125
112
|
values = []
|
|
126
113
|
append = values.append
|
|
127
|
-
for
|
|
128
|
-
if
|
|
129
|
-
append(self.render_geometry(instance,
|
|
114
|
+
for xsd_element in xsd_elements:
|
|
115
|
+
if xsd_element.type.is_geometry:
|
|
116
|
+
append(self.render_geometry(instance, xsd_element))
|
|
130
117
|
continue
|
|
131
118
|
|
|
132
|
-
value =
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
119
|
+
value = xsd_element.get_value(instance)
|
|
120
|
+
if xsd_element.type.is_complex_type:
|
|
121
|
+
values.extend(
|
|
122
|
+
self.get_row(
|
|
123
|
+
instance=value,
|
|
124
|
+
projection=projection,
|
|
125
|
+
xsd_elements=projection.xsd_child_nodes[xsd_element],
|
|
126
|
+
)
|
|
127
|
+
)
|
|
136
128
|
elif isinstance(value, list):
|
|
137
129
|
# Array field
|
|
138
130
|
append(",".join(map(str, value)))
|
|
@@ -142,35 +134,39 @@ class CSVRenderer(OutputRenderer):
|
|
|
142
134
|
append(value)
|
|
143
135
|
return values
|
|
144
136
|
|
|
145
|
-
def render_geometry(self, instance: models.Model,
|
|
137
|
+
def render_geometry(self, instance: models.Model, geo_element: GeometryXsdElement):
|
|
146
138
|
"""Render the contents of a geometry value."""
|
|
147
|
-
return
|
|
139
|
+
return geo_element.get_value(instance)
|
|
148
140
|
|
|
149
141
|
|
|
150
142
|
class DBCSVRenderer(CSVRenderer):
|
|
151
143
|
"""Further optimized CSV renderer that uses the database to render EWKT.
|
|
152
|
-
This is about 40% faster
|
|
144
|
+
This is about 40% faster than calling the GEOS C-API from python.
|
|
153
145
|
"""
|
|
154
146
|
|
|
155
|
-
@classmethod
|
|
156
147
|
def decorate_queryset(
|
|
157
|
-
|
|
158
|
-
feature_type: FeatureType,
|
|
159
|
-
queryset: models.QuerySet,
|
|
160
|
-
output_crs: CRS,
|
|
161
|
-
**params,
|
|
148
|
+
self, projection: FeatureProjection, queryset: models.QuerySet
|
|
162
149
|
) -> models.QuerySet:
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
@@ -3,21 +3,19 @@
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
4
|
from decimal import Decimal
|
|
5
5
|
from io import BytesIO
|
|
6
|
-
from typing import cast
|
|
7
6
|
|
|
8
7
|
import orjson
|
|
9
|
-
from django.conf import settings
|
|
10
8
|
from django.contrib.gis.db.models.functions import AsGeoJSON
|
|
11
9
|
from django.db import models
|
|
12
10
|
from django.utils.functional import Promise
|
|
13
11
|
|
|
14
12
|
from gisserver import conf
|
|
15
13
|
from gisserver.db import get_db_geometry_target
|
|
16
|
-
from gisserver.
|
|
17
|
-
from gisserver.
|
|
18
|
-
from gisserver.types import
|
|
14
|
+
from gisserver.geometries import CRS84, WGS84
|
|
15
|
+
from gisserver.projection import FeatureProjection
|
|
16
|
+
from gisserver.types import XsdElement
|
|
19
17
|
|
|
20
|
-
from .base import
|
|
18
|
+
from .base import CollectionOutputRenderer
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def _json_default(obj):
|
|
@@ -27,7 +25,7 @@ def _json_default(obj):
|
|
|
27
25
|
raise TypeError(f"Unable to serialize {obj.__class__.__name__} to JSON")
|
|
28
26
|
|
|
29
27
|
|
|
30
|
-
class GeoJsonRenderer(
|
|
28
|
+
class GeoJsonRenderer(CollectionOutputRenderer):
|
|
31
29
|
"""Fast GeoJSON renderer, using a stream response.
|
|
32
30
|
|
|
33
31
|
The complex encoding bits are handled by the C-library "orjson"
|
|
@@ -43,29 +41,29 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
43
41
|
max_page_size = conf.GISSERVER_GEOJSON_MAX_PAGE_SIZE
|
|
44
42
|
chunk_size = 40_000
|
|
45
43
|
|
|
46
|
-
@classmethod
|
|
47
44
|
def decorate_queryset(
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
self,
|
|
46
|
+
projection: FeatureProjection,
|
|
50
47
|
queryset: models.QuerySet,
|
|
51
|
-
output_crs: CRS,
|
|
52
|
-
**params,
|
|
53
48
|
):
|
|
54
|
-
|
|
49
|
+
"""Redefine which fields to query, always include geometry, but remove all others"""
|
|
50
|
+
# make sure output CRS matches the coordinate ordering that GEOSGeometry.json returns
|
|
51
|
+
if projection.output_crs == WGS84:
|
|
52
|
+
projection.output_crs = CRS84
|
|
55
53
|
|
|
54
|
+
# Make sure geometry is always queried.
|
|
56
55
|
# Other geometries can be excluded as these are not rendered by 'properties'
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if other_geometries:
|
|
63
|
-
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
|
+
)
|
|
64
61
|
|
|
65
|
-
|
|
62
|
+
# Apply the normal optimizations with this altered projection
|
|
63
|
+
return super().decorate_queryset(projection, queryset)
|
|
66
64
|
|
|
67
65
|
def render_stream(self):
|
|
68
|
-
output = BytesIO()
|
|
66
|
+
self.output = output = BytesIO()
|
|
69
67
|
|
|
70
68
|
# Generate the header from a Python dict,
|
|
71
69
|
# but replace the last "}" into a comma, to allow writing more
|
|
@@ -78,6 +76,8 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
78
76
|
# Flatten the results, they are not grouped in a second FeatureCollection
|
|
79
77
|
is_first_collection = True
|
|
80
78
|
for sub_collection in self.collection.results:
|
|
79
|
+
projection = sub_collection.projection
|
|
80
|
+
|
|
81
81
|
if is_first_collection:
|
|
82
82
|
is_first_collection = False
|
|
83
83
|
else:
|
|
@@ -92,7 +92,7 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
92
92
|
|
|
93
93
|
# The "properties" object is generated by orjson.dumps(),
|
|
94
94
|
# while the "geometry" object uses the built-in 'GEOSGeometry.json' result.
|
|
95
|
-
output.write(self.render_feature(
|
|
95
|
+
output.write(self.render_feature(projection, instance))
|
|
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.
|
|
@@ -113,35 +113,28 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
113
113
|
|
|
114
114
|
def render_exception(self, exception: Exception):
|
|
115
115
|
"""Render the exception in a format that fits with the output."""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return f"/* {exception.__class__.__name__} during rendering! */\n"
|
|
116
|
+
message = super().render_exception(exception)
|
|
117
|
+
buffer = self.output.getvalue().decode()
|
|
118
|
+
return f"{buffer}/* {message} */\n"
|
|
120
119
|
|
|
121
|
-
def render_feature(self,
|
|
120
|
+
def render_feature(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
122
121
|
"""Render the output of a single feature"""
|
|
123
122
|
|
|
124
123
|
# Get all instance attributes:
|
|
125
|
-
properties = self.get_properties(
|
|
124
|
+
properties = self.get_properties(projection, projection.xsd_root_elements, instance)
|
|
125
|
+
feature_type = projection.feature_type
|
|
126
126
|
|
|
127
|
+
# Add the name field
|
|
127
128
|
if feature_type.show_name_field:
|
|
128
129
|
name = feature_type.get_display_value(instance)
|
|
129
|
-
|
|
130
|
+
json_geometry_name = b'"geometry_name":%b,' % orjson.dumps(name)
|
|
130
131
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
b" {"
|
|
135
|
-
b'"type":"Feature",'
|
|
136
|
-
b'"id":%b,'
|
|
137
|
-
b"%b"
|
|
138
|
-
b'"geometry":%b,'
|
|
139
|
-
b'"properties":%b'
|
|
140
|
-
b"}"
|
|
141
|
-
) % (
|
|
132
|
+
json_geometry_name = b""
|
|
133
|
+
|
|
134
|
+
return b' {"type":"Feature","id":%b,%b"geometry":%b,"properties":%b}' % (
|
|
142
135
|
orjson.dumps(f"{feature_type.name}.{instance.pk}"),
|
|
143
|
-
|
|
144
|
-
self.render_geometry(
|
|
136
|
+
json_geometry_name,
|
|
137
|
+
self.render_geometry(projection, instance),
|
|
145
138
|
orjson.dumps(properties, default=_json_default),
|
|
146
139
|
)
|
|
147
140
|
|
|
@@ -154,15 +147,17 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
154
147
|
else:
|
|
155
148
|
return value
|
|
156
149
|
|
|
157
|
-
def render_geometry(self,
|
|
150
|
+
def render_geometry(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
158
151
|
"""Generate the proper GeoJSON notation for a geometry.
|
|
159
152
|
This calls the GDAL C-API rendering found in 'GEOSGeometry.json'
|
|
160
153
|
"""
|
|
161
|
-
geometry =
|
|
154
|
+
geometry = projection.get_main_geometry_value(instance)
|
|
162
155
|
if geometry is None:
|
|
163
156
|
return b"null"
|
|
164
157
|
|
|
165
|
-
|
|
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)
|
|
166
161
|
return geometry.json.encode()
|
|
167
162
|
|
|
168
163
|
def get_header(self) -> dict:
|
|
@@ -171,11 +166,12 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
171
166
|
The format is based on the WFS 3.0 DRAFT. The count fields are moved
|
|
172
167
|
to the footer allowing them to be calculated without performing queries.
|
|
173
168
|
"""
|
|
169
|
+
output_crs = self.collection.results[0].projection.output_crs
|
|
174
170
|
return {
|
|
175
171
|
"type": "FeatureCollection",
|
|
176
172
|
"timeStamp": self._format_geojson_value(self.collection.timestamp),
|
|
177
173
|
# "numberReturned": is written at the end for better query performance.
|
|
178
|
-
"crs": {"type": "name", "properties": {"name": str(
|
|
174
|
+
"crs": {"type": "name", "properties": {"name": str(output_crs)}},
|
|
179
175
|
}
|
|
180
176
|
|
|
181
177
|
def get_footer(self) -> dict:
|
|
@@ -218,40 +214,36 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
218
214
|
return links
|
|
219
215
|
|
|
220
216
|
def get_properties(
|
|
221
|
-
self,
|
|
222
|
-
feature_type: FeatureType,
|
|
223
|
-
xsd_type: XsdComplexType,
|
|
224
|
-
instance: models.Model,
|
|
217
|
+
self, projection: FeatureProjection, xsd_elements: list[XsdElement], instance: models.Model
|
|
225
218
|
) -> dict:
|
|
226
219
|
"""Collect the data for the 'properties' field.
|
|
227
220
|
|
|
228
221
|
This is based on the original XSD definition,
|
|
229
222
|
so the rendering is consistent with other output formats.
|
|
223
|
+
|
|
224
|
+
:param projection: Overview of all fields to render.
|
|
225
|
+
:param xsd_elements: The XSD elements to render.
|
|
226
|
+
:param instance: The object instance.
|
|
230
227
|
"""
|
|
231
228
|
props = {}
|
|
232
|
-
for xsd_element in
|
|
233
|
-
if not xsd_element.is_geometry:
|
|
229
|
+
for xsd_element in xsd_elements:
|
|
230
|
+
if not xsd_element.type.is_geometry:
|
|
234
231
|
value = xsd_element.get_value(instance)
|
|
235
232
|
if xsd_element.type.is_complex_type:
|
|
236
233
|
# Nested object data
|
|
237
234
|
if value is None:
|
|
238
235
|
props[xsd_element.name] = None
|
|
239
236
|
else:
|
|
240
|
-
|
|
237
|
+
sub_elements = projection.xsd_child_nodes[xsd_element]
|
|
241
238
|
if xsd_element.is_many:
|
|
242
|
-
# If the retrieved QuerySet was not filtered yet, do so now.
|
|
243
|
-
# This can't be done in get_value() because the FeatureType
|
|
244
|
-
# is not known there.
|
|
245
|
-
value = feature_type.filter_related_queryset(value)
|
|
246
|
-
|
|
247
239
|
# "..._to_many relation; reverse FK, M2M or array field.
|
|
248
240
|
props[xsd_element.name] = [
|
|
249
|
-
self.get_properties(
|
|
241
|
+
self.get_properties(projection, sub_elements, item)
|
|
250
242
|
for item in value
|
|
251
243
|
]
|
|
252
244
|
else:
|
|
253
245
|
props[xsd_element.name] = self.get_properties(
|
|
254
|
-
|
|
246
|
+
projection, sub_elements, value
|
|
255
247
|
)
|
|
256
248
|
else:
|
|
257
249
|
# Scalar value, or list (for ArrayField).
|
|
@@ -263,33 +255,32 @@ class GeoJsonRenderer(OutputRenderer):
|
|
|
263
255
|
class DBGeoJsonRenderer(GeoJsonRenderer):
|
|
264
256
|
"""GeoJSON renderer that relays the geometry rendering to the database.
|
|
265
257
|
|
|
266
|
-
This is even more efficient
|
|
258
|
+
This is even more efficient than calling the C-API for each feature.
|
|
267
259
|
"""
|
|
268
260
|
|
|
269
|
-
|
|
270
|
-
def decorate_queryset(self, feature_type: FeatureType, queryset, output_crs, **params):
|
|
261
|
+
def decorate_queryset(self, projection: FeatureProjection, queryset):
|
|
271
262
|
"""Update the queryset to let the database render the GML output.
|
|
272
|
-
This is far more efficient
|
|
263
|
+
This is far more efficient than GeoDjango's logic, which performs a
|
|
273
264
|
C-API call for every single coordinate of a geometry.
|
|
274
265
|
"""
|
|
275
|
-
queryset = super().decorate_queryset(
|
|
266
|
+
queryset = super().decorate_queryset(projection, queryset)
|
|
276
267
|
# If desired, the entire FeatureCollection could be rendered
|
|
277
268
|
# in PostgreSQL as well: https://postgis.net/docs/ST_AsGeoJSON.html
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
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(
|
|
281
272
|
_as_db_geojson=AsGeoJSON(
|
|
282
|
-
get_db_geometry_target(
|
|
273
|
+
get_db_geometry_target(main_geo_element, projection.output_crs),
|
|
283
274
|
precision=conf.GISSERVER_DB_PRECISION,
|
|
284
275
|
)
|
|
285
276
|
)
|
|
286
277
|
|
|
287
278
|
return queryset
|
|
288
279
|
|
|
289
|
-
def render_geometry(self,
|
|
280
|
+
def render_geometry(self, projection: FeatureProjection, instance: models.Model) -> bytes:
|
|
290
281
|
"""Generate the proper GeoJSON notation for a geometry"""
|
|
291
282
|
# Database server rendering
|
|
292
|
-
if
|
|
283
|
+
if projection.main_geometry_element is None:
|
|
293
284
|
return b"null"
|
|
294
285
|
|
|
295
286
|
geojson = instance._as_db_geojson
|