【问题标题】:Ensuring all tiles are loaded in Open Layers 3 XYZ source确保所有切片都加载到 Open Layers 3 XYZ 源中
【发布时间】:2015-10-11 03:25:32
【问题描述】:

我们有一些使用ol.source.XYZ 源的层。对于加载策略,我们使用ol.loadingstrategy.tile(new ol.tilegrid.createXYZ({}))。在进行其他操作之前,我们需要确保所有瓦片都已完全加载到地图视图中。

我们遇到了多篇关于此的文章,但尚未找到一个 100% 的解决方案来为我们提供所需的解决方案。即使不是这种情况,逻辑也会返回 true。我们尝试使用example 页面上显示的 tileloadstart、tileloadend、tileloaderror 事件,但这似乎并不总是返回预期的结果。

GIS Stack Exchange 文章 here 看起来很有希望,因为我们可以将下面列出的代码与 tileloadstart/tileloadend 事件结合使用,但是有许多函数调用仅在 ol-debug.js 中可用 而不是 ol.js 源代码。因此,下面粘贴的代码不适用于 ol.js。此代码只是引用的 GIS Stack Exchange 文章的副本。

function calculateNumberOfTiles(tileSource) {
     var tg = (tileSource.getTileGrid()) ? tileSource.getTileGrid(): ol.tilegrid.getForProjection(map.getView().getProjection()), 
            z = tg.getZForResolution(map.getView().getResolution()),
            tileRange = tg.getTileRangeForExtentAndZ(map.getView().calculateExtent(map.getSize()), z),
            xTiles = tileRange['maxX'] - tileRange['minX'] + 1,
            yTiles = tileRange['maxY'] - tileRange['minY'] + 1;
        return xTiles * yTiles;
}

我有两个问题,任何人都可以就我们可能遗漏的内容提供任何其他想法吗?谢谢你的帮助。

  1. 当函数调用挂在tilegrid 对象的原型时,为什么在 ol-debug.js 而不是 ol.js 中可用的函数调用?
  2. 任何其他建议如何告诉地图中的所有图块都已完全加载?

【问题讨论】:

  • 您能否描述一下结果与您的预期有何不同?您是否试图推断是否已加载给定 tilegrid 中的所有图块,或者是否仍在进行任何加载?另外,你用的是什么版本?
  • 尝试确定当前地图范围和缩放是否还有任何需要加载的内容。我们需要确保没有待加载的图块。基于我们在 SO question 中找到的另一个示例,我们认为我们可以在 tileloadstart 事件中保留一个计数器,并在每次触发时递增计数器,并在每次触发 tileloadend 或 tileloaderror 时递减计数器。当计数器变为 0 时假设完成。在某些情况下,我们的计数器从未达到 0,这就是它与预期不同的地方。它似乎在批量做一些事情。
  • 我们最近刚刚更新到 OL 的 v3.9,就在 3.10 可用之前
  • 所以您的问题是某些图块似乎开始加载但似乎没有完成(根据源的事件)?您是否确定了那些(未完成的)瓷砖会发生什么?他们还在加载还是完成了?
  • 有点,tileloadend 事件确实会触发,就像它表明它已完成但在地图中瓷砖还没有出现。我们的计数器降为 0,因为即使磁贴尚未完全加载,事件也已触发。最终,瓷砖出现在地图中,但 tileloadend 事件似乎触发得太早了,可能吗?

标签: openlayers-3 tiles


【解决方案1】:

加载事件

您正确地假设源上的每个tileloadstart 事件后都应跟随相应磁贴的tileloadendtileloaderror。就像链接的官方示例中一样,可以使用它来跟踪加载图块的数量。

当发出的tileloadendtileloaderror 事件的总和等于tileloadstart 事件的数量时,没有进行加载。如果不是这种情况,您应该尝试制作一个可重现的示例,因为它可能是库中的一个错误。

然而,了解这些事件的含义很重要。 tileloadend 事件并不意味着该图块在地图上可见,它意味着该图块已完成加载并可用于渲染。瓦片的实际渲染将在事件处理程序被调用后完成。因此,任何需要有关何时加载和渲染所有图块的信息(例如截屏/创建打印时)的图块加载逻辑都必须等到下一个 postrender 事件。

您提到tileloadend 与实际出现在地图上的图块之间存在 5-10 秒,这对于渲染相关的时间来说太长了(除非您执行一些非常奇怪的渲染回调)。

ol-debug.js 与 ol.js

与许多 JS 库一样,OpenLayers 代码在构建过程中进行了优化和最小化,以创建更小、更高效的构建。任何不属于 API 的类型或函数都将被缩小或删除。仅应使用 ol.js 中可用且记录在 openlayers.org 中的方法,因为任何缩小的方法都可能会更改每个构建。

ol-debug.js 是该库的非优化版本,旨在用于调试或探索。

【讨论】:

  • 感谢您提供了一些很好的反馈。我会将此标记为答案,因为它回答了我发布的问题。我会看看我是否可以组合一个可重现的 jsfiddle 或 plunk 并与您联系。谢谢。
【解决方案2】:

这是我的方法。它使用了一个未记录的 API,但它可以在非调试 openlayers 4.2.0 中运行。

//"Dirty" tiles can be in one of two states: Either they are being downloaded,
//or the map is holding off downloading their replacement, and they are "wanted."
//We can tell when the map is ready when there are no tiles in either of these
//states, and rendering is done.

var numInFlightTiles = 0;
map.getLayers().forEach(function (layer) {
    var source = layer.getSource();
    if (source instanceof ol.source.TileImage) {
        source.on('tileloadstart', function () {++numInFlightTiles})
        source.on('tileloadend', function () {--numInFlightTiles})
    }
})

map.on('postrender', function (evt) {
    if (!evt.frameState)
        return;

    var numHeldTiles = 0;
    var wanted = evt.frameState.wantedTiles;
    for (var layer in wanted)
        if (wanted.hasOwnProperty(layer))
            numHeldTiles += Object.keys(wanted[layer]).length;

    var ready = numInFlightTiles === 0 && numHeldTiles === 0;
    if (map.get('ready') !== ready)
        map.set('ready', ready);
});

map.set('ready', false);

function whenMapIsReady(callback) {
    if (map.get('ready'))
        callback();
    else
        map.once('change:ready', whenMapIsReady.bind(null, callback));
}

【讨论】:

  • 适用于 openlayers 5.2 ?
【解决方案3】:

此答案已过时,不适用于 openlayers 6 或更高版本。

如此处所述:https://github.com/openlayers/openlayers/releases/tag/v6.0.0

解决这个问题的方法是将后渲染放在这样的层上

const mapLayer = map.getLayers().getArray()[0];

mapLayer.on('postrender', (evt) => {});

这会在第一层渲染后触发后渲染

【讨论】: