Skip to content

Commit 624e3cd

Browse files
authored
Merge pull request #71 from OpenSPP/feat/api-v2-gis
feat(spp_api_v2_gis): add OGC API Features GIS endpoints
2 parents 8be2776 + 7e101f1 commit 624e3cd

47 files changed

Lines changed: 10887 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/audit-api-auth.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@
8080
("spp_encryption_rest_api", "well_known.py", "*"),
8181
# FastAPI demo router (development only)
8282
("fastapi", "demo_router.py", "*"),
83+
# OGC OPTIONS endpoint - CORS preflight, public by design
84+
("spp_api_v2_gis", "ogc_features.py", "options_collection_items"),
8385
}
8486

8587

spp_api_v2_gis/README.rst

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
===============
2+
OpenSPP GIS API
3+
===============
4+
5+
..
6+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7+
!! This file is generated by oca-gen-addon-readme !!
8+
!! changes will be overwritten. !!
9+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
10+
!! source digest: sha256:b0c4c426ac92738100187cd709693fbf8d1a849f2a7a31b69b9874a834376f1c
11+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
12+
13+
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
14+
:target: https://odoo-community.org/page/development-status
15+
:alt: Alpha
16+
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
17+
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
18+
:alt: License: LGPL-3
19+
.. |badge3| image:: https://img.shields.io/badge/github-OpenSPP%2FOpenSPP2-lightgray.png?logo=github
20+
:target: https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_gis
21+
:alt: OpenSPP/OpenSPP2
22+
23+
|badge1| |badge2| |badge3|
24+
25+
REST API for QGIS plugin integration, providing OGC API - Features
26+
endpoints, spatial queries, and geofence management.
27+
28+
Key Features
29+
------------
30+
31+
- **OGC API - Features**: Standards-compliant feature collections
32+
(GovStack GIS BB)
33+
- **GeoJSON Export**: Get pre-aggregated layer data for QGIS
34+
- **QML Styling**: Fetch QGIS style files for consistent visualization
35+
- **Spatial Queries**: Query registrant statistics within arbitrary
36+
polygons using PostGIS
37+
- **Geofence Management**: Save and manage areas of interest
38+
39+
Architecture
40+
------------
41+
42+
Follows thin client architecture where QGIS displays data and OpenSPP
43+
performs all computation:
44+
45+
- All spatial queries executed in PostGIS for performance (including
46+
bbox via ST_Intersects)
47+
- Pre-aggregated data returned to minimize data transfer
48+
- Configuration-driven styling using QML templates
49+
- JWT authentication with scope-based access control
50+
51+
API Endpoints
52+
-------------
53+
54+
**OGC API - Features (primary interface)**
55+
56+
+-------------------------------------------+--------+------------------------------+
57+
| Endpoint | Method | Description |
58+
+===========================================+========+==============================+
59+
| ``/gis/ogc/`` | GET | OGC API landing page |
60+
+-------------------------------------------+--------+------------------------------+
61+
| ``/gis/ogc/conformance`` | GET | OGC conformance classes |
62+
+-------------------------------------------+--------+------------------------------+
63+
| ``/gis/ogc/collections`` | GET | List feature collections |
64+
+-------------------------------------------+--------+------------------------------+
65+
| ``/gis/ogc/collections/{id}`` | GET | Collection metadata |
66+
+-------------------------------------------+--------+------------------------------+
67+
| ``/gis/ogc/collections/{id}/items`` | GET | Feature items (GeoJSON) |
68+
+-------------------------------------------+--------+------------------------------+
69+
| ``/gis/ogc/collections/{id}/items/{fid}`` | GET | Single feature |
70+
+-------------------------------------------+--------+------------------------------+
71+
| ``/gis/ogc/collections/{id}/qml`` | GET | QGIS style file (extension) |
72+
+-------------------------------------------+--------+------------------------------+
73+
74+
**Additional endpoints**
75+
76+
========================== ========== =======================
77+
Endpoint Method Description
78+
========================== ========== =======================
79+
``/gis/query/statistics`` POST Query stats for polygon
80+
``/gis/geofences`` POST/GET Geofence management
81+
``/gis/geofences/{id}`` GET/DELETE Single geofence
82+
``/gis/export/geopackage`` GET Export for offline use
83+
========================== ========== =======================
84+
85+
Scopes and Data Privacy
86+
-----------------------
87+
88+
**OAuth Scopes**
89+
90+
+------------------+--------------+------------------------------------+
91+
| Scope | Access | Description |
92+
+==================+==============+====================================+
93+
| ``gis:read`` | Read-only | View collections, layers, |
94+
| | | statistics, export data |
95+
+------------------+--------------+------------------------------------+
96+
| ``gis:geofence`` | Read + Write | Create and archive geofences (also |
97+
| | | requires ``gis:read`` for listing) |
98+
+------------------+--------------+------------------------------------+
99+
100+
**What data is exposed**
101+
102+
**Aggregated statistics only.** No endpoint in this module returns
103+
individual registrant records.
104+
105+
- **OGC collections/items**: Return GeoJSON features organized by
106+
administrative area, with pre-computed aggregate values (counts,
107+
percentages). Each feature represents an *area*, not a person.
108+
- **Spatial query statistics** (``POST /gis/query/statistics``): Accepts
109+
a GeoJSON polygon and returns configured aggregate statistics computed
110+
by ``spp.aggregation.service``. Individual registrant IDs are computed
111+
internally for aggregation but are **explicitly stripped** from the
112+
response before it is sent (see ``spatial_query.py``).
113+
- **Exports** (GeoPackage/GeoJSON): Contain the same area-level
114+
aggregated layer data, not registrant-level records.
115+
- **Geofences**: Store only geometry and metadata — no registrant data.
116+
117+
**Privacy controls**
118+
119+
- **K-anonymity suppression**: Statistics backed by CEL variables can
120+
apply k-anonymity thresholds. When a cell count falls below the
121+
configured minimum, the value is replaced with a suppression marker
122+
and flagged as ``"suppressed": true`` in the response. This prevents
123+
re-identification in small populations.
124+
- **CEL variable configuration**: Administrators control which
125+
statistics are published and their suppression thresholds via
126+
``spp.statistic`` records.
127+
- **Scope separation**: ``gis:read`` and ``gis:geofence`` are separate
128+
scopes, allowing clients to be granted read-only access without write
129+
capability.
130+
131+
**Design rationale**
132+
133+
This module follows a **thin client** architecture: QGIS (or any
134+
OGC-compatible client) displays pre-aggregated data, while OpenSPP
135+
retains all individual-level data server-side. This ensures that GIS API
136+
clients — including the QGIS plugin — never need access to personally
137+
identifiable information.
138+
139+
Dependencies
140+
------------
141+
142+
- ``spp_api_v2`` - FastAPI infrastructure
143+
- ``spp_gis`` - PostGIS integration
144+
- ``spp_gis_report`` - Report configuration
145+
- ``spp_area`` - Administrative area data
146+
147+
.. IMPORTANT::
148+
This is an alpha version, the data model and design can change at any time without warning.
149+
Only for development or testing purpose, do not use in production.
150+
151+
**Table of contents**
152+
153+
.. contents::
154+
:local:
155+
156+
Bug Tracker
157+
===========
158+
159+
Bugs are tracked on `GitHub Issues <https://github.com/OpenSPP/OpenSPP2/issues>`_.
160+
In case of trouble, please check there if your issue has already been reported.
161+
If you spotted it first, help us to smash it by providing a detailed and welcomed
162+
`feedback <https://github.com/OpenSPP/OpenSPP2/issues/new?body=module:%20spp_api_v2_gis%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
163+
164+
Do not contact contributors directly about support or help with technical issues.
165+
166+
Credits
167+
=======
168+
169+
Authors
170+
-------
171+
172+
* OpenSPP.org
173+
174+
Maintainers
175+
-----------
176+
177+
.. |maintainer-jeremi| image:: https://github.com/jeremi.png?size=40px
178+
:target: https://github.com/jeremi
179+
:alt: jeremi
180+
.. |maintainer-gonzalesedwin1123| image:: https://github.com/gonzalesedwin1123.png?size=40px
181+
:target: https://github.com/gonzalesedwin1123
182+
:alt: gonzalesedwin1123
183+
.. |maintainer-reichie020212| image:: https://github.com/reichie020212.png?size=40px
184+
:target: https://github.com/reichie020212
185+
:alt: reichie020212
186+
187+
Current maintainers:
188+
189+
|maintainer-jeremi| |maintainer-gonzalesedwin1123| |maintainer-reichie020212|
190+
191+
This module is part of the `OpenSPP/OpenSPP2 <https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_gis>`_ project on GitHub.
192+
193+
You are welcome to contribute.

spp_api_v2_gis/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
from . import models
3+
from . import routers
4+
from . import schemas
5+
from . import services

spp_api_v2_gis/__manifest__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
{
3+
"name": "OpenSPP GIS API",
4+
"category": "OpenSPP/Integration",
5+
"version": "19.0.2.0.0",
6+
"sequence": 1,
7+
"author": "OpenSPP.org",
8+
"website": "https://github.com/OpenSPP/OpenSPP2",
9+
"license": "LGPL-3",
10+
"development_status": "Alpha",
11+
"maintainers": ["jeremi", "gonzalesedwin1123", "reichie020212"],
12+
"depends": [
13+
"spp_api_v2",
14+
"spp_gis",
15+
"spp_gis_report",
16+
"spp_area",
17+
"spp_hazard",
18+
"spp_vocabulary",
19+
"spp_statistic",
20+
"spp_aggregation",
21+
],
22+
"data": [
23+
"security/ir.model.access.csv",
24+
],
25+
"assets": {},
26+
"demo": [],
27+
"images": [],
28+
"application": False,
29+
"installable": True,
30+
"auto_install": False,
31+
"summary": """
32+
OGC API - Features compliant GIS endpoints for QGIS and GovStack GIS BB.
33+
""",
34+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
2+
<qgis version="3.28.0" styleCategories="Symbology">
3+
<renderer-v2 type="graduatedSymbol" attr="{{FIELD_NAME}}" graduatedMethod="GraduatedColor" enableorderby="0" forceraster="0">
4+
<ranges>
5+
{{RANGES}}
6+
</ranges>
7+
<symbols>
8+
{{SYMBOLS}}
9+
</symbols>
10+
<rotation/>
11+
<sizescale/>
12+
</renderer-v2>
13+
<blendMode>0</blendMode>
14+
<featureBlendMode>0</featureBlendMode>
15+
<layerOpacity>{{OPACITY}}</layerOpacity>
16+
</qgis>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
2+
<qgis version="3.28.0" styleCategories="Symbology">
3+
<renderer-v2 type="singleSymbol" enableorderby="0" forceraster="0">
4+
<symbols>
5+
<symbol type="marker" name="0" alpha="{{OPACITY}}" clip_to_extent="1">
6+
<layer class="SimpleMarker" locked="0" enabled="1">
7+
<prop k="angle" v="0"/>
8+
<prop k="color" v="{{COLOR}}"/>
9+
<prop k="horizontal_anchor_point" v="1"/>
10+
<prop k="joinstyle" v="bevel"/>
11+
<prop k="name" v="circle"/>
12+
<prop k="offset" v="0,0"/>
13+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
14+
<prop k="offset_unit" v="MM"/>
15+
<prop k="outline_color" v="35,35,35,255"/>
16+
<prop k="outline_style" v="solid"/>
17+
<prop k="outline_width" v="0"/>
18+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
19+
<prop k="outline_width_unit" v="MM"/>
20+
<prop k="scale_method" v="diameter"/>
21+
<prop k="size" v="3"/>
22+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
23+
<prop k="size_unit" v="MM"/>
24+
<prop k="vertical_anchor_point" v="1"/>
25+
</layer>
26+
</symbol>
27+
</symbols>
28+
<rotation/>
29+
<sizescale/>
30+
</renderer-v2>
31+
<blendMode>0</blendMode>
32+
<featureBlendMode>0</featureBlendMode>
33+
<layerOpacity>{{OPACITY}}</layerOpacity>
34+
</qgis>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
2+
<qgis version="3.28.0" styleCategories="Symbology">
3+
<renderer-v2 type="pointCluster" enableorderby="0" forceraster="0">
4+
<renderer-v2 type="singleSymbol" enableorderby="0" forceraster="0">
5+
<symbols>
6+
<symbol type="marker" name="0" alpha="{{OPACITY}}" clip_to_extent="1">
7+
<layer class="SimpleMarker" locked="0" enabled="1">
8+
<prop k="angle" v="0"/>
9+
<prop k="color" v="{{COLOR}}"/>
10+
<prop k="horizontal_anchor_point" v="1"/>
11+
<prop k="joinstyle" v="bevel"/>
12+
<prop k="name" v="circle"/>
13+
<prop k="offset" v="0,0"/>
14+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
15+
<prop k="offset_unit" v="MM"/>
16+
<prop k="outline_color" v="35,35,35,255"/>
17+
<prop k="outline_style" v="solid"/>
18+
<prop k="outline_width" v="0.4"/>
19+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
20+
<prop k="outline_width_unit" v="MM"/>
21+
<prop k="scale_method" v="diameter"/>
22+
<prop k="size" v="4"/>
23+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
24+
<prop k="size_unit" v="MM"/>
25+
<prop k="vertical_anchor_point" v="1"/>
26+
</layer>
27+
</symbol>
28+
</symbols>
29+
</renderer-v2>
30+
<symbol type="marker" name="centerSymbol" alpha="1" clip_to_extent="1">
31+
<layer class="FontMarker" locked="0" enabled="1">
32+
<prop k="angle" v="0"/>
33+
<prop k="chr" v="@cluster_size"/>
34+
<prop k="color" v="255,255,255,255"/>
35+
<prop k="font" v="Arial"/>
36+
<prop k="horizontal_anchor_point" v="1"/>
37+
<prop k="joinstyle" v="miter"/>
38+
<prop k="offset" v="0,0"/>
39+
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
40+
<prop k="offset_unit" v="MM"/>
41+
<prop k="outline_color" v="35,35,35,255"/>
42+
<prop k="outline_width" v="0"/>
43+
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
44+
<prop k="outline_width_unit" v="MM"/>
45+
<prop k="size" v="3"/>
46+
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
47+
<prop k="size_unit" v="MM"/>
48+
<prop k="vertical_anchor_point" v="1"/>
49+
</layer>
50+
</symbol>
51+
</renderer-v2>
52+
<blendMode>0</blendMode>
53+
<featureBlendMode>0</featureBlendMode>
54+
<layerOpacity>{{OPACITY}}</layerOpacity>
55+
</qgis>

spp_api_v2_gis/models/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
from . import api_client_scope
3+
from . import fastapi_endpoint
4+
from . import geofence
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
2+
"""Extends API client scope to support GIS resources."""
3+
4+
from odoo import fields, models
5+
6+
7+
class ApiClientScope(models.Model):
8+
"""Extend API client scope to include GIS resources."""
9+
10+
_inherit = "spp.api.client.scope"
11+
12+
resource = fields.Selection(
13+
selection_add=[
14+
("gis", "GIS"),
15+
("statistics", "Statistics"),
16+
],
17+
ondelete={"gis": "cascade", "statistics": "cascade"},
18+
)

0 commit comments

Comments
 (0)