Wednesday, December 30, 2015

Using GeoWebCache cached tiles as OpenLayers Tiled layer

If you have created maps using Geoserver then you may aware that those maps can be cached as image tiles using GeoWebCache. If you want to get rid of another back-end server to render and serve your map, serving it as map tiles might be a good idea. Open Layers ol.source.XYZ is the option provided for us to render a map from cached image tiles. But with GeoWebCache caching mechanism there is a little problem. XYZ source requires the tiles to be stored in a structure similar to {z}/{x}/{y}.png where z is the zoom level and x,y is the coordinate of the tile and GeoWebCache generated tiles are not in this format.  OpenLayers gives us an option to provide a url generation function as tileUrlFunction. I came up with functions below with the help of GeoWebCache.js which is the OpenLayers 2 way of doing this.

var zeroPad = function(num, len, radix)
{
    var str = num.toString(radix || 10);
    while (str.length < len)
    {
        str = "0" + str;
    }
    return str;
};

var customUrlFunction = function(bounds)
{
    var x = bounds[1];
    var y = bounds[2];
    var z = bounds[0];
    var shift = z / 2 | 0;
    var half = Math.pow(2, shift + 1);
    var digits = 1;
    if (half > 10) {
        digits = ((Math.log(half) / Math.LN10).toFixed(14) | 0) + 1;
    }
    var halfX = x / half | 0;
    var halfY = y / half | 0;
 
    var path = [
        gridsetName + "_" + zeroPad(z, 2),
        zeroPad(halfX, digits) + "_" + zeroPad(halfY, digits),
        zeroPad(x, 2 * digits) + "_" + zeroPad(y, 2 * digits) + "." + "png"
    ].join("/");
 
    var base = url.replace(/\/$/, "") + "/";
    return base + path;
};

The gridsetName is the name of the GridSet which was used to cache the tiles in GeoServer, normally this is the projection, for example, 'EPSG:4327'. The url is the local folder where the tiles are located.
By default, openlayers start calculating the tile coordinates starting from the bottom-left corner of the map. But Geoserver cached tiles are named taking top-left corner as the origin. So to make it right, when initializing the XYZ source, a tile grid must be provided with the origin set to top left corner of the map.

var tileGrid = ol.tilegrid.createXYZ({
        extent: mapExtent,
        tileSize: 256,
        origin: [mapExtent[0], mapExtent[1]]
    });

var tiled_layer = new ol.layer.Tile(
        {
           source: new ol.source.XYZ(
                    {
                        projection: projection,
                        tileGrid: tileGrid,
                        maxExtent: mapExtent,
                        tileUrlFunction: customUrlFunction
                    })
       });

Following is the complete code I used to load the tiles I have generated from GeoWebCache.

var url = file/path/to/tiles/directory/;
var gridsetName= name_of_the_Gridset;
var projection = projection_of_the_map;
var mapExtent= [];
var zeroPad = function(num, len, radix)
{
    var str = num.toString(radix || 10);
    while (str.length < len)
    {
        str = "0" + str;
    }
    return str;
};

var customUrlFunction = function(bounds)
{
    var x = bounds[1];
    var y = bounds[2];
    var z = bounds[0];
    var shift = z / 2 | 0;
    var half = Math.pow(2, shift + 1);
    var digits = 1;
    if (half > 10) {
        digits = ((Math.log(half) / Math.LN10).toFixed(14) | 0) + 1;
    }
    var halfX = x / half | 0;
    var halfY = y / half | 0;
 
    var path = [
        gridsetName + "_" + zeroPad(z, 2),
        zeroPad(halfX, digits) + "_" + zeroPad(halfY, digits),
        zeroPad(x, 2 * digits) + "_" + zeroPad(y, 2 * digits) + "." + "png"
    ].join("/");
 
    var base = url.replace(/\/$/, "") + "/";
    return base + path;
};

var tileGrid = ol.tilegrid.createXYZ({
        extent: mapExtent,
        maxZoom: 8,
        tileSize: 256,
        origin: [mapExtent[0], mapExtent[1]]
    });


var tiled_layer = new ol.layer.Tile(
        {
           source: new ol.source.XYZ(
                    {
                        projection: projection,
                        tileGrid: tileGrid,
                        maxExtent: mapExtent,
                        tileSize: 256,
                        tileUrlFunction: customUrlFunction
                    })
       });


var map = new ol.Map(
        {
            target: 'map',
            layers: [tiled_layer],
            view: new ol.View(
                {
                   center: [(mapExtent[0]+mapExtent[2])/2, (mapExtent[1]+ mapExtent[3])/2],
                   projection: projection
                })
        });