I'm using MapServer tile mode with OpenLayers 5.3

new ol.layer.Tile({
    source: new ol.source.XYZ({
        url: 'http://localhost/cgi-bin/mapserv.exe?' +
            'map=mapfile&' +
            'mode=tile&' +
            'tilemode=gmap&' +

My feature's rendering depends on zoom level, so I also need to append '&zoom=x' to the URL at runtime.

I know that the URL can be udpated with setUrl function, but what event handler should it be called in?

I've tried putting it into layer 'precompose'/'postcompose' events but they are triggerd abount a hundred times at one zoom change which I guess will impact performance much and calling getZoom() within these events may return zoom as double value like this


whereas only int value is expected.


I presume you want the tile grid zoom, not view zoom, in which case you can replace url with a tileUrlFunction in the source:

 tileUrlFunction: function(tileCoord) {
     var x = ??? //some value depending on tileCoord[0]
     return 'http://localhost/cgi-bin/mapserv.exe?' +
            'map=mapfile&' +
            'mode=tile&' +
            'tilemode=gmap&' +
            'tile=' + tileCoord[1] + '+' + (-(tileCoord[2]+1)) + '+' + tileCoord[0] +
            '&zoom=' + x;

For any particular view zoom level (which may be fractional) tiles will be fetched for nearest appropriate integer tile grid zoom level (the one which most closely matches the view resolution). The tile grid zoom value for an integer view zoom value of viewZoom can be calculated using source.getTileGrid().getZForResolution(view.getResolutions(viewZoom)) In most cases you can use that value in a tileUrlFunction to determine what additional parameters are needed.

Another option would be to use a layer group made up of two or more layers with maxResolution and minResolution set with each having a different url appropriate to the resolution range.

Note that attempting something on the precompose/postcompose events is too late - the tiles have already been fetched.

Extra notes about changed() and refresh()

When I've used .setUrl() I've not needed .changed() or .refresh(). OpenLayers knows you have changed a property and does whatever is needed. But it won't automatically know a function is producing a different result so you need to tell it somehow. I've taken at look at the OL source to see what is happening.

.changed() does nothing except fire a changed event which the code can listen for. .refresh() clears the entire cache as well as fire a changed event. .setUrl() also updates the default tileUrlFunction to use the new url, which in turn calls a private method to "prune" the cache. Therefore I think calling source.setTileUrlFunction(source.getTileUrlFunction()); might do what you want.

ol.source.Tile.prototype.refresh = function() {

ol.source.UrlTile.prototype.setUrl = function(url) {
  var urls = this.urls = ol.TileUrlFunction.expandUrl(url);
  this.setTileUrlFunction(this.fixedTileUrlFunction ?
    this.fixedTileUrlFunction.bind(this) :
    ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid), url);

ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
  this.tileUrlFunction = tileUrlFunction;
  if (typeof opt_key !== 'undefined') {
  } else {
  • If you want a different base url at each zoom level you need a tielUrlFunction, not a url. A tileUrlFunction is called when the tiles are fetched, the tileCoord[0] parameter is the tile grid zoom level appropriate for the view zoom. – Mike Nov 14 '18 at 10:32
  • I've set up the tileUrlFunction and got the integer view zoom value with source.getTileGrid().getZForResolution(view.getResolution()) and now it works perfectly. Thank you @Mike! – yaugenka Nov 14 '18 at 11:45
  • note that there is currently an issue with tileUrlFunction github.com/openlayers/openlayers/issues/6632 which prevents already loaded tiles from being updated – yaugenka Nov 15 '18 at 7:41
  • That issue only applies to reprojection. Even without that problem if your view and source are in different projections I suggest you use a layer group and have different urls for each layer in the group because you could get more than one tile zoom level used in the display - see the "Dynamic triangulation" explanation here openlayers.org/en/latest/doc/tutorials/raster-reprojection.html – Mike Nov 15 '18 at 9:58
  • My data is in 4326 projection. In the mapfile I have it set to 4326 in the LAYER and to 3857 in the MAP objects. In OpenLayers nothing is set for the vew or the source which means it uses 3857 by default. So I guess my source and view projections are the same but still the source.changed() method won't make the loaded tiles update. Any ideas on this? – yaugenka Nov 15 '18 at 14:44

Your Answer

