QGIS Server 3.4: Server cache manager and WMTS service
Par René-Luc D'Hont le vendredi 26 octobre 2018, 14:30 - Système d'Information Géographique (SIG) - Lien permanent
Funded by Ifremer
QGIS Server 3.4 supports WMTS 1.0.0 OGC standard.
In QGIS project properties, the user can defined:
- which part of the project (all the project, layer groups or layers) has to be published through WMTS standard
- the min scale of the tiles
QGIS Server reused the CRS defined in the WMS capabilities configuration for WMTS.
To manage the tile cache, a QGIS Server plugin with a QgsServerCacheFilter class has to be installed and loaded by QGIS Server.
By default, QGIS Server only caches WMS GetCapabilities document in memory. With WMTS implementation, it is necessary to have a way to cache tiles. But the cache manager has not been developed to only cache tiles.
The cache manager plugin, can be used to cache:
- WMS, WFS, WCS, WMTS GetCapabilities documents
- WFS DescribeFeatureType documents
- WCS DescribeCoverage documents
- WMTS GetTile images
- WMS GetLegendGraphic images
Here is a python cache manager class, which do not verifying if the QGIS project has changed:
class PyServerCache(QgsServerCacheFilter): """ Used to have restriction access """ # Be able to deactivate the access control to have a reference point _active = False def __init__(self, server_iface): super(QgsServerCacheFilter, self).__init__(server_iface) self._cache_dir = os.path.join(tempfile.gettempdir(), "qgs_server_cache") if not os.path.exists(self._cache_dir): os.mkdir(self._cache_dir) self._tile_cache_dir = os.path.join(self._cache_dir, 'tiles') if not os.path.exists(self._tile_cache_dir): os.mkdir(self._tile_cache_dir) def getCachedDocument(self, project, request, key): m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) if not os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")): return QByteArray() doc = QDomDocument(m.hexdigest() + ".xml") with open(os.path.join(self._cache_dir, m.hexdigest() + ".xml"), "r") as f: statusOK, errorStr, errorLine, errorColumn = doc.setContent(f.read(), True) if not statusOK: print("Could not read or find the contents document. Error at line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr)) return QByteArray() return doc.toByteArray() def setCachedDocument(self, doc, project, request, key): if not doc: print("Could not cache None document") return False m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) with open(os.path.join(self._cache_dir, m.hexdigest() + ".xml"), "w") as f: f.write(doc.toString()) return os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) def deleteCachedDocument(self, project, request, key): m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) if os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")): os.remove(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) return not os.path.exists(os.path.join(self._cache_dir, m.hexdigest() + ".xml")) def deleteCachedDocuments(self, project): filelist = [f for f in os.listdir(self._cache_dir) if f.endswith(".xml")] for f in filelist: os.remove(os.path.join(self._cache_dir, f)) filelist = [f for f in os.listdir(self._cache_dir) if f.endswith(".xml")] return len(filelist) == 0 def getCachedImage(self, project, request, key): m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) if not os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")): return QByteArray() img = QImage(m.hexdigest() + ".png") with open(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png"), "rb") as f: statusOK = img.loadFromData(f.read()) if not statusOK: print("Could not read or find the contents document. Error at line %d, column %d:\n%s" % (errorLine, errorColumn, errorStr)) return QByteArray() ba = QByteArray() buff = QBuffer(ba) buff.open(QIODevice.WriteOnly) img.save(buff, 'PNG') return ba def setCachedImage(self, img, project, request, key): m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) with open(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png"), "wb") as f: f.write(img) return os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) def deleteCachedImage(self, project, request, key): m = hashlib.md5() paramMap = request.parameters() urlParam = "&".join(["%s=%s" % (k, paramMap[k]) for k in paramMap.keys()]) m.update(urlParam.encode('utf8')) if os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")): os.remove(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) return not os.path.exists(os.path.join(self._tile_cache_dir, m.hexdigest() + ".png")) def deleteCachedImages(self, project): filelist = [f for f in os.listdir(self._tile_cache_dir) if f.endswith(".png")] for f in filelist: os.remove(os.path.join(self._tile_cache_dir, f)) filelist = [f for f in os.listdir(self._tile_cache_dir) if f.endswith(".png")] return len(filelist) == 0
And the way to add it to the QGIS Server cache manager.
servercache = PyServerCache(server_iface) server_iface.registerServerCache(servercache, 100)