美文网首页开源
OpenLayers地图渲染机制解析

OpenLayers地图渲染机制解析

作者: jadefan | 来源:发表于2019-11-03 20:32 被阅读0次

    OpenLayers支持canvas和webgl两种方式渲染地图,默认采用的是canvas
    我们由浅入深来看ol的渲染原理
    显示地图第一步就是初始化地图,代码如下:

    const map = new Map({
      layers: [
        new TileLayer({
          source: new OSM()
        })
      ],
      target: 'map',
      view: new View({
        center: [0, 0],
        zoom: 2
      })
    });
    

    要显示地图,需传入默认图层、目标dom、默认视角。
    可以看出地图渲染流程要包含:

    1. 构建dom
    2. 解析图层参数,渲染到canvas上
    3. 调整视角

    一个基本概念:
    地图是由很多图层构成,地图渲染可以归结为图层渲染
    ol的图层类别有很多中,渲染逻辑也各不相同
    所有本文主要探讨地图组装图层以及初始dom的操作过程,图层后续分别详细探讨

    ol的Map初始化逻辑

    Map为主入口,继承自PluggableMap,主要逻辑在PluggableMap中实现

    1. 执行Map的构造函数,接收参数,判断组件和交互事件
    PluggableMap实现逻辑
    1. 执行Map的父类PluggableMap的构造函数,
    • 执行createOptionsInternal()方法,创建内部选项
    • 创建viewport并填入子容器,
    • 创建切片队列tileQueue
    • 将viewport添加到文档流中,
    • 初始化各类事件监听,包括:LayerGroup、View、Size、Target的Changed事件
    • 将内部选项赋值给Map,包括LayerGroup、View、Target,会逐一触发监听的事件
    • 1)触发LayerGroupChanged事件,从而调用了render()
    • render()在存在renderer_且定时器不存在时创建定时器执行renderFrame_()
      render() {
        if (this.renderer_ && this.animationDelayKey_ === undefined) {
          this.animationDelayKey_ = requestAnimationFrame(this.animationDelay_);
        }
      }
    
    • 但此时renderer_不存在
    • 2)触发TargetChanged事件,
    • 获取Target要素,进而获取到要素的SIZE,并创建渲染器,
    • 调用updateSize(),就触发了SizeChanged事件
    • 3)触发SizeChanged事件,调用render()
    • 此时renderer_存在且定时器还未创建,就创建了定时器,用以执行animationDelay_()
    • 采用requestAnimationFrame创建定时器,在页面可见时才会触发定时器执行
    • animationDelay_()中调用renderFrame_()并传入时间戳作为参数
        this.animationDelay_ = function () {
          this.animationDelayKey_ = undefined;
          this.renderFrame_(Date.now());
        }.bind(this);
    
    • 4)触发ViewChanged事件,从而调用了render()
    • 此时renderer_存在但定时器已创建,跳过

    上述中提到的定时器在触发后,会调用renderFrame_,进而执行渲染器的渲染过程,代码如下:

      renderFrame_(time) {
         let frameState = {...}
         this.renderer_.renderFrame(frameState);
      }
    

    其中renderer_就是地图的渲染器,它的创建是在MapcreateRenderer()实现

    import CompositeMapRenderer from './renderer/Composite.js';
    class Map extends PluggableMap {
     ...
      createRenderer() {
        return new CompositeMapRenderer(this);
      }
    }
    export default Map;
    

    可以看到实例化了一个CompositeMapRenderer,接下来看CompositeMapRenderer的工作过程

    CompositeMapRenderer实现逻辑

    CompositeMapRenderer继承自MapRenderer
    它在构造函数中创建了存放图层要素的dom,并插入到了 map的Viewport中
    它定义了renderFrame,并在其中调用了父类的同名方法:super.renderFrame(frameState)
    renderFrame的主要逻辑:

    1. 派发渲染前事件:RenderEventType.PRECOMPOSE
    2. 从frameState中取出图层组信息layerStatesArray,
    3. 遍历每一个图层layer,调用layer.render(...),得到layer的element要素
    4. 将获取到的所有结果要素替换调构造函数中的dom
    5. 派发渲染中事件:RenderEventType.POSTCOMPOSE

    看到这里,就知道下一步该探讨layer.render(...)

    layer的构建过程

    先来看CompositeMapRenderer中调用layer.render()部分的代码:

      renderFrame(frameState) {
        ...
        const layerStatesArray = frameState.layerStatesArray.sort(...);
        for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
          const layerState = layerStatesArray[i];
          const layer = layerState.layer;
          const element = layer.render(frameState, previousElement);
          ...
         }
      }
    

    可以看出,layer取自frameState,那接着看frameStatelayerStatesArray定义过程
    frameState是在PluggableMaprenderFrame_()中定义:

      renderFrame_(time) {
      ...
      let frameState = null;
        if (size !== undefined && hasArea(size) && view && view.isDef()) {
          const viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
          viewState = view.getState();
          frameState = {
            animate: false,
            coordinateToPixelTransform: this.coordinateToPixelTransform_,
            declutterItems: previousFrameState ? previousFrameState.declutterItems : [],
            extent: extent,
            focus: this.focus_ ? this.focus_ : viewState.center,
            index: this.frameIndex_++,
            layerIndex: 0,
            layerStatesArray: this.getLayerGroup().getLayerStatesArray(),  //这里
            pixelRatio: this.pixelRatio_,
            pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
            postRenderFunctions: [],
            size: size,
            skippedFeatureUids: this.skippedFeatureUids_,
            tileQueue: this.tileQueue_,
            time: time,
            usedTiles: {},
            viewState: viewState,
            viewHints: viewHints,
            wantedTiles: {}
          };
        }
      ...
      }
    

    也就是要找LayerGroup赋值的地方,在PluggableMapcreateOptionsInternal()

    function createOptionsInternal(options) { 
      const layerGroup = options.layers && typeof /** @type {?} */ (options.layers).getLayers === 'function' ?
        /** @type {LayerGroup} */ (options.layers) : new LayerGroup({ layers: /** @type {Collection} */ (options.layers) });
      values[MapProperty.LAYERGROUP] = layerGroup;
    }
    

    options追溯到了Map的初始参数,也就是最初调用的时候传入的图层了
    绕了一大圈,回到的最初的代码上

      layers: [
        new TileLayer({
          source: new OSM()
        })
      ],
    

    也就是layer.render()调用的就是TileLayer的渲染方法了
    后续文章我们详解解析图层layer的渲染过程!

    ol的dom初始化过程

    先来看ol的最终渲染要素结构

    <!-- 地图容器 -->
    <div id="map" class="map">
      <!-- 地图视口:所有ol内容都放在这里 -->
      <div class="ol-viewport" touch-action="none"
        style="position: relative; overflow: hidden; width: 100%; height: 100%; touch-action: none;" data-view="3">
        <!-- 核心要素的canvas绘制容器 -->
        <div class="ol-unselectable ol-layers" style="position: absolute; width: 100%; height: 100%; z-index: 0;">
          <div class="ol-layer" style="position: absolute; width: 100%; height: 100%;">
            <!-- canvas画板 -->
            <canvas width="1873" height="400"
              style="position: absolute; transform-origin: left top; transform: matrix(1, 0, 0, 1, 0, 0);">
            </canvas>
          </div>
        </div>
        <!-- 覆盖物容器 -->
        <div class="ol-overlaycontainer" style="position: absolute; z-index: 0; width: 100%; height: 100%;"></div>
        <!-- 界面组件容器 -->
        <div class="ol-overlaycontainer-stopevent" style="position: absolute; z-index: 0; width: 100%; height: 100%;">
          <!-- 组件:放大缩小 -->
          <div class="ol-zoom ol-unselectable ol-control">
            <button class="ol-zoom-in" type="button" title="Zoom in">+</button>
            <button class="ol-zoom-out" type="button" title="Zoom out">−</button>
          </div>
          <!-- 组件:图层控制等 -->
          <div class="ol-rotate ol-unselectable ol-control ol-hidden">
            <button class="ol-rotate-reset" type="button" title="Reset rotation">
              <span class="ol-compass" style="transform: rotate(0rad);">⇧</span>
            </button>
          </div>
          <!-- 组件:底图属性 -->
          <div class="ol-attribution ol-unselectable ol-control ol-uncollapsible">
            <ul>
              <li>© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors.</li>
            </ul>
            <button type="button" title="Attributions"><span>»</span></button>
          </div>
        </div>
      </div>
    </div>
    

    要素添加是在PluggableMap.js中实现,我们常用的Map.js就是继承自它,它实现了主要逻辑

        /**
         * viewport_添加
         * @private
         * @type {!HTMLElement}
         */
        this.viewport_ = document.createElement('div');
        this.viewport_.className = 'ol-viewport' + ('ontouchstart' in window ? ' ol-touch' : '');
        this.viewport_.style.position = 'relative';
        this.viewport_.style.overflow = 'hidden';
        this.viewport_.style.width = '100%';
        this.viewport_.style.height = '100%';
        // prevent page zoom on IE >= 10 browsers
        this.viewport_.style.msTouchAction = 'none';
        this.viewport_.style.touchAction = 'none';
    
        /**
         * overlayContainer_添加
         * @private
         * @type {!HTMLElement}
         */
        this.overlayContainer_ = document.createElement('div');
        this.overlayContainer_.style.position = 'absolute';
        this.overlayContainer_.style.zIndex = '0';
        this.overlayContainer_.style.width = '100%';
        this.overlayContainer_.style.height = '100%';
        this.overlayContainer_.className = 'ol-overlaycontainer';
        this.viewport_.appendChild(this.overlayContainer_);
    
        /**
         * overlayContainerStopEvent_添加
         * @private 
         * @type {!HTMLElement}
         */
        this.overlayContainerStopEvent_ = document.createElement('div');
        this.overlayContainerStopEvent_.style.position = 'absolute';
        this.overlayContainerStopEvent_.style.zIndex = '0';
        this.overlayContainerStopEvent_.style.width = '100%';
        this.overlayContainerStopEvent_.style.height = '100%';
        this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
        this.viewport_.appendChild(this.overlayContainerStopEvent_);
    

    图层容器添加入viewport是在CompositeMapRenderer中实现,继承自MapRenderer

        /**
         * ol-layers 添加
         * @private
         * @type {HTMLDivElement}
         */
        this.element_ = document.createElement('div');
        const style = this.element_.style;
        style.position = 'absolute';
        style.width = '100%';
        style.height = '100%';
        style.zIndex = '0';
    
        this.element_.className = CLASS_UNSELECTABLE + ' ol-layers';
    
        const container = map.getViewport();
        container.insertBefore(this.element_, container.firstChild || null);
    

    相关文章

      网友评论

        本文标题:OpenLayers地图渲染机制解析

        本文链接:https://www.haomeiwen.com/subject/zkulbctx.html