Skip to content

Commit

Permalink
Merge pull request #9 from pblottiere/raster
Browse files Browse the repository at this point in the history
Add raster layer support
  • Loading branch information
pblottiere authored Apr 9, 2024
2 parents 7638839 + a4167b0 commit 5e13bba
Show file tree
Hide file tree
Showing 19 changed files with 239 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ tests/data/data.gpkg-wal
examples
docs/build
sandbox/projects
*.aux.xml
23 changes: 23 additions & 0 deletions docs/src/DEVELOPERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Developers

Documentation:

```` console
$ mdbook build docs
````

Unit tests:

```` console
$ cd qsa-api
$ pytest -sv test/api.py
````

Integration tests:

```` console
$ cd sandbox
$ docker-compose up -d
$ cd ../qsa-api
$ QSA_GEOTIFF="/landsat_4326.tif" QSA_GPKG="/data.gpkg" QSA_HOST=127.0.01 QSA_PORT=5000 pytest -sv tests/api.py
````
5 changes: 2 additions & 3 deletions docs/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ Components:

Features:
* Create and manage QGIS projects stored on filesystem
* Create and update vector layers : symbology, theme, ...
* Create and update layers : symbology, theme, ...
* Inspect online QGIS Server instances
* Cache management with MapProxy
* Optional cache management with MapProxy

![QSA](images/qsa_archi.png)

Roadmap:
* Add more documentation
* Add raster layer support
* Add PostgreSQL support to store QGIS projects
* Publish `qsa-cli` on PyPI
* Publish a `qsa-api` Docker image on DockerHub
Expand Down
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
- [Manage projects](sandbox/projects.md)
- [Manage layers](sandbox/layers.md)
- [Manage styles](sandbox/styles.md)
- [Developers](DEVELOPERS.md)
- [Funders](FUNDERS.md)
2 changes: 1 addition & 1 deletion docs/src/qsa-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ providing a REST API for managing QGIS Server.
Features:

- Create and manage projects stored on filesystem
- Add and update vector layers based on multiple datasources (AWS S3 buckets, ...)
- Add and update layers based on multiple datasources (AWS S3 buckets, ...)
- Configure symbology and themes based on simple symbols parameters

Optional features:
Expand Down
18 changes: 9 additions & 9 deletions docs/src/qsa-api/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ Several themes can be associated with a layer like in QGIS Desktop, but only
the current one is used when the `STYLE` parameter in OGC web services is
empty.

| Method | URL | Description |
|---------|--------------------------------------------------|-------------------------------------------------------------------------------------|
| GET | `/api/projects/{project}/layers` | List layers in project |
| GET | `/api/projects/{project}/layers/{layer}` | List layer's metadata |
| GET | `/api/projects/{project}/layers/{layer}/map` | WMS `GetMap` result with default parameters |
| GET | `/api/projects/{project}/layers/{layer}/map/url` | WMS `GetMap` URL with default parameters |
| POST | `/api/projects/{project}/layers` | Add layer to project with `name`, `datasource` and `crs` |
| POST | `/api/projects/{project}/layers/{layer}/style` | Add/Update layer's style with `name` (style name) and `current` (`True` or `False`) |
| DELETE | `/api/projects/{project}/layers/{layer}` | Remove layer from project |
| Method | URL | Description |
|---------|--------------------------------------------------|------------------------------------------------------------------------------------------|
| GET | `/api/projects/{project}/layers` | List layers in project |
| GET | `/api/projects/{project}/layers/{layer}` | List layer's metadata |
| GET | `/api/projects/{project}/layers/{layer}/map` | WMS `GetMap` result with default parameters |
| GET | `/api/projects/{project}/layers/{layer}/map/url` | WMS `GetMap` URL with default parameters |
| POST | `/api/projects/{project}/layers` | Add layer to project with `type` (`vector` or `raster`), `name`, `datasource` and `crs` |
| POST | `/api/projects/{project}/layers/{layer}/style` | Add/Update layer's style with `name` (style name) and `current` (`True` or `False`) |
| DELETE | `/api/projects/{project}/layers/{layer}` | Remove layer from project |

Examples:

Expand Down
6 changes: 4 additions & 2 deletions docs/src/sandbox/layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ $ curl "http://localhost:5000/api/projects/my_project/layers" \
-d '{
"crs": 4326,
"datasource":"/data.gpkg|layername=polygons",
"name":"polygons"
"name":"polygons",
"type":"vector"
}'
true
````
Expand All @@ -28,7 +29,8 @@ $ curl "http://localhost:5000/api/projects/my_project/layers" \
-d '{
"crs": 4326,
"datasource":"/data.gpkg|layername=lines",
"name":"lines"
"name":"lines",
"type":"vector"
}'
true
````
Expand Down
1 change: 1 addition & 0 deletions qsa-api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ python = ">=3.10"
flask = "3.0.0"
click = "8.1.7"
pyyaml = "^6.0.1"
jsonschema = "^4.21.1"

[build-system]
requires = ["poetry-core"]
Expand Down
11 changes: 11 additions & 0 deletions qsa-api/qsa_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
# coding: utf8

import os
from qgis.core import QgsApplication


# avoid "Application path not initialized" message
os.environ["QT_QPA_PLATFORM"] = "offscreen"

QgsApplication.setPrefixPath('/usr', True)
qgs = QgsApplication([], False)
qgs.initQgis()
103 changes: 82 additions & 21 deletions qsa-api/qsa_api/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import shutil
import requests
from jsonschema import validate
from jsonschema.exceptions import ValidationError
from flask import send_file, Blueprint, jsonify, request

from ..wms import WMS
Expand Down Expand Up @@ -36,15 +38,24 @@ def project_info(name: str):

@projects.post("/")
def project_add():
if request.is_json:
if "name" not in request.get_json():
return {"error": "Parameter 'name' is missing"}, 415
schema = {
"type": "object",
"required": ["name", "author"],
"properties": {
"name": {"type": "string"},
"author": {"type": "string"},
},
}

if "author" not in request.get_json():
return {"error": "Parameter 'author' is missing"}, 415
if request.is_json:
data = request.get_json()
try:
validate(data, schema)
except ValidationError as e:
return {"error": e.message}, 415

name = request.get_json()["name"]
author = request.get_json()["author"]
name = data["name"]
author = data["author"]

project = QSAProject(name)
if project.exists():
Expand Down Expand Up @@ -97,11 +108,22 @@ def project_del_style(name, style):

@projects.post("/<name>/layers/<layer_name>/style")
def project_layer_update_style(name, layer_name):
schema = {
"type": "object",
"required": ["name", "current"],
"properties": {
"name": {"type": "string"},
"current": {"type": "boolean"},
},
}

project = QSAProject(name)
if project.exists():
data = request.get_json()
if "current" not in data or "name" not in data:
return {"error": "Invalid parameters"}, 415
try:
validate(data, schema)
except ValidationError as e:
return {"error": e.message}, 415

current = str_to_bool(data["current"])

Expand Down Expand Up @@ -142,17 +164,24 @@ def project_layer_map(name, layer_name):

@projects.post("/<name>/styles")
def project_add_style(name):
schema = {
"type": "object",
"required": ["name", "symbol", "symbology", "properties"],
"properties": {
"name": {"type": "string"},
"symbol": {"type": "string"},
"symbology": {"type": "string"},
"properties": {"type": "object"},
},
}

project = QSAProject(name)
if project.exists():
data = request.get_json()

if (
"name" not in data
or "symbol" not in data
or "symbology" not in data
or "properties" not in data
):
return {"error": "Invalid parameters"}, 415
try:
validate(data, schema)
except ValidationError as e:
return {"error": e.message}, 415

# legacy support
symbology = data["symbology"]
Expand Down Expand Up @@ -182,11 +211,22 @@ def project_default_styles(name):

@projects.post("/<name>/styles/default")
def project_update_default_style(name):
schema = {
"type": "object",
"required": ["geometry", "style"],
"properties": {
"geometry": {"type": "string"},
"style": {"type": "string"},
},
}

project = QSAProject(name)
if project.exists():
data = request.get_json()
if "geometry" not in data or "style" not in data:
return {"error": "Invalid parameters"}, 415
try:
validate(data, schema)
except ValidationError as e:
return {"error": e.message}, 415

project.style_update(data["geometry"], data["style"])
return jsonify(True), 201
Expand All @@ -205,11 +245,32 @@ def project_layers(name):

@projects.post("/<name>/layers")
def project_add_layer(name):
schema = {
"type": "object",
"required": ["name", "datasource", "crs", "type"],
"properties": {
"name": {"type": "string"},
"datasource": {"type": "string"},
"crs": {"type": "number"},
"type": {"type": "string"},
},
}

project = QSAProject(name)
if project.exists():
data = request.get_json()
rc = project.add_layer(data["datasource"], data["name"], data["crs"])
return jsonify(rc), 201
try:
validate(data, schema)
except ValidationError as e:
return {"error": e.message}, 415

rc, err = project.add_layer(
data["datasource"], data["type"], data["name"], data["crs"]
)
if rc:
return jsonify(rc), 201
else:
return {"error": err}, 415
else:
return {"error": "Project does not exist"}, 415

Expand Down
12 changes: 6 additions & 6 deletions qsa-api/qsa_api/api/symbology.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ def symbology_symbols_line():
@symbology.get("/vector/polygon/single_symbol/fill/properties")
def symbology_symbols_fill():
props = QgsSimpleFillSymbolLayer().properties()
props["outline_style"] = (
"solid (no, solid, dash, dot, dash dot, dash dot dot)"
)
props[
"outline_style"
] = "solid (no, solid, dash, dot, dash dot, dash dot dot)"
return jsonify(props)


@symbology.get("/vector/point/single_symbol/marker/properties")
def symbology_symbols_marker():
props = QgsSimpleMarkerSymbolLayer().properties()
props["outline_style"] = (
"solid (no, solid, dash, dot, dash dot, dash dot dot)"
)
props[
"outline_style"
] = "solid (no, solid, dash, dot, dash dot, dash dot dot)"
return jsonify(props)
2 changes: 1 addition & 1 deletion qsa-api/qsa_api/mapproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def clear_cache(self, layer_name: str) -> None:
for d in cache_dir.glob(f"**/{layer_name}_cache_*"):
shutil.rmtree(d)

def add_layer(self, name: str, bbox: list, srs: str) -> None:
def add_layer(self, name: str, bbox: list, srs: int) -> None:
if "layers" not in self.cfg:
self.cfg["layers"] = []
self.cfg["caches"] = {}
Expand Down
Loading

0 comments on commit 5e13bba

Please sign in to comment.