开源

OpenLayers地图渲染机制解析

2019-11-03  本文已影响0人  jadefan

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的构造函数,
  render() {
    if (this.renderer_ && this.animationDelayKey_ === undefined) {
      this.animationDelayKey_ = requestAnimationFrame(this.animationDelay_);
    }
  }
    this.animationDelay_ = function () {
      this.animationDelayKey_ = undefined;
      this.renderFrame_(Date.now());
    }.bind(this);

上述中提到的定时器在触发后,会调用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);
上一篇下一篇

猜你喜欢

热点阅读