Skip to content
Open
Show file tree
Hide file tree
Changes from all 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