Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/audit-api-auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
("spp_encryption_rest_api", "well_known.py", "*"),
# FastAPI demo router (development only)
("fastapi", "demo_router.py", "*"),
# OGC OPTIONS endpoint - CORS preflight, public by design
("spp_api_v2_gis", "ogc_features.py", "options_collection_items"),
}


Expand Down
193 changes: 193 additions & 0 deletions spp_api_v2_gis/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
===============
OpenSPP GIS API
===============

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:b0c4c426ac92738100187cd709693fbf8d1a849f2a7a31b69b9874a834376f1c
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |badge2| image:: https://img.shields.io/badge/license-LGPL--3-blue.png
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OpenSPP%2FOpenSPP2-lightgray.png?logo=github
:target: https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_gis
:alt: OpenSPP/OpenSPP2

|badge1| |badge2| |badge3|

REST API for QGIS plugin integration, providing OGC API - Features
endpoints, spatial queries, and geofence management.

Key Features
------------

- **OGC API - Features**: Standards-compliant feature collections
(GovStack GIS BB)
- **GeoJSON Export**: Get pre-aggregated layer data for QGIS
- **QML Styling**: Fetch QGIS style files for consistent visualization
- **Spatial Queries**: Query registrant statistics within arbitrary
polygons using PostGIS
- **Geofence Management**: Save and manage areas of interest

Architecture
------------

Follows thin client architecture where QGIS displays data and OpenSPP
performs all computation:

- All spatial queries executed in PostGIS for performance (including
bbox via ST_Intersects)
- Pre-aggregated data returned to minimize data transfer
- Configuration-driven styling using QML templates
- JWT authentication with scope-based access control

API Endpoints
-------------

**OGC API - Features (primary interface)**

+-------------------------------------------+--------+------------------------------+
| Endpoint | Method | Description |
+===========================================+========+==============================+
| ``/gis/ogc/`` | GET | OGC API landing page |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/conformance`` | GET | OGC conformance classes |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/collections`` | GET | List feature collections |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/collections/{id}`` | GET | Collection metadata |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/collections/{id}/items`` | GET | Feature items (GeoJSON) |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/collections/{id}/items/{fid}`` | GET | Single feature |
+-------------------------------------------+--------+------------------------------+
| ``/gis/ogc/collections/{id}/qml`` | GET | QGIS style file (extension) |
+-------------------------------------------+--------+------------------------------+

**Additional endpoints**

========================== ========== =======================
Endpoint Method Description
========================== ========== =======================
``/gis/query/statistics`` POST Query stats for polygon
``/gis/geofences`` POST/GET Geofence management
``/gis/geofences/{id}`` GET/DELETE Single geofence
``/gis/export/geopackage`` GET Export for offline use
========================== ========== =======================

Scopes and Data Privacy
-----------------------

**OAuth Scopes**

+------------------+--------------+------------------------------------+
| Scope | Access | Description |
+==================+==============+====================================+
| ``gis:read`` | Read-only | View collections, layers, |
| | | statistics, export data |
+------------------+--------------+------------------------------------+
| ``gis:geofence`` | Read + Write | Create and archive geofences (also |
| | | requires ``gis:read`` for listing) |
+------------------+--------------+------------------------------------+

**What data is exposed**

**Aggregated statistics only.** No endpoint in this module returns
individual registrant records.

- **OGC collections/items**: Return GeoJSON features organized by
administrative area, with pre-computed aggregate values (counts,
percentages). Each feature represents an *area*, not a person.
- **Spatial query statistics** (``POST /gis/query/statistics``): Accepts
a GeoJSON polygon and returns configured aggregate statistics computed
by ``spp.aggregation.service``. Individual registrant IDs are computed
internally for aggregation but are **explicitly stripped** from the
response before it is sent (see ``spatial_query.py``).
- **Exports** (GeoPackage/GeoJSON): Contain the same area-level
aggregated layer data, not registrant-level records.
- **Geofences**: Store only geometry and metadata — no registrant data.

**Privacy controls**

- **K-anonymity suppression**: Statistics backed by CEL variables can
apply k-anonymity thresholds. When a cell count falls below the
configured minimum, the value is replaced with a suppression marker
and flagged as ``"suppressed": true`` in the response. This prevents
re-identification in small populations.
- **CEL variable configuration**: Administrators control which
statistics are published and their suppression thresholds via
``spp.statistic`` records.
- **Scope separation**: ``gis:read`` and ``gis:geofence`` are separate
scopes, allowing clients to be granted read-only access without write
capability.

**Design rationale**

This module follows a **thin client** architecture: QGIS (or any
OGC-compatible client) displays pre-aggregated data, while OpenSPP
retains all individual-level data server-side. This ensures that GIS API
clients — including the QGIS plugin — never need access to personally
identifiable information.

Dependencies
------------

- ``spp_api_v2`` - FastAPI infrastructure
- ``spp_gis`` - PostGIS integration
- ``spp_gis_report`` - Report configuration
- ``spp_area`` - Administrative area data

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.

**Table of contents**

.. contents::
:local:

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OpenSPP/OpenSPP2/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`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**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
-------

* OpenSPP.org

Maintainers
-----------

.. |maintainer-jeremi| image:: https://github.com/jeremi.png?size=40px
:target: https://github.com/jeremi
:alt: jeremi
.. |maintainer-gonzalesedwin1123| image:: https://github.com/gonzalesedwin1123.png?size=40px
:target: https://github.com/gonzalesedwin1123
:alt: gonzalesedwin1123
.. |maintainer-reichie020212| image:: https://github.com/reichie020212.png?size=40px
:target: https://github.com/reichie020212
:alt: reichie020212

Current maintainers:

|maintainer-jeremi| |maintainer-gonzalesedwin1123| |maintainer-reichie020212|

This module is part of the `OpenSPP/OpenSPP2 <https://github.com/OpenSPP/OpenSPP2/tree/19.0/spp_api_v2_gis>`_ project on GitHub.

You are welcome to contribute.
5 changes: 5 additions & 0 deletions spp_api_v2_gis/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import models
from . import routers
from . import schemas
from . import services
34 changes: 34 additions & 0 deletions spp_api_v2_gis/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
{
"name": "OpenSPP GIS API",
"category": "OpenSPP/Integration",
"version": "19.0.2.0.0",
"sequence": 1,
"author": "OpenSPP.org",
"website": "https://github.com/OpenSPP/OpenSPP2",
"license": "LGPL-3",
"development_status": "Alpha",
"maintainers": ["jeremi", "gonzalesedwin1123", "reichie020212"],
"depends": [
"spp_api_v2",
"spp_gis",
"spp_gis_report",
"spp_area",
"spp_hazard",
"spp_vocabulary",
"spp_statistic",
"spp_aggregation",
],
"data": [
"security/ir.model.access.csv",
],
"assets": {},
"demo": [],
"images": [],
"application": False,
"installable": True,
"auto_install": False,
"summary": """
OGC API - Features compliant GIS endpoints for QGIS and GovStack GIS BB.
""",
}
16 changes: 16 additions & 0 deletions spp_api_v2_gis/data/qml_templates/graduated_polygon.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.28.0" styleCategories="Symbology">
<renderer-v2 type="graduatedSymbol" attr="{{FIELD_NAME}}" graduatedMethod="GraduatedColor" enableorderby="0" forceraster="0">
<ranges>
{{RANGES}}
</ranges>
<symbols>
{{SYMBOLS}}
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>{{OPACITY}}</layerOpacity>
</qgis>
34 changes: 34 additions & 0 deletions spp_api_v2_gis/data/qml_templates/point_basic.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.28.0" styleCategories="Symbology">
<renderer-v2 type="singleSymbol" enableorderby="0" forceraster="0">
<symbols>
<symbol type="marker" name="0" alpha="{{OPACITY}}" clip_to_extent="1">
<layer class="SimpleMarker" locked="0" enabled="1">
<prop k="angle" v="0"/>
<prop k="color" v="{{COLOR}}"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="3"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>{{OPACITY}}</layerOpacity>
</qgis>
55 changes: 55 additions & 0 deletions spp_api_v2_gis/data/qml_templates/point_cluster.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.28.0" styleCategories="Symbology">
<renderer-v2 type="pointCluster" enableorderby="0" forceraster="0">
<renderer-v2 type="singleSymbol" enableorderby="0" forceraster="0">
<symbols>
<symbol type="marker" name="0" alpha="{{OPACITY}}" clip_to_extent="1">
<layer class="SimpleMarker" locked="0" enabled="1">
<prop k="angle" v="0"/>
<prop k="color" v="{{COLOR}}"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="name" v="circle"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_style" v="solid"/>
<prop k="outline_width" v="0.4"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="scale_method" v="diameter"/>
<prop k="size" v="4"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
</layer>
</symbol>
</symbols>
</renderer-v2>
<symbol type="marker" name="centerSymbol" alpha="1" clip_to_extent="1">
<layer class="FontMarker" locked="0" enabled="1">
<prop k="angle" v="0"/>
<prop k="chr" v="@cluster_size"/>
<prop k="color" v="255,255,255,255"/>
<prop k="font" v="Arial"/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="miter"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="size" v="3"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
</layer>
</symbol>
</renderer-v2>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>{{OPACITY}}</layerOpacity>
</qgis>
4 changes: 4 additions & 0 deletions spp_api_v2_gis/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
from . import api_client_scope
from . import fastapi_endpoint
from . import geofence
18 changes: 18 additions & 0 deletions spp_api_v2_gis/models/api_client_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Part of OpenSPP. See LICENSE file for full copyright and licensing details.
"""Extends API client scope to support GIS resources."""

from odoo import fields, models


class ApiClientScope(models.Model):
"""Extend API client scope to include GIS resources."""

_inherit = "spp.api.client.scope"

resource = fields.Selection(
selection_add=[
("gis", "GIS"),
("statistics", "Statistics"),
],
ondelete={"gis": "cascade", "statistics": "cascade"},
)
Loading
Loading