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、默认视角。
可以看出地图渲染流程要包含:
- 构建dom
- 解析图层参数,渲染到canvas上
- 调整视角
一个基本概念:
地图是由很多图层构成,地图渲染可以归结为图层渲染
ol的图层类别有很多中,渲染逻辑也各不相同
所有本文主要探讨地图组装图层以及初始dom的操作过程,图层后续分别详细探讨
ol的Map初始化逻辑
Map
为主入口,继承自PluggableMap
,主要逻辑在PluggableMap
中实现
- 执行
Map
的构造函数,接收参数,判断组件和交互事件
PluggableMap实现逻辑
- 执行
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_
就是地图的渲染器,它的创建是在Map
中createRenderer()
实现
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
的主要逻辑:
- 派发渲染前事件:
RenderEventType.PRECOMPOSE
- 从frameState中取出图层组信息layerStatesArray,
- 遍历每一个图层layer,调用
layer.render(...)
,得到layer的element要素 - 将获取到的所有结果要素替换调构造函数中的dom
- 派发渲染中事件:
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,那接着看frameState
的layerStatesArray
定义过程
frameState
是在PluggableMap
的renderFrame_()
中定义:
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赋值的地方,在PluggableMap
的createOptionsInternal()
中
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);
网友评论