openlayers-3qgistilestache

How to cache QGIS Server WMS?


It seems like raster tiles have started to go out of fashion, but still, I need a solution to do it somehow for my QGIS Server's WMS.

Up to this point I have tried TileCache, but I couldn't make it to work in OL3, and it also seems to be a little "oldish".

So what would be my best bid, if later I would like to use the cached layer in my OL3 application? TileStache, Mapproxy, MapCache?

I have my QGIS Server running under CentOS 7.


Solution

  • QGIS Server works well with MapProxy. With QGIS Server+MapProxy you will get the best of the QGIS styling plus the speed of a tile cache.

    MapProxy is written in Python and you probably already have Python installed on the server. You can (and you should) run MapProxy in a virtual environment. The MapProxy instructions are quite clear and it is really a question of minutes to have it up and running, fetching data from QGIS Server.

    1. It is much lighter than GeoWebCache
    2. It caches and serves tiles (just use tiled: true in your WMS request)
    3. It works pretty well with OpenLayers. As soon as you install it, you will get a demo page, with OpenLayers examples.
    4. You can call GetFeatureInfo requests against the cached source
    5. You can call GetLegendGraphic requests against the cached source
    6. It can handle custom defined grids (as long as you use the same in OpenLayers)
    7. You can ask for more than one tile in parallel and take advantage of QGIS Server parallel render support (if enable).
    8. Since QGIS Server can store projects on Postgis, you can easily update the project without any uploads. MapProxy will use the updated styles from QGIS Server.

    Example

    There are very nice small examples in the MapProxy documentation.

    This one is one of the most complicated examples, because it uses a custom grid and a CRS other than EPSG:3857. If you use the usual GLOBAL_MERCATOR grid, it is much simpler (on the MapProxy side and on the OpenLayers side).

    This is a small example of a mapproxy.yaml configuration file, with a custom grid. The source is QGIS Server. I've added a GetFeatureInfo request on mouse click to show how these requests can be forwarded to QGIS Server. I've also added the layer's legend (using service=WMS&REQUEST=GetLegendGraphic&VERSION=1.3.0).

    layers:
      - name: caop
        title: CAOP by QGIS Server
        sources: [caop_cache_continente]
    caches:
      caop_cache_continente:
        meta_size: [4, 4]
        meta_buffer: 20
        # 20+4x256+20
        # width=1064&height=1064
        use_direct_from_level: 14
        concurrent_tile_creators: 2
        link_single_color_images: true
        grids: [continente]
        sources: [continente_wms]
    sources:
      continente_wms:
        type: wms
        wms_opts:
          featureinfo: true
          legendgraphic: true
        req:
          url: http://continente.qgis.demo/cgi-bin/qgis_mapserv.fcgi
          layers: freguesia
          transparent: true
    grids:
      continente:
        srs: 'EPSG:3763'
        bbox_srs: 'EPSG:3763'
        bbox: [-127104, -301712, 173088, 278544]
        origin: nw
        res: [ 1172.625, 586.3125, 293.15625, 146.578125, 73.2890625, 36.64453125, 18.322265625, 9.1611328125, 4.58056640625, 2.290283203125, 1.1451416015625, 0.57257080078125, 0.286285400390625, 0.1431427001953125, 0.07157135009765625 ]
    

    The following OpenLayers file is able to take fetch the tiles from MapProxy.

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
      <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css"
        type="text/css">
      <style>
        .map {
          height: 600px;
          width: 100%;
        }
      </style>
      <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
      <script src="resources/js/proj4js/proj4.js"></script>
      <title>OpenLayers example using QGIS Server and MapProxy</title>
    </head>
    
    <body>
      <div id="map" class="map"></div>
      <p><image src="http://mapproxy.qgis.demo/mapproxy/service?service=WMS&REQUEST=GetLegendGraphic&VERSION=1.3.0&style=default&FORMAT=image/png&LAYER=caop&transparent=true"></image></p>
      <div id="nodelist"><em>Click on the map to get feature info</em></div>
      <script>
        proj4.defs("EPSG:3763", "+proj=tmerc +lat_0=39.66825833333333 +lon_0=-8.133108333333334 +k=1 +x_0=0 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs");
        ol.proj.proj4.register(proj4);
        var projection = new ol.proj.Projection({
          code: 'EPSG:3763',
          extent: [-127104, -301712, 173088, 278544]
        });
    
        var projectionExtent = projection.getExtent();
    
        var size = ol.extent.getWidth(projectionExtent) / 256;
        var newresolutions = new Array(15);
        var newmatrixIds = new Array(15);
        for (var z = 0; z < 15; ++z) {
          newresolutions[z] = size / Math.pow(2, z);
          newmatrixIds[z] = z;
        }
    
        var tileGrid = new ol.tilegrid.WMTS({
          origin: ol.extent.getTopLeft(projectionExtent), // [ 270000, 3650000 ]
          resolutions: newresolutions,
          matrixIds: newmatrixIds,
          tileSize: [256, 256]
        });
    
        var caop = new ol.layer.Tile({
          source: new ol.source.TileWMS({
            url: 'http://mapproxy.qgis.demo/mapproxy/service?',
            params: { layers: 'caop', tiled: true, srs: "EPSG:3763" },
            format: 'image/png',
            projection: projection,
            tileGrid: tileGrid
          })
        });
    
        var map = new ol.Map({
          layers: [caop],
          target: 'map',
          view: new ol.View({
            projection: projection,
            center: [0, 0],
            zoom: 1
          })
        });
    
        map.on('singleclick', function (evt) {
          document.getElementById('nodelist').innerHTML = "Loading... please wait...";
          var view = map.getView();
          var viewResolution = view.getResolution();
          var url = caop.getSource().getGetFeatureInfoUrl(
            evt.coordinate, viewResolution, view.getProjection(),
            { 'INFO_FORMAT': 'text/html', 'FEATURE_COUNT': 50 });
          if (url) {
            document.getElementById('nodelist').innerHTML = '<iframe seamless src="' + url + '" style="width:100%"></iframe>';
          }
        });
      </script>
    </body>
    
    </html>