2

I am trying to print the map that I create on the fly. However the image that I am getting from the map canvas sometimes looks half baked:enter image description here

In the screen shot, the top portion is the openlayers map that eventually got rendered and the bottom portion is what I captured from the canvas after postrender.

I am starting from the official Export PDF example, but I am working with a map that just got created. I keep a track of the tiles loading and the tiles loaded through the tileloadstart and tileloadend map events. I also tried incorporating the postrender event as per this answer on stackoverflow. When all the tiles have been loaded I wait for the postrender event to capture the map image from the canvas.

The problem:

  1. I think the problem has to do with the postrender event firing off two early and I don't know how much time to wait after the event before capturing the image from the canvas. I started with a delay of 100 milliseconds and then moved to 1 second and it seemed like it was working fine. But if I add more layers I had to take the delay to 3 seconds for it to start behaving correctly. So I don't know what the delay should be for maps with a lot of layers.

  2. I also get the problem that sometimes the tileloadstart and tileloadend event keeps on firing even after postrender because some tiles had yet to be loaded and even though the tile load count had equalized.

I have created a codepen to demonstrate the issue: Print map using tile loading and postrender events

var bingKey = ""; // Please enter a bing key

var exportButton = document.getElementById("prepareMap");
exportButton.addEventListener("click", function() {
  exportButton.disabled = true;
  document.body.style.cursor = "progress";
  prepareMap();
});

var map;
var mapCanvas;

var printDelayAfterLoad = 100;
var loading;
var loaded;
var allLoaded;

var prepareMap = function() {
  loading = 0;
  loaded = 0;
  allLoaded = false;

  var raster = new ol.layer.Tile({
    source: new ol.source.OSM(),
    opacity: 0.7
  });

  var bingLayer = new ol.layer.Tile({
    source: new ol.source.BingMaps({
      key: bingKey,
      imagerySet: "Aerial"
    })
  });

  var mapLayers = [bingLayer, raster];

  map = new ol.Map({
    layers: mapLayers,
    target: "map",
    controls: ol.control.defaults({
      attributionOptions: {
        collapsible: false
      }
    }),
    view: new ol.View({
      center: [0, 0],
      zoom: 2
    })
  });

  for (var i = 0; i < mapLayers.length; i++) {
    var layerSource = mapLayers[i].getSource();
    layerSource.on("tileloadstart", tileLoadStart);
    layerSource.on("tileloadend", tileLoadEnd);
    layerSource.on("tileloaderror", tileLoadEnd);
  }

  map.renderSync();
};

var tileLoadStart = function() {
  ++loading;
  if (allLoaded) {
    logExtraTilesLoadError();
  }
};

var tileLoadEnd = function() {
  ++loaded;
  if (loading === loaded) {
    allLoaded = true;
    listenToPostRender();
  }
};

var alreadyPrinted = false;
var listenToPostRender = function() {
  map.once("postrender", function() {
    if (!alreadyPrinted) {
      console.log("postrender called");
      window.setTimeout(function() {
        printMap();
      }, printDelayAfterLoad);
      alreadyPrinted = true;
    }
  });

  // listening to postcompose just to capture the mapCanvas from the event
  map.once("postcompose", function(event) {
    console.log("postcompose called");
    mapCanvas = event.context.canvas;
  });
};

var printMap = function() {
  console.log("printMap called");
  var data = mapCanvas.toDataURL("image/png");
  var mapImageElement = document.getElementById("mapImage");
  mapImageElement.setAttribute("src", data);
  cleanUp();
};

var cleanUp = function() {
  console.log("cleanUp called");
  //exportButton.disabled = false;
  document.body.style.cursor = "auto";
};

var errorMessage =
  " more tiles loaded after initially all tiles had been loaded";
var extraTileLoadCount = 0;
var logExtraTilesLoadError = function() {
  extraTileLoadCount++;
  var errorsDiv = document.getElementById("errorsDiv");
  errorsDiv.innerHTML = extraTileLoadCount + errorMessage;
  console.error("errorMessage");
};

You would need to enter a Bing Maps Key at the top of the javascript file. A Bing Map Key can be generated from: https://www.bingmapsportal.com/. With the BingMap layer the problem is reproducible more consistently. In the code pen I am also capturing the problem when tileloadstart is called after the postrender and writing an error to the top of the page. Also please note that the behavior is inconsistent and you might have to reload several times for it to reproduce.

Ashar
  • 176
  • 1
  • 7

0 Answers0