返回

OpenLayers升级致自定义瓦片缓存失效解决方案

vue.js

OpenLayers 版本升级导致自定义瓦片加载缓存失效

OpenLayers 从 8.2.0 升级到 10.2.1 可能会导致使用自定义 tileLoadFunction 并包含缓存机制的代码出现故障。特别是在预加载矢量瓦片时,新版本中会出现瓦片闪烁问题,这意味着缓存机制未能正常工作。

现象及问题分析

在旧版本中,通过扩展 VectorTileSource 类,自定义 tileLoadFunction 可以有效管理矢量瓦片的加载和缓存。通过检查缓存可以快速获取已经加载过的瓦片数据,同时在加载一个瓦片时预加载该瓦片在不同时间戳下的数据避免后续的闪烁现象。升级到 10.2.1 版本后,这种预加载机制失效,导致首次加载新瓦片时会发生闪烁。

这种问题很可能与 OpenLayers 在两个版本间的内部机制变化有关,尤其是瓦片加载和状态管理方面的更新。10.2.1 版本的 OpenLayers 对 VectorTile 对象的类型定义进行调整后。旧的代码直接更新类型,并未真正适配底层行为的改变。尽管类型错误得以修复,但预加载的功能仍然无法正常使用。

解决方案

下面提供几种解决方案,并介绍其对应的操作步骤和原理,最终的解决方案应该以兼容新版本、稳定为准则。

解决方案一:适配新版 Tile 类型及状态管理

针对新版 OpenLayers,需重新审视 tileLoadFunctionWithCache 方法。保证其中对 tile 对象的状态设置以及 setLoader 函数的回调与新版本的API 兼容。

  1. 理解新的 VectorTile 类型 :阅读 OpenLayers 10.2.1 的文档,了解 VectorTile 类型相较于 8.2.0 的变化。注意任何关于状态、事件以及生命周期的修改。
  2. 调整 setLoader 函数 :修改 setLoader 的回调函数,保证与新版本的 VectorTile 对象的交互符合规范。需要密切注意tile 状态转换和回调调用时机。
  3. 修改预加载逻辑 : 需要注意是否在新版VectorTileSource中,调用了非预期的内部方法导致。查看并更改逻辑以防止其被更改和不使用,或者在新版本中将预加载功能替换成更稳定的API

代码示例:

export class VectorTileSourceWithCache extends VectorTileSource {
    // ... 其他代码 ...

    private tileLoadFunctionWithCache(tile: any, url: string): void {
        const vectorTileStore = useVectorTileStore();
        //需要根据新的API更改tile.setState,避免旧的状态码可能已经弃用
        tile.setState(TileState.LOADING); // 使用 OpenLayers 提供的 TileState 枚举
        tile.key = url;

        tile.setLoader(async (extent: Extent, resolution: number, projection: ProjectionLike) => {
            if (!vectorTileStore.isTileInProductBounds(url)) {
                this.handleEmptyTile(tile);
            } else if (vectorTileStore.isTileLoaded(url)) {
                const data = vectorTileStore.getDataByUrl(url);
                if (data) {
                    this.setTileFeatures(tile, data, extent, projection);
                } else {
                    this.handleEmptyTile(tile);
                }
            } else {
                // 记录并防止重复请求
                console.log("PBF not found, requesting...");
                try {
                    const response = await axios.get(url, { responseType: "arraybuffer" });
                    if (response.status !== 200) {
                        this.handleEmptyTile(tile);
                    } else {
                        const data = response.data;
                        this.setTileFeatures(tile, data, extent, projection);
                        vectorTileStore.addFeatures(url, data, tile.getFormat() as MVT, extent, projection);
                    }
                } catch (error) {
                    console.error("Error loading tile:", error);
                    tile.setState(TileState.ERROR); // 标记瓦片为错误状态
                }
            }
            await this.preloadAllInstances(url, tile, extent, projection);
        });
    }
    private handleEmptyTile(tile: any) {
      tile.setState(TileState.EMPTY); // 设置瓦片状态为EMPTY
      tile.setFeatures([] as Array<Feature<Geometry>>);
  }

  private setTileFeatures(tile: any, data: ArrayBuffer, extent: Extent, projection: ProjectionLike) {
      const format = tile.getFormat() as MVT;
      const features = format.readFeatures(data, {
          extent,
          featureProjection: projection
      });
      tile.setFeatures(features as Array<Feature<Geometry>>);
      tile.setState(TileState.LOADED); // 标记瓦片为已加载
  }
  private async preloadAllInstances (baseUrl: string, tile: any, extent: Extent, projection: ProjectionLike): Promise<void> {
      const vectorTileStore = useVectorTileStore();
      // ... 预加载逻辑,可能需要针对新版本进行调整,例如检查瓦片状态是否需要更新
      //  判断新版的VectorTile是否存在getURLs函数,有则尝试
      const existGetUrl = "getURLs" in tile;
      const newTileUrls: string[] = [];
      if(existGetUrl) {
         const newTile = tile.getURLs();
         for (const url of newTileUrls) {
            urls.push(url);
        }
       }
      vectorTileStore.addUlrsToPreloadQueue(newTileUrls, tile, extent, projection);

  }

    // ... 其他代码 ...
}

注意,此代码为示例参考。要对新版的VectorTileSource中的所有函数,逐一查看是否进行了大的API更改。如调用了私有方法或者不稳定方法则需要小心。必要时考虑引入新的状态来避免反复刷新.

解决方案二:调整 preloadAllInstances 触发时机

preloadAllInstances 预加载触发条件改变也可能出现错误。将预加载逻辑移动到合适的位置,避免预加载无效或者造成负面影响。

  1. 检查 tileLoadFunctionWithCache 中的异步流程 :确认在 tile 状态正确更新后才调用 preloadAllInstances
  2. 利用事件监听 : 不要在 tileLoadFunctionWithCache 里面做太多的事情,而是注册 tile 的 featuresloadendfeaturesloaderror,改为使用事件监听的方式触发 preloadAllInstances
    代码示例:
export class VectorTileSourceWithCache extends VectorTileSource {
    // ... 其他代码 ...

    private tileLoadFunctionWithCache(tile: any, url: string): void {
        // ... 瓦片加载的主要逻辑 ...

        tile.on('featuresloadend', async (event) => {
           const eventTile = event.target as any; //event.tile已被弃用
           if(eventTile.getState()=== TileState.LOADED) {
            // 在确认瓦片主数据加载完成的情况下预加载数据
               const urls=eventTile.getURLs() as Array<string>;
               if(urls && urls.length) {
                  await this.preloadAllInstances(urls[0], tile, eventTile.getExtent(), eventTile.getProjection());
                 }

             }
         });
       tile.on('featuresloaderror', (event) => {
          // 需要的话处理瓦片错误加载事件,避免重复加载死循环。可以考虑清理预加载队列相关url等
      })
    }

    // ... 其他代码 ...
}

利用featuresloadend,并适当更改原有的preloadAllInstances传入参数,在新版中可以尝试获得稳定的预加载数据结果。

解决方案三:使用官方推荐的缓存策略

可以避免自行维护一套缓存机制,转而利用 OpenLayers 内置的缓存策略,利用框架层面的支持。可以避免出现自己实现的机制,无法适配新版 API 的情况。

  1. 移除自定义缓存逻辑 :不再依赖自定义的 vectorTileStore 进行缓存。
  2. 配置 VectorTileSourcecacheSize 属性 :增大 cacheSize 以容纳更多瓦片。这个属性可以保证一定的内存缓存效果。

代码示例:

const vectorTileSource = new VectorTileSourceWithCache({
  // ... 其他配置 ...
  cacheSize: 2048, // 根据需要设置,例如:1024 或更高,默认为512。需要根据实际场景的硬件条件进行测试。
});

使用 OpenLayers 官方的策略,可能会和最初的需求存在出入,并且不能完全消除闪烁。但可以保证升级OpenLayers版本时,仍然正常运作。

结语

遇到版本升级导致的问题,排查和解决都需要耐心。遵循由简入繁的原则可以加快问题解决,尝试官方策略可以避开新版 API 不稳定的坑。根据实际需求灵活变通。