django-gisserver 1.5.0__tar.gz → 2.0__tar.gz
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 → django-gisserver-2.0}/CHANGES.md +72 -0
- {django_gisserver-1.5.0/django_gisserver.egg-info → django-gisserver-2.0}/PKG-INFO +14 -14
- {django_gisserver-1.5.0 → django-gisserver-2.0}/README.md +6 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0/django_gisserver.egg-info}/PKG-INFO +14 -14
- {django_gisserver-1.5.0 → django-gisserver-2.0}/django_gisserver.egg-info/SOURCES.txt +23 -11
- django-gisserver-2.0/gisserver/__init__.py +1 -0
- django-gisserver-2.0/gisserver/compat.py +23 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/conf.py +7 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/db.py +56 -47
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/exceptions.py +26 -2
- django-gisserver-2.0/gisserver/extensions/__init__.py +4 -0
- {django_gisserver-1.5.0/gisserver/parsers/fes20 → django-gisserver-2.0/gisserver/extensions}/functions.py +10 -4
- django-gisserver-2.0/gisserver/extensions/queries.py +261 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/features.py +220 -156
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/geometries.py +32 -37
- django-gisserver-2.0/gisserver/management/commands/loadgeojson.py +291 -0
- django-gisserver-2.0/gisserver/operations/__init__.py +0 -0
- django-gisserver-2.0/gisserver/operations/base.py +262 -0
- django-gisserver-2.0/gisserver/operations/wfs20.py +578 -0
- django-gisserver-2.0/gisserver/output/__init__.py +35 -0
- django-gisserver-2.0/gisserver/output/base.py +268 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/output/csv.py +65 -74
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/output/geojson.py +34 -35
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/output/gml32.py +254 -246
- django_gisserver-1.5.0/gisserver/output/utils.py → django-gisserver-2.0/gisserver/output/iters.py +30 -2
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/output/results.py +52 -26
- django-gisserver-2.0/gisserver/output/stored.py +143 -0
- django-gisserver-2.0/gisserver/output/utils.py +84 -0
- django-gisserver-2.0/gisserver/output/xmlschema.py +161 -0
- django-gisserver-2.0/gisserver/parsers/__init__.py +13 -0
- django-gisserver-2.0/gisserver/parsers/ast.py +320 -0
- django-gisserver-2.0/gisserver/parsers/fes20/__init__.py +28 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/parsers/fes20/expressions.py +82 -38
- django-gisserver-2.0/gisserver/parsers/fes20/filters.py +165 -0
- django-gisserver-2.0/gisserver/parsers/fes20/identifiers.py +112 -0
- django-gisserver-2.0/gisserver/parsers/fes20/lookups.py +144 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/parsers/fes20/operators.py +331 -127
- django-gisserver-2.0/gisserver/parsers/fes20/sorting.py +146 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/parsers/gml/__init__.py +12 -11
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/parsers/gml/base.py +5 -2
- django-gisserver-2.0/gisserver/parsers/gml/geometries.py +134 -0
- django-gisserver-2.0/gisserver/parsers/ows/__init__.py +25 -0
- django-gisserver-2.0/gisserver/parsers/ows/kvp.py +190 -0
- django-gisserver-2.0/gisserver/parsers/ows/requests.py +158 -0
- django-gisserver-2.0/gisserver/parsers/query.py +175 -0
- django-gisserver-2.0/gisserver/parsers/values.py +66 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/__init__.py +37 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/adhoc.py +245 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/base.py +143 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/projection.py +103 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/requests.py +482 -0
- django-gisserver-2.0/gisserver/parsers/wfs20/stored.py +192 -0
- django-gisserver-2.0/gisserver/parsers/xml.py +249 -0
- django-gisserver-2.0/gisserver/projection.py +357 -0
- django-gisserver-2.0/gisserver/static/gisserver/index.css +15 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/templates/gisserver/index.html +1 -1
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/templates/gisserver/service_description.html +2 -2
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml +9 -9
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/feature_field.html +2 -2
- django-gisserver-2.0/gisserver/templatetags/__init__.py +0 -0
- django-gisserver-2.0/gisserver/templatetags/gisserver_tags.py +30 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/types.py +322 -259
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/views.py +198 -56
- {django_gisserver-1.5.0 → django-gisserver-2.0}/pyproject.toml +4 -3
- {django_gisserver-1.5.0 → django-gisserver-2.0}/setup.py +3 -2
- django_gisserver-1.5.0/gisserver/__init__.py +0 -1
- django_gisserver-1.5.0/gisserver/operations/base.py +0 -448
- django_gisserver-1.5.0/gisserver/operations/wfs20.py +0 -492
- django_gisserver-1.5.0/gisserver/output/__init__.py +0 -74
- django_gisserver-1.5.0/gisserver/output/base.py +0 -229
- django_gisserver-1.5.0/gisserver/output/xmlschema.py +0 -122
- django_gisserver-1.5.0/gisserver/parsers/__init__.py +0 -13
- django_gisserver-1.5.0/gisserver/parsers/base.py +0 -149
- django_gisserver-1.5.0/gisserver/parsers/fes20/__init__.py +0 -42
- django_gisserver-1.5.0/gisserver/parsers/fes20/filters.py +0 -97
- django_gisserver-1.5.0/gisserver/parsers/fes20/identifiers.py +0 -94
- django_gisserver-1.5.0/gisserver/parsers/fes20/query.py +0 -285
- django_gisserver-1.5.0/gisserver/parsers/fes20/sorting.py +0 -75
- django_gisserver-1.5.0/gisserver/parsers/gml/geometries.py +0 -100
- django_gisserver-1.5.0/gisserver/parsers/tags.py +0 -102
- django_gisserver-1.5.0/gisserver/parsers/values.py +0 -40
- django_gisserver-1.5.0/gisserver/queries/__init__.py +0 -37
- django_gisserver-1.5.0/gisserver/queries/adhoc.py +0 -185
- django_gisserver-1.5.0/gisserver/queries/base.py +0 -186
- django_gisserver-1.5.0/gisserver/queries/projection.py +0 -240
- django_gisserver-1.5.0/gisserver/queries/stored.py +0 -206
- django_gisserver-1.5.0/gisserver/static/gisserver/index.css +0 -4
- django_gisserver-1.5.0/gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml +0 -20
- django_gisserver-1.5.0/gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml +0 -14
- django_gisserver-1.5.0/gisserver/templatetags/gisserver_tags.py +0 -10
- {django_gisserver-1.5.0 → django-gisserver-2.0}/LICENSE +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/MANIFEST.in +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/django_gisserver.egg-info/dependency_links.txt +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/django_gisserver.egg-info/not-zip-safe +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/django_gisserver.egg-info/requires.txt +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/django_gisserver.egg-info/top_level.txt +0 -0
- {django_gisserver-1.5.0/gisserver/operations → django-gisserver-2.0/gisserver/management}/__init__.py +0 -0
- {django_gisserver-1.5.0/gisserver/templatetags → django-gisserver-2.0/gisserver/management/commands}/__init__.py +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/gisserver/templates/gisserver/wfs/feature_type.html +0 -0
- {django_gisserver-1.5.0 → django-gisserver-2.0}/setup.cfg +0 -0
|
@@ -1,10 +1,82 @@
|
|
|
1
|
+
# 2024-04-28 (2.0)
|
|
2
|
+
|
|
3
|
+
* Added support for XML POST requests.
|
|
4
|
+
* Added support for geometry elements on child nodes.
|
|
5
|
+
* Added support for Django 5 `GeneratedField` fields as geometry field.
|
|
6
|
+
* Added `WFSView.check_permissions()` API to simplify permission checking.
|
|
7
|
+
* Added `WFSView.xml_namespace_aliases` attribute to configure custom namespace prefixes.
|
|
8
|
+
* Added example app and docker-compose setup for testing.
|
|
9
|
+
* Added `GISSERVER_EXTRA_OUTPUT_FORMATS` setting to define additional output formats.
|
|
10
|
+
* Added `GISSERVER_GET_FEATURE_OUTPUT_FORMATS` setting to override the default output formats.
|
|
11
|
+
* Added `CRS84` and `WEB_MERCATOR` constants in `gisserver.geometries`.
|
|
12
|
+
* Improved debugging by adding debug-level logging and better error messages.
|
|
13
|
+
* Improved font styling somewhat for browsable HTML pages.
|
|
14
|
+
* Fixed XML namespace support (e.g. handling `<ValueReference>ns0:tagname</ValueReference>`).
|
|
15
|
+
* Fixed bugs found by CITE compliance testing:
|
|
16
|
+
* Support `<fes:PropertyIsLike>` for array elements.
|
|
17
|
+
* Support `<fes:PropertyIsNil>` for geometry elements.
|
|
18
|
+
* Support `resulttype=hits` with `count` arguments.
|
|
19
|
+
* Fixed `ArrayField` detection when `django.contrib.postgres` is not in `INSTALLED_APPS`.
|
|
20
|
+
* Fixed `main_geometry_element` detection.
|
|
21
|
+
* Fixed swapped X/Y coordinates for systems that use a different axis-ordering (e.g. EPSG:3879 for Finland).
|
|
22
|
+
* Fixed GeoJSON CRS value to be `CRS84` (urn:ogc:def:crs:OGC::CRS84) instead of WGS84 (urn:ogc:def:crs:EPSG::4326).
|
|
23
|
+
* Fixed applying the `CRS.backend` when the client requests a custom coordinate system using `srsName`.
|
|
24
|
+
* Fixed rendering JSON exceptions during streaming errors.
|
|
25
|
+
* Fixed internal XML Schema; use proper model CamelCasing for class names (doesn't affect requests).
|
|
26
|
+
* Fixed internal XML schema; remove unneeded inheritance from `gml:AbstractFeatureType` for nested elements.
|
|
27
|
+
* Fixed CI testing.
|
|
28
|
+
* Confirmed support for Python 3.13.
|
|
29
|
+
|
|
30
|
+
This release has a lot of API changes, renamed and moved classes,
|
|
31
|
+
which was needed to implement POST support and XML namespace handling.
|
|
32
|
+
|
|
33
|
+
This won't affect most projects as they use the basic `FeatureType` functionality.
|
|
34
|
+
For implementations that have taken full advantage of our architecture,
|
|
35
|
+
the notable API changes are:
|
|
36
|
+
|
|
37
|
+
* Configuration:
|
|
38
|
+
* `WFSOperation.output_formats` still works, but `get_output_formats()` is preferred.
|
|
39
|
+
* `WFSOperation.parameters` is replaced by ``get_parameters()`` and only lists parameters that need to be mentioned in `GetCapabilities`.
|
|
40
|
+
* The `Parameter` class only exposes choices, parsing happens in `gisserver.parsers.wfs20` now.
|
|
41
|
+
* `FeatureType.xml_namespace` now defines which namespace the feature exists in (defaults to `WFSView.xml_namespace`).
|
|
42
|
+
* Overiding and extending queries:
|
|
43
|
+
* `FeatureType.get_extent()` was replaced with ``GmlBoundedByElement.get_value()``.
|
|
44
|
+
* `gisserver.operations`: renamed `WFSMethod` -> `WFSOperation`.
|
|
45
|
+
* `gisserver.extensions.functions` now tracks filter function registration.
|
|
46
|
+
* `gisserver.extensions.queries` now tracks stored query registration.
|
|
47
|
+
* The `StoredQuery` base class is now `StoredQueryImplementation` providing a `build_query()` method.
|
|
48
|
+
* The internal `CompiledQuery` moved to `gisserver.parsers.query` and receives a `feature_types` array now with a single element.
|
|
49
|
+
(This change reflects the WFS spec and allowing to potentially implement JOIN queries later).
|
|
50
|
+
* Request parsing:
|
|
51
|
+
* `WFSView.ows_request` and `request.ows_request` both provide access to the parsed WFS request.
|
|
52
|
+
* `WFSOperation.parser_class` allows to define a custom parser for a additional WFS operations.
|
|
53
|
+
* The WFS request parsing moved to `gisserver.parsers.wfs20`, which builds an Abstract Syntax Tree (AST) of the XML request.
|
|
54
|
+
* The GET request (KVP format) parsing is now a special-case of the XML-based parsing classes.
|
|
55
|
+
* Output formats:
|
|
56
|
+
* `gisserver.output` no longer exposes the auto-switching DB/non-DB rendering aliases, as `get_output_formats()` can do that easier.
|
|
57
|
+
* `OutputRenderer` only provides the basic XML aliasing, a new `CollectionOutputRenderer` base class provides the collection logic.
|
|
58
|
+
* `OutputRenderer.xml_namespaces` allows defining XML namespace aliases that construct default `xmlns` attributes.
|
|
59
|
+
* All rendering parameters (e.g. output CRS) moved to the `FeatureProjection` logic.
|
|
60
|
+
* The `decorate_queryset()` logic is no longer a classmethod.
|
|
61
|
+
* Internal XSD schema elements:
|
|
62
|
+
* XML tag parsing is reworked for simplicity and namespace handling.
|
|
63
|
+
* Namespace aliases/prefixes are removed entirely, and resolved during rendering.
|
|
64
|
+
* `FeatureType.xml_name` now returns the full XML name, not the QName.
|
|
65
|
+
* `XsdTypes` use fully qualified XML names, not the QName.
|
|
66
|
+
* `XsdElement.xml_name` now returns the full XML name, not the QName.
|
|
67
|
+
* `XsdElement.is_geometry` was unneeded, use `XsdElement.type.is_geometry` now.
|
|
68
|
+
* `XsdElement.orm_path` points to the absolute path, and `XsdElement.local_orm_path` to the relative path.
|
|
69
|
+
|
|
70
|
+
|
|
1
71
|
# 2024-11-25 (1.5.0)
|
|
2
72
|
|
|
3
73
|
* Added `PROPERTYNAME` support
|
|
4
74
|
* Added rendering of `<wfs:truncatedResponse>` when errors happen during output streaming.
|
|
75
|
+
* Fixed accessing a feature with 3 geometry fields (fixed our PostgreSQL `ST_Union` syntax).
|
|
5
76
|
* Make sure Django 4.1+ won't do double prefetches on relations that we prefetch.
|
|
6
77
|
* Hide the erroneous output formats in `GetCapabilities` that are supported for FME.
|
|
7
78
|
* Bump requirements to non-vulnerable versions (of lxml and orjson).
|
|
79
|
+
* Improved documentation.
|
|
8
80
|
* Cleaned up code, removing some internal methods.
|
|
9
81
|
* Cleaned up leftover Python 3.7 compat code.
|
|
10
82
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-gisserver
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: Django speaking WFS 2.0 (exposing GeoDjango model fields)
|
|
5
5
|
Home-page: https://github.com/amsterdam/django-gisserver
|
|
6
6
|
Author: Diederik van der Boor
|
|
7
7
|
Author-email: opensource@edoburu.nl
|
|
8
8
|
License: Mozilla Public License 2.0
|
|
9
|
-
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Environment :: Web Environment
|
|
11
12
|
Classifier: Intended Audience :: Developers
|
|
12
13
|
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -16,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
21
|
Classifier: Framework :: Django
|
|
20
22
|
Classifier: Framework :: Django :: 3.2
|
|
21
23
|
Classifier: Framework :: Django :: 4.0
|
|
@@ -27,20 +29,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
|
27
29
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
28
30
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
31
|
Requires: Django (>=3.2)
|
|
30
|
-
Requires-Python: >=3.
|
|
32
|
+
Requires-Python: >=3.9
|
|
31
33
|
Description-Content-Type: text/markdown
|
|
32
|
-
License-File: LICENSE
|
|
33
|
-
Requires-Dist: Django>=3.2
|
|
34
|
-
Requires-Dist: defusedxml>=0.6.0
|
|
35
|
-
Requires-Dist: lru_dict>=1.1.7
|
|
36
|
-
Requires-Dist: orjson>=3.9.15
|
|
37
34
|
Provides-Extra: tests
|
|
38
|
-
|
|
39
|
-
Requires-Dist: psycopg2-binary>=2.8.4; extra == "tests"
|
|
40
|
-
Requires-Dist: lxml>=4.9.1; extra == "tests"
|
|
41
|
-
Requires-Dist: pytest>=6.2.3; extra == "tests"
|
|
42
|
-
Requires-Dist: pytest-django>=4.1.0; extra == "tests"
|
|
43
|
-
Requires-Dist: pytest-cov>=2.11.1; extra == "tests"
|
|
35
|
+
License-File: LICENSE
|
|
44
36
|
|
|
45
37
|
[](https://django-gisserver.readthedocs.io/en/latest/?badge=latest)
|
|
46
38
|
[](https://github.com/Amsterdam/django-gisserver/actions/workflows/tests.yaml)
|
|
@@ -66,6 +58,12 @@ Django speaking WFS 2.0 to expose geo data.
|
|
|
66
58
|
|
|
67
59
|
For more details, see: <https://django-gisserver.readthedocs.io/>
|
|
68
60
|
|
|
61
|
+
## Test drive
|
|
62
|
+
|
|
63
|
+
* Run `docker compose up`.
|
|
64
|
+
* Open http://localhost:8000/ to see this works.
|
|
65
|
+
* Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
|
|
66
|
+
* Add more data on the map using the Django admin, username and password are both "admin".
|
|
69
67
|
|
|
70
68
|
## Quickstart
|
|
71
69
|
|
|
@@ -168,3 +166,5 @@ software developed with public money is also publicly available.
|
|
|
168
166
|
|
|
169
167
|
This package is initially developed by the City of Amsterdam, but the tools
|
|
170
168
|
and concepts created in this project can be used in any city.
|
|
169
|
+
|
|
170
|
+
|
|
@@ -22,6 +22,12 @@ Django speaking WFS 2.0 to expose geo data.
|
|
|
22
22
|
|
|
23
23
|
For more details, see: <https://django-gisserver.readthedocs.io/>
|
|
24
24
|
|
|
25
|
+
## Test drive
|
|
26
|
+
|
|
27
|
+
* Run `docker compose up`.
|
|
28
|
+
* Open http://localhost:8000/ to see this works.
|
|
29
|
+
* Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
|
|
30
|
+
* Add more data on the map using the Django admin, username and password are both "admin".
|
|
25
31
|
|
|
26
32
|
## Quickstart
|
|
27
33
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-gisserver
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: Django speaking WFS 2.0 (exposing GeoDjango model fields)
|
|
5
5
|
Home-page: https://github.com/amsterdam/django-gisserver
|
|
6
6
|
Author: Diederik van der Boor
|
|
7
7
|
Author-email: opensource@edoburu.nl
|
|
8
8
|
License: Mozilla Public License 2.0
|
|
9
|
-
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Environment :: Web Environment
|
|
11
12
|
Classifier: Intended Audience :: Developers
|
|
12
13
|
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
|
|
@@ -16,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
21
|
Classifier: Framework :: Django
|
|
20
22
|
Classifier: Framework :: Django :: 3.2
|
|
21
23
|
Classifier: Framework :: Django :: 4.0
|
|
@@ -27,20 +29,10 @@ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
|
27
29
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
28
30
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
31
|
Requires: Django (>=3.2)
|
|
30
|
-
Requires-Python: >=3.
|
|
32
|
+
Requires-Python: >=3.9
|
|
31
33
|
Description-Content-Type: text/markdown
|
|
32
|
-
License-File: LICENSE
|
|
33
|
-
Requires-Dist: Django>=3.2
|
|
34
|
-
Requires-Dist: defusedxml>=0.6.0
|
|
35
|
-
Requires-Dist: lru_dict>=1.1.7
|
|
36
|
-
Requires-Dist: orjson>=3.9.15
|
|
37
34
|
Provides-Extra: tests
|
|
38
|
-
|
|
39
|
-
Requires-Dist: psycopg2-binary>=2.8.4; extra == "tests"
|
|
40
|
-
Requires-Dist: lxml>=4.9.1; extra == "tests"
|
|
41
|
-
Requires-Dist: pytest>=6.2.3; extra == "tests"
|
|
42
|
-
Requires-Dist: pytest-django>=4.1.0; extra == "tests"
|
|
43
|
-
Requires-Dist: pytest-cov>=2.11.1; extra == "tests"
|
|
35
|
+
License-File: LICENSE
|
|
44
36
|
|
|
45
37
|
[](https://django-gisserver.readthedocs.io/en/latest/?badge=latest)
|
|
46
38
|
[](https://github.com/Amsterdam/django-gisserver/actions/workflows/tests.yaml)
|
|
@@ -66,6 +58,12 @@ Django speaking WFS 2.0 to expose geo data.
|
|
|
66
58
|
|
|
67
59
|
For more details, see: <https://django-gisserver.readthedocs.io/>
|
|
68
60
|
|
|
61
|
+
## Test drive
|
|
62
|
+
|
|
63
|
+
* Run `docker compose up`.
|
|
64
|
+
* Open http://localhost:8000/ to see this works.
|
|
65
|
+
* Connect to http://localhost:8000/wfs/ in your GIS client (e.g. QGis) and zoom to Amsterdam.
|
|
66
|
+
* Add more data on the map using the Django admin, username and password are both "admin".
|
|
69
67
|
|
|
70
68
|
## Quickstart
|
|
71
69
|
|
|
@@ -168,3 +166,5 @@ software developed with public money is also publicly available.
|
|
|
168
166
|
|
|
169
167
|
This package is initially developed by the City of Amsterdam, but the tools
|
|
170
168
|
and concepts created in this project can be used in any city.
|
|
169
|
+
|
|
170
|
+
|
|
@@ -11,13 +11,21 @@ django_gisserver.egg-info/not-zip-safe
|
|
|
11
11
|
django_gisserver.egg-info/requires.txt
|
|
12
12
|
django_gisserver.egg-info/top_level.txt
|
|
13
13
|
gisserver/__init__.py
|
|
14
|
+
gisserver/compat.py
|
|
14
15
|
gisserver/conf.py
|
|
15
16
|
gisserver/db.py
|
|
16
17
|
gisserver/exceptions.py
|
|
17
18
|
gisserver/features.py
|
|
18
19
|
gisserver/geometries.py
|
|
20
|
+
gisserver/projection.py
|
|
19
21
|
gisserver/types.py
|
|
20
22
|
gisserver/views.py
|
|
23
|
+
gisserver/extensions/__init__.py
|
|
24
|
+
gisserver/extensions/functions.py
|
|
25
|
+
gisserver/extensions/queries.py
|
|
26
|
+
gisserver/management/__init__.py
|
|
27
|
+
gisserver/management/commands/__init__.py
|
|
28
|
+
gisserver/management/commands/loadgeojson.py
|
|
21
29
|
gisserver/operations/__init__.py
|
|
22
30
|
gisserver/operations/base.py
|
|
23
31
|
gisserver/operations/wfs20.py
|
|
@@ -26,36 +34,40 @@ gisserver/output/base.py
|
|
|
26
34
|
gisserver/output/csv.py
|
|
27
35
|
gisserver/output/geojson.py
|
|
28
36
|
gisserver/output/gml32.py
|
|
37
|
+
gisserver/output/iters.py
|
|
29
38
|
gisserver/output/results.py
|
|
39
|
+
gisserver/output/stored.py
|
|
30
40
|
gisserver/output/utils.py
|
|
31
41
|
gisserver/output/xmlschema.py
|
|
32
42
|
gisserver/parsers/__init__.py
|
|
33
|
-
gisserver/parsers/
|
|
34
|
-
gisserver/parsers/
|
|
43
|
+
gisserver/parsers/ast.py
|
|
44
|
+
gisserver/parsers/query.py
|
|
35
45
|
gisserver/parsers/values.py
|
|
46
|
+
gisserver/parsers/xml.py
|
|
36
47
|
gisserver/parsers/fes20/__init__.py
|
|
37
48
|
gisserver/parsers/fes20/expressions.py
|
|
38
49
|
gisserver/parsers/fes20/filters.py
|
|
39
|
-
gisserver/parsers/fes20/functions.py
|
|
40
50
|
gisserver/parsers/fes20/identifiers.py
|
|
51
|
+
gisserver/parsers/fes20/lookups.py
|
|
41
52
|
gisserver/parsers/fes20/operators.py
|
|
42
|
-
gisserver/parsers/fes20/query.py
|
|
43
53
|
gisserver/parsers/fes20/sorting.py
|
|
44
54
|
gisserver/parsers/gml/__init__.py
|
|
45
55
|
gisserver/parsers/gml/base.py
|
|
46
56
|
gisserver/parsers/gml/geometries.py
|
|
47
|
-
gisserver/
|
|
48
|
-
gisserver/
|
|
49
|
-
gisserver/
|
|
50
|
-
gisserver/
|
|
51
|
-
gisserver/
|
|
57
|
+
gisserver/parsers/ows/__init__.py
|
|
58
|
+
gisserver/parsers/ows/kvp.py
|
|
59
|
+
gisserver/parsers/ows/requests.py
|
|
60
|
+
gisserver/parsers/wfs20/__init__.py
|
|
61
|
+
gisserver/parsers/wfs20/adhoc.py
|
|
62
|
+
gisserver/parsers/wfs20/base.py
|
|
63
|
+
gisserver/parsers/wfs20/projection.py
|
|
64
|
+
gisserver/parsers/wfs20/requests.py
|
|
65
|
+
gisserver/parsers/wfs20/stored.py
|
|
52
66
|
gisserver/static/gisserver/index.css
|
|
53
67
|
gisserver/templates/gisserver/index.html
|
|
54
68
|
gisserver/templates/gisserver/service_description.html
|
|
55
69
|
gisserver/templates/gisserver/wfs/feature_field.html
|
|
56
70
|
gisserver/templates/gisserver/wfs/feature_type.html
|
|
57
|
-
gisserver/templates/gisserver/wfs/2.0.0/describe_stored_queries.xml
|
|
58
71
|
gisserver/templates/gisserver/wfs/2.0.0/get_capabilities.xml
|
|
59
|
-
gisserver/templates/gisserver/wfs/2.0.0/list_stored_queries.xml
|
|
60
72
|
gisserver/templatetags/__init__.py
|
|
61
73
|
gisserver/templatetags/gisserver_tags.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0" # follows PEP440
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Compatability imports"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import django
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.db import models
|
|
8
|
+
|
|
9
|
+
if (
|
|
10
|
+
"django.contrib.postgres" in settings.INSTALLED_APPS
|
|
11
|
+
or "django.contrib.postgres" in sys.modules
|
|
12
|
+
):
|
|
13
|
+
from django.contrib.postgres.fields import ArrayField
|
|
14
|
+
else:
|
|
15
|
+
ArrayField = None
|
|
16
|
+
|
|
17
|
+
GeneratedField = models.GeneratedField if django.VERSION >= (5, 0) else None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = (
|
|
21
|
+
"ArrayField",
|
|
22
|
+
"GeneratedField",
|
|
23
|
+
)
|
|
@@ -28,6 +28,13 @@ GISSERVER_SUPPORTED_CRS_ONLY = getattr(settings, "GISSERVER_SUPPORTED_CRS_ONLY",
|
|
|
28
28
|
# 0 = No counting, 1 = all pages, 2 = only for the first page.
|
|
29
29
|
GISSERVER_COUNT_NUMBER_MATCHED = getattr(settings, "GISSERVER_COUNT_NUMBER_MATCHED", 1)
|
|
30
30
|
|
|
31
|
+
# -- output rendering
|
|
32
|
+
|
|
33
|
+
GISSERVER_EXTRA_OUTPUT_FORMATS = getattr(settings, "GISSERVER_EXTRA_OUTPUT_FORMATS", {})
|
|
34
|
+
GISSERVER_GET_FEATURE_OUTPUT_FORMATS = getattr(
|
|
35
|
+
settings, "GISSERVER_GET_FEATURE_OUTPUT_FORMATS", {}
|
|
36
|
+
)
|
|
37
|
+
|
|
31
38
|
# -- max page size
|
|
32
39
|
|
|
33
40
|
# Allow tuning the page size without having to override code.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from functools import lru_cache, reduce
|
|
6
7
|
|
|
7
8
|
from django.contrib.gis.db.models import functions
|
|
@@ -9,7 +10,9 @@ from django.db import connection, connections, models
|
|
|
9
10
|
|
|
10
11
|
from gisserver import conf
|
|
11
12
|
from gisserver.geometries import CRS
|
|
12
|
-
from gisserver.types import
|
|
13
|
+
from gisserver.types import GeometryXsdElement
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class AsEWKT(functions.GeoFunc):
|
|
@@ -78,63 +81,69 @@ def get_geometries_union(
|
|
|
78
81
|
return reduce(functions.Union, expressions)
|
|
79
82
|
|
|
80
83
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def build_db_annotations(
|
|
91
|
-
selects: dict[str, str | functions.Func],
|
|
92
|
-
name_template: str,
|
|
93
|
-
wrapper_func: type[functions.Func],
|
|
94
|
-
) -> dict:
|
|
95
|
-
"""Utility to build annotations for all geometry fields for an XSD type.
|
|
96
|
-
This is used by various DB-optimized rendering methods.
|
|
84
|
+
def replace_queryset_geometries(
|
|
85
|
+
queryset: models.QuerySet,
|
|
86
|
+
geo_elements: list[GeometryXsdElement],
|
|
87
|
+
output_crs: CRS,
|
|
88
|
+
wrapper_func: type[functions.GeoFunc],
|
|
89
|
+
) -> models.QuerySet:
|
|
90
|
+
"""Replace the queryset geometry retrieval with a database-rendered version.
|
|
91
|
+
|
|
92
|
+
This uses absolute paths in the queryset, but can use relative paths for related querysets.
|
|
97
93
|
"""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
defer_names = []
|
|
95
|
+
as_geo_map = {}
|
|
96
|
+
for geo_element in geo_elements:
|
|
97
|
+
if geo_element.source is not None: # excludes GmlBoundedByElement
|
|
98
|
+
defer_names.append(geo_element.local_orm_path)
|
|
99
|
+
annotation_name = _as_annotation_name(geo_element.local_orm_path, wrapper_func)
|
|
100
|
+
as_geo_map[annotation_name] = wrapper_func(
|
|
101
|
+
get_db_geometry_target(geo_element, output_crs, use_relative_path=True)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if not defer_names:
|
|
105
|
+
return queryset
|
|
106
|
+
|
|
107
|
+
logger.debug(
|
|
108
|
+
"DB rendering: QuerySet for %s replacing %r with %r",
|
|
109
|
+
queryset.model._meta.label,
|
|
110
|
+
defer_names,
|
|
111
|
+
list(as_geo_map.keys()),
|
|
112
|
+
)
|
|
113
|
+
return queryset.defer(*defer_names).annotate(**as_geo_map)
|
|
102
114
|
|
|
103
115
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
def get_db_rendered_geometry(
|
|
117
|
+
instance: models.Model, geo_element: GeometryXsdElement, replacement: type[functions.GeoFunc]
|
|
118
|
+
) -> str:
|
|
119
|
+
"""Retrieve the database-rendered geometry.
|
|
120
|
+
This includes formatted EWKT or GML output, rendered by the database.
|
|
121
|
+
"""
|
|
122
|
+
annotation_name = _as_annotation_name(geo_element.local_orm_path, replacement)
|
|
108
123
|
try:
|
|
109
|
-
return getattr(instance,
|
|
124
|
+
return getattr(instance, annotation_name)
|
|
110
125
|
except AttributeError as e:
|
|
126
|
+
prefix = _as_annotation_name("", replacement)
|
|
127
|
+
available = ", ".join(key for key in instance.__dict__ if key.startswith(prefix)) or "none"
|
|
111
128
|
raise AttributeError(
|
|
112
|
-
f" DB annotation {instance._meta.
|
|
113
|
-
f" not found (using {name_template})"
|
|
129
|
+
f" DB annotation {instance._meta.label}.{annotation_name} not found. Found {available}."
|
|
114
130
|
) from e
|
|
115
131
|
|
|
116
132
|
|
|
117
133
|
@lru_cache
|
|
118
|
-
def
|
|
134
|
+
def _as_annotation_name(name: str, func: type[functions.GeoFunc]) -> str:
|
|
119
135
|
"""Escape an XML name to be used as annotation name."""
|
|
120
|
-
return
|
|
136
|
+
return f"_{func.__name__}_{name.replace('.', '_')}"
|
|
121
137
|
|
|
122
138
|
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
) ->
|
|
126
|
-
"""
|
|
127
|
-
|
|
139
|
+
def get_db_geometry_target(
|
|
140
|
+
geo_element: GeometryXsdElement, output_crs: CRS, use_relative_path: bool = False
|
|
141
|
+
) -> str | functions.Transform:
|
|
142
|
+
"""Translate a GML geometry field into the proper expression for retrieving it from the database.
|
|
143
|
+
The path will be wrapped into a CRS Transform function if needed.
|
|
128
144
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def get_db_geometry_target(gml_element: GmlElement, output_crs: CRS) -> str | functions.Transform:
|
|
137
|
-
"""Wrap the selection of a geometry field in a CRS Transform if needed."""
|
|
138
|
-
return conditional_transform(
|
|
139
|
-
gml_element.orm_path, gml_element.source.srid, output_srid=output_crs.srid
|
|
140
|
-
)
|
|
145
|
+
orm_path = geo_element.local_orm_path if use_relative_path else geo_element.orm_path
|
|
146
|
+
if geo_element.source_srid != output_crs.srid:
|
|
147
|
+
return functions.Transform(orm_path, srid=output_crs.srid)
|
|
148
|
+
else:
|
|
149
|
+
return orm_path
|
|
@@ -8,10 +8,32 @@ https://docs.opengeospatial.org/is/09-025r2/09-025r2.html#35
|
|
|
8
8
|
https://docs.opengeospatial.org/is/09-025r2/09-025r2.html#411
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import logging
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
|
|
11
14
|
from django.conf import settings
|
|
12
15
|
from django.http import HttpResponse
|
|
13
16
|
from django.utils.html import format_html
|
|
14
17
|
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@contextmanager
|
|
22
|
+
def wrap_parser_errors(name: str, locator: str):
|
|
23
|
+
"""Convert the value into a Python format.
|
|
24
|
+
This catches any typical exceptions and transforms them into an OWSException.
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
except ExternalParsingError as e:
|
|
29
|
+
raise OperationParsingFailed(
|
|
30
|
+
f"Unable to parse {name} argument: {e}", locator=locator
|
|
31
|
+
) from None
|
|
32
|
+
except (TypeError, ValueError, NotImplementedError) as e:
|
|
33
|
+
# TypeError/ValueError are raised by most handlers for unexpected data
|
|
34
|
+
# The NotImplementedError can be raised by fes parsing.
|
|
35
|
+
raise InvalidParameterValue(f"Invalid {name} argument: {e}", locator=locator) from None
|
|
36
|
+
|
|
15
37
|
|
|
16
38
|
class ExternalValueError(ValueError):
|
|
17
39
|
"""Raise a ValueError for external input.
|
|
@@ -38,7 +60,7 @@ class OWSException(Exception):
|
|
|
38
60
|
def __init__(self, text=None, code=None, locator=None, status_code=None):
|
|
39
61
|
text = text or self.text_template.format(code=self.code, locator=locator)
|
|
40
62
|
if (code and len(text) < len(code)) or (locator and len(text) < len(locator)):
|
|
41
|
-
raise ValueError("text/locator arguments are switched")
|
|
63
|
+
raise ValueError(f"text/locator arguments are switched: {text!r}, locator={locator!r}")
|
|
42
64
|
|
|
43
65
|
super().__init__(text)
|
|
44
66
|
self.locator = locator
|
|
@@ -47,8 +69,10 @@ class OWSException(Exception):
|
|
|
47
69
|
self.status_code = status_code or self.status_code
|
|
48
70
|
|
|
49
71
|
def as_response(self):
|
|
72
|
+
logger.debug("Returning HTTP %d for %s: %s", self.status_code, self.code, self.text)
|
|
73
|
+
xml_body = self.as_xml()
|
|
50
74
|
return HttpResponse(
|
|
51
|
-
b'<?xml version="1.0" encoding="UTF-8"?>\n%b' %
|
|
75
|
+
b'<?xml version="1.0" encoding="UTF-8"?>\n%b' % xml_body.encode("utf-8"),
|
|
52
76
|
content_type="text/xml; charset=utf-8",
|
|
53
77
|
status=self.status_code,
|
|
54
78
|
reason=self.reason,
|
|
@@ -22,8 +22,14 @@ FesFunctionBody = Union[models.Func, Callable[..., models.Func]]
|
|
|
22
22
|
|
|
23
23
|
@dataclass(order=True)
|
|
24
24
|
class FesFunction:
|
|
25
|
-
"""
|
|
26
|
-
|
|
25
|
+
"""A registered database function that can be used by ``<fes:Function name="...">`.
|
|
26
|
+
|
|
27
|
+
The :class:`~gisserver.parsers.fes20.expressions.Function` class will resolve
|
|
28
|
+
these registered functions by name, and call :meth:`build_query` to include them
|
|
29
|
+
in the database query. This will actually insert a Django ORM function in the query!
|
|
30
|
+
|
|
31
|
+
This wrapper class also provides the metadata and type descriptions of the function,
|
|
32
|
+
which is exposed in the ``GetCapabilities`` call.
|
|
27
33
|
"""
|
|
28
34
|
|
|
29
35
|
#: Name of the function
|
|
@@ -50,7 +56,7 @@ class FesFunction:
|
|
|
50
56
|
|
|
51
57
|
|
|
52
58
|
class FesFunctionRegistry:
|
|
53
|
-
"""Registry of functions to be callable by
|
|
59
|
+
"""Registry of functions to be callable by ``<fes:Function>``.
|
|
54
60
|
|
|
55
61
|
The registered functions should be capable of running an SQL function.
|
|
56
62
|
"""
|
|
@@ -102,7 +108,7 @@ class FesFunctionRegistry:
|
|
|
102
108
|
return self.functions[function_name]
|
|
103
109
|
except KeyError:
|
|
104
110
|
raise InvalidParameterValue(
|
|
105
|
-
|
|
111
|
+
f"Unsupported function: {function_name}", locator="filter"
|
|
106
112
|
) from None
|
|
107
113
|
|
|
108
114
|
|