OpenLayers地图渲染机制解析

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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容

  • Samples 通过EPSG.io搜索进行二次投影 视图(View)动画 使用动态 ArcGIS REST Map...
    changhr2013阅读 15,338评论 0 8
  • 一 OpenLayers核心职责   OpenLayers最新大版本是openlayers4,它是一个基于h5的G...
    遥想公瑾当年阅读 22,302评论 2 24
  • source 是 Layer 的重要组成部分,表示图层的来源,也就是服务地址。除了在构造函数中指定外,还可以使用 ...
    changhr2013阅读 10,336评论 1 1
  • 今天我早早的吃完了早饭来到了学校,过了一会董老师也来了她叫我们读书,过了一会儿升旗仪式我们排好队伍就...
    谷殿艳阅读 128评论 0 0
  • 呵笔寻诗 文/蔚蓝 者, 握住耕耘的犁铧, 让诗雨沐浴深情的沃土; 敞开诗以道志的萦怀; 一个...
    Aanpaoyilian蔚蓝阅读 178评论 0 0