Skip to content

Commit

Permalink
Enable publication via WFS
Browse files Browse the repository at this point in the history
  • Loading branch information
JakobMiksch committed Aug 28, 2024
1 parent 0cc4b5f commit 20c1040
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/src/qsa-api/endpoints/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ empty.
| POST | `/api/projects/{project}/layers` | Add layer to project. See [Layer definition](#layer-definition) for more information. |
| 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 |
| GET | `/api/projects/{project}/layers/wfs` | List all published WFS layers |
| GET | `/api/projects/{project}/layers/{layer}/wfs` | List a published WFS layer's metadata |
| POST | `/api/projects/{project}/layers/{layer}/wfs` | Toggle WFS publication status of an existing vector layer |

#### Layer definition {#layer-definition}

Expand Down Expand Up @@ -81,6 +84,22 @@ $ curl "http://localhost/api/projects/my_project/layers" \
}'
````

#### WFS Publication

These parameters are needed to publish an existing vector layer as WFS:

- `published` : If the layer shall be published as boolean. Allowed values: `true`, `false`
- `geometry_precision` (optional) : the geometric precision as integer, default is `8`

```` shell
$ curl "http://localhost/api/projects/my_project/layers/my_layer/wfs" \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"published": true
}'
````

## Style

A QSA style may be used through the `STYLE` OGC web services parameter to
Expand Down
81 changes: 81 additions & 0 deletions qsa-api/qsa_api/api/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,88 @@ def project_info_layer(name, layer_name):
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415


@projects.get("/<name>/layers/wfs")
def project_layers_wfs(name):
"""
Get all published WFS layers
"""
log_request()
try:
psql_schema = request.args.get("schema", default="public")
project = QSAProject(name, psql_schema)
if project.exists():
return jsonify(project.layers_wfs), 201
else:
return {"error": "Project does not exist"}, 415
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415

@projects.get("/<name>/layers/<layer_name>/wfs")
def project_info_layer_wfs(name, layer_name):
"""
Get information about a single WFS layer
"""
log_request()
try:
psql_schema = request.args.get("schema", default="public")
project = QSAProject(name, psql_schema)
if project.exists():
return jsonify(project.layer_wfs(layer_name)), 201
else:
return {"error": "Project does not exist"}, 415
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415

@projects.post("/<name>/layers/<layer_name>/wfs")
def project_publish_wfs_layer(name, layer_name):
"""
Change WFS publication status of an vector layer
"""
log_request()
try:
json_schema = {
"type": "object",
"required": ["published"],
"properties": {
"published": {"type": "boolean"},
"geometry_precision": {"type": "integer"}
},
}

psql_schema = request.args.get("schema", default="public")
project = QSAProject(name, psql_schema)

if project.exists():
data = request.get_json()
try:
validate(data, json_schema)
except ValidationError as e:
return {"error": e.message}, 415

published = data["published"]
geometry_precision = 8

if "geometry_precision" in data:
geometry_precision = data["geometry_precision"]

project = QSAProject(name, psql_schema)

rc, err = project.publish_wfs_layer(layer_name, published, geometry_precision)

if err:
return {"error": err}, 415
return jsonify(rc), 201

else:
return {"error": "Project does not exist"}, 415

except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415

@projects.delete("/<name>/layers/<layer_name>")
def project_del_layer(name, layer_name):
Expand Down
90 changes: 90 additions & 0 deletions qsa-api/qsa_api/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ def layers(self) -> list:
self.debug(f"{len(layers)} layers found")
return layers

@property
def layers_wfs(self) -> list:
wfs_layers = []

p = QgsProject()
p.read(self._qgis_project_uri, Qgis.ProjectReadFlag.DontResolveLayers)

for layer_id in p.readListEntry('WFSLayers', '/')[0]:
layer_infos = p.mapLayer(layer_id)
if layer_infos:
wfs_layers.append({
"name": layer_infos.name(),
"precision": self._wfs_precision_from_layer_id(p, layer_id)
})

self.debug(f"{len(wfs_layers)} layers found")
return wfs_layers

@property
def metadata(self) -> dict:
m = {}
Expand Down Expand Up @@ -228,6 +246,24 @@ def layer(self, name: str) -> dict:

return infos
return {}

def layer_wfs(self, name: str) -> dict:
wfs_layers = self.layers_wfs
self.debug("wfs_layers " + str(wfs_layers))
wfs_layer_names = [item['name'] for item in wfs_layers]
if name in wfs_layer_names:
project = QgsProject()
project.read(self._qgis_project_uri)
layers = project.mapLayersByName(name)

if layers:
layer = layers[0]
infos = {}
infos["name"] = layer.name()
infos["precision"] = self._wfs_precision_from_layer_id(project, layer.id())
return infos

return {}

def layer_update_style(
self, layer_name: str, style_name: str, current: bool
Expand Down Expand Up @@ -478,6 +514,47 @@ def add_layer(
mp.write()

return True, ""

def publish_wfs_layer(
self,
name: str,
do_publish: bool,
geometry_precision: int
) -> (bool, str):
project = QgsProject()
project.read(self._qgis_project_uri, Qgis.ProjectReadFlag.DontResolveLayers)

# Attempt to get the layer ID
layers = project.mapLayersByName(name)
if not layers:
return False, f"Layer '{name}' does not exist"
layer_id = layers[0].id()

wfs_layers = self.layers_wfs
wfs_layer_names = [item['name'] for item in wfs_layers]
wfs_layer_ids = self._layer_ids_from_names(project, wfs_layer_names)

already_published = name in wfs_layer_names

if do_publish and already_published:
return False, f"Layer '{name}' is already published"

if not do_publish and not already_published:
return False, f"Layer '{name}' is not published"

if do_publish:
wfs_layer_ids.append(layer_id)
project.writeEntry("WFSLayersPrecision", f"/{layer_id}", geometry_precision)
else:
wfs_layer_ids.remove(layer_id)
project.removeEntry('WFSLayersPrecision', f"/{layer_id}")

project.writeEntry("WFSLayers", "/", wfs_layer_ids)

self.debug("Write QGIS project")
project.write()

return True, ""

def add_style(
self,
Expand Down Expand Up @@ -757,6 +834,19 @@ def _layer_bbox(lyr) -> list:
.split(" "),
)
)

@staticmethod
def _wfs_precision_from_layer_id(qgis_project: QgsProject, wfs_layer_id: str) -> int:
return qgis_project.readEntry('WFSLayersPrecision', '/' + wfs_layer_id)[0]

@staticmethod
def _layer_ids_from_names(qgis_project: QgsProject, layer_names: list[str]) -> list[str]:
layer_ids = []
for layer_name in layer_names:
layers = qgis_project.mapLayersByName(layer_name)
if layers:
layer_ids.append(layers[0].id())
return layer_ids

@property
def _qgis_project_uri(self) -> str:
Expand Down

0 comments on commit 20c1040

Please sign in to comment.