diff --git a/docs/src/qsa-api/endpoints/projects.md b/docs/src/qsa-api/endpoints/projects.md
index 99d5ad62..d7c81025 100644
--- a/docs/src/qsa-api/endpoints/projects.md
+++ b/docs/src/qsa-api/endpoints/projects.md
@@ -175,3 +175,28 @@ $ curl "http://localhost:5000/api/projects/my_project/styles" \
}
}'
````
+
+## Cache
+
+| Method | URL | Description |
+|---------|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
+| GET | `/api/projects/{project}/cache` | Return metadata about the cache |
+| POST | `/api/projects/{project}/cache/reset` | Clear cached data and reset cache configuration |
+
+Example:
+
+```` shell
+$ curl "http://localhost:5000/api/projects/my_project/cache"
+{
+ "valid":true,
+ "storage":"filesystem"
+}
+````
+
+
+Reset cache
+
+When a QGIS project is created manually without QSA, the cache is not
+initialized. This method allows to create the MapProxy configuration file
+accordingly.
+
diff --git a/qsa-api/qsa_api/api/projects.py b/qsa-api/qsa_api/api/projects.py
index c563d514..d0f456e2 100644
--- a/qsa-api/qsa_api/api/projects.py
+++ b/qsa-api/qsa_api/api/projects.py
@@ -436,3 +436,39 @@ def project_del_layer(name, layer_name):
except Exception as e:
logger().exception(str(e))
return {"error": "internal server error"}, 415
+
+
+@projects.get("//cache")
+def project_cache(name):
+ log_request()
+ try:
+ psql_schema = request.args.get("schema", default="public")
+ project = QSAProject(name, psql_schema)
+ if project.exists():
+ cache_infos, err = project.cache_metadata()
+ if err:
+ return {"error": err}, 415
+ return jsonify(cache_infos), 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("//cache/reset")
+def project_cache_reset(name):
+ log_request()
+ try:
+ psql_schema = request.args.get("schema", default="public")
+ project = QSAProject(name, psql_schema)
+ if project.exists():
+ rc, err = project.cache_reset()
+ 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
diff --git a/qsa-api/qsa_api/mapproxy/mapproxy.py b/qsa-api/qsa_api/mapproxy/mapproxy.py
index 927f6236..72e7533a 100644
--- a/qsa-api/qsa_api/mapproxy/mapproxy.py
+++ b/qsa-api/qsa_api/mapproxy/mapproxy.py
@@ -30,7 +30,12 @@ def write(self) -> None:
with open(self._mapproxy_project, "w") as file:
yaml.safe_dump(self.cfg, file, sort_keys=False)
- def read(self) -> bool:
+ def read(self) -> (bool, str):
+ # if a QGIS project is created manually without QSA, the MapProxy
+ # configuration file may not be created at this point.
+ if not self._mapproxy_project.exists():
+ self.create()
+
try:
with open(self._mapproxy_project, "r") as file:
self.cfg = yaml.safe_load(file)
@@ -48,6 +53,21 @@ def read(self) -> bool:
return True, ""
+ def metadata(self) -> dict:
+ md = {}
+
+ md["storage"] = ""
+ md["valid"] = False
+
+ if self._mapproxy_project.exists():
+ md["valid"] = True
+
+ md["storage"] = "filesystem"
+ if config().mapproxy_cache_s3_bucket:
+ md["storage"] = "s3"
+
+ return md
+
def clear_cache(self, layer_name: str) -> None:
if config().mapproxy_cache_s3_bucket:
bucket_name = config().mapproxy_cache_s3_bucket
diff --git a/qsa-api/qsa_api/project.py b/qsa-api/qsa_api/project.py
index 6e590a32..9179b852 100644
--- a/qsa-api/qsa_api/project.py
+++ b/qsa-api/qsa_api/project.py
@@ -129,8 +129,44 @@ def metadata(self) -> dict:
if StorageBackend.type() == StorageBackend.POSTGRESQL:
m["schema"] = self.schema
+
+ m["cache"] = "disabled"
+ if self._mapproxy_enabled:
+ m["cache"] = "mapproxy"
+
return m
+ def cache_metadata(self) -> (dict, str):
+ if self._mapproxy_enabled:
+ return QSAMapProxy(self.name).metadata(), ""
+ return {}, "Cache is disabled"
+
+ def cache_reset(self) -> (bool, str):
+ if self._mapproxy_enabled:
+ mp = QSAMapProxy(self.name)
+ rc, err = mp.read()
+ if not rc:
+ return False, err
+
+ p = QgsProject()
+ p.read(self._qgis_project_uri)
+
+ for layer in p.mapLayers().values():
+ t = layer.type()
+ bbox = QSAProject._layer_bbox(layer)
+ epsg_code = QSAProject._layer_epsg_code(layer)
+
+ mp.remove_layer(layer.name())
+ mp.add_layer(
+ layer.name(), bbox, epsg_code, t == Qgis.LayerType.Raster, None
+ )
+
+ mp.write()
+
+ return True, ""
+
+ return False, "Cache is disabled"
+
def style_default(self, geometry: str) -> bool:
con = sqlite3.connect(self.sqlite_db.as_posix())
cur = con.cursor()
@@ -245,7 +281,7 @@ def layer_update_style(
def layer_exists(self, name: str) -> bool:
return bool(self.layer(name))
- def remove_layer(self, name: str) -> None:
+ def remove_layer(self, name: str) -> bool:
# remove layer in qgis project
project = QgsProject()
project.read(self._qgis_project_uri, Qgis.ProjectReadFlag.DontResolveLayers)
@@ -260,7 +296,11 @@ def remove_layer(self, name: str) -> None:
# remove layer in mapproxy config
if self._mapproxy_enabled:
mp = QSAMapProxy(self.name)
- mp.read()
+ rc, err = mp.read()
+ if not rc:
+ self.debug(err)
+ return False
+
mp.remove_layer(name)
mp.write()
@@ -319,8 +359,13 @@ def remove(self) -> None:
for layer in self.layers:
self.remove_layer(layer)
+ # remove mapproxy config file
+ if self._mapproxy_enabled:
+ mp = QSAMapProxy(self.name)
+ mp.remove()
+
# remove qsa projects dir
- shutil.rmtree(self._qgis_project_dir)
+ shutil.rmtree(self._qgis_project_dir, ignore_errors=True)
# remove remove qgis prohect in db if necessary
if StorageBackend.type() == StorageBackend.POSTGRESQL:
@@ -412,20 +457,10 @@ def add_layer(
if self._mapproxy_enabled:
self.debug("Update MapProxy configuration file")
- bbox = list(
- map(
- float,
- lyr.extent()
- .asWktCoordinates()
- .replace(",", "")
- .split(" "),
- )
- )
-
- authid_items = lyr.crs().authid().split(":")
- if len(authid_items) < 2:
+ bbox = QSAProject._layer_bbox(lyr)
+ epsg_code = QSAProject._layer_epsg_code(lyr)
+ if epsg_code < 0:
return False, f"Invalid CRS {lyr.crs().authid()}"
- epsg_code = int(authid_items[1])
self.debug(f"EPSG code {epsg_code}")
@@ -704,6 +739,25 @@ def _layer_provider(layer_type: Qgis.LayerType, datasource: str) -> str:
provider = "gdal"
return provider
+ @staticmethod
+ def _layer_epsg_code(lyr) -> int:
+ authid_items = lyr.crs().authid().split(":")
+ if len(authid_items) < 2:
+ return -1
+ return int(authid_items[1])
+
+ @staticmethod
+ def _layer_bbox(lyr) -> list:
+ return list(
+ map(
+ float,
+ lyr.extent()
+ .asWktCoordinates()
+ .replace(",", "")
+ .split(" "),
+ )
+ )
+
@property
def _qgis_project_uri(self) -> str:
if StorageBackend.type() == StorageBackend.POSTGRESQL: