openlayers源码阅读笔记(一)—— 地图ol.Map
2. ol/Map类
class Map extends PluggableMap {
/**
* @param {import("./PluggableMap.js").MapOptions} options Map options.
*/
constructor(options) {
options = assign({}, options);
if (!options.controls) {
options.controls = defaultControls();
}
if (!options.interactions) {
options.interactions = defaultInteractions({
onFocusOnly: true,
});
}
super(options);
}
createRenderer() {
return new CompositeMapRenderer(this);
}
}
Map类继承自PluggableMap类并且实现了的父类的createRenderer方法,该方法的作用是返回一个地图渲染器,接下来看PluggableMap类。
3. ol/PluggableMap类
3.1 创建DOM节点
class PluggableMap extends BaseObject {
/**
* @param {MapOptions} options Map options.
*/
constructor(options) {
super();
... // 305行到341行代码
/**
* @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%';
/**
* @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_.style.pointerEvents = 'none';
this.overlayContainer_.className = 'ol-overlaycontainer';
this.viewport_.appendChild(this.overlayContainer_);
/**
* @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_.style.pointerEvents = 'none';
this.overlayContainerStopEvent_.className = 'ol-overlaycontainer-stopevent';
this.viewport_.appendChild(this.overlayContainerStopEvent_);
...
}
}
openlayers中的DOM结构如下,其中div.ol-viewport、div.ol-overlaycontainer、div.ol-overlaycontainer-stopevent便是在这里定义的;div.ol-layers则是在渲染图层的时候创建。
<div class="map">
<div class="ol-viewport">
<div class="ol-unselectable ol-layers"></div>
<div class="ol-overlaycontainer"></div>
<div class="ol-overlaycontainer-stopevent"></div>
</div>
</div>
3.2 监听事件
class PluggableMap extends BaseObject {
/**
* @param {MapOptions} options Map options.
*/
constructor(options) {
... // 413行到423行代码
this.addChangeListener(
MapProperty.LAYERGROUP,
this.handleLayerGroupChanged_
);
this.addChangeListener(MapProperty.VIEW, this.handleViewChanged_);
this.addChangeListener(MapProperty.SIZE, this.handleSizeChanged_);
this.addChangeListener(MapProperty.TARGET, this.handleTargetChanged_);
// setProperties will trigger the rendering of the map if the map
// is "defined" already.
this.setProperties(optionsInternal.values);
...
}
}
这里监听了图层组LAYERGROUP、视图VIEW、地图尺寸SIZE、地图关联的HTML元素TARGET的改变事假,紧跟着的setProperties会触发事件渲染地图。
3.3 地图渲染
我们先看图层改变的事件,我们new Map(option)时option里面的layers属性,以及调用map.addLayer,map.removeLayer时都会改变layerGroup,触发此事件。
/**
* @private
*/
handleLayerGroupChanged_() {
if (this.layerGroupPropertyListenerKeys_) {
this.layerGroupPropertyListenerKeys_.forEach(unlistenByKey);
this.layerGroupPropertyListenerKeys_ = null;
}
const layerGroup = this.getLayerGroup();
if (layerGroup) {
this.handleLayerAdd_(new GroupEvent('addlayer', layerGroup));
this.layerGroupPropertyListenerKeys_ = [
listen(layerGroup, ObjectEventType.PROPERTYCHANGE, this.render, this),
listen(layerGroup, EventType.CHANGE, this.render, this),
listen(layerGroup, 'addlayer', this.handleLayerAdd_, this),
listen(layerGroup, 'removelayer', this.handleLayerRemove_, this),
];
}
this.render();
}
前面几行代码就是解绑和监听LayerGroup状态改变相关的事件,最后执行了地图的渲染函数,接下来我们看render方法。
/**
* Request a map rendering (at the next animation frame).
* @api
*/
render() {
if (this.renderer_ && this.animationDelayKey_ === undefined) {
this.animationDelayKey_ = requestAnimationFrame(this.animationDelay_);
}
}
/**
* @private
*/
this.animationDelay_ = /** @this {PluggableMap} */ function () {
this.animationDelayKey_ = undefined;
this.renderFrame_(Date.now());
}.bind(this);
requestAnimationFrame要求浏览器在下次重绘之前调用指定的回调函数更新动画,也就是说在render函数中执行了一个方法this.animationDelay_,而在animationDelay_调用了方法this.renderFrame_,接下来我们看renderFrame_方法。
3.4 renderFrame方法
虽然renderFrame有100行代码,但很多都是对framestate的配置以及地图含有动画的情况的处理。framestate我理解是帧状态,也就是地图接下来需要渲染的那一帧的状态信息,包括地图的范围extent、坐标与像素相互转换矩阵、像素比pixelRatio、地图尺寸size、请求渲染的时间time以及视图状态、图层状态等。如果地图正处于动画中,则会在次调用地图的render函数并且在动画开始和结束时给地图派发MOVESTART和MOVEEND事件。
renderFrame_(time) {
......
this.frameState_ = frameState;
this.renderer_.renderFrame(frameState);
if (frameState) {
if (frameState.animate) {
this.render();
}
Array.prototype.push.apply(
this.postRenderFunctions_,
frameState.postRenderFunctions
)
......
}
this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
......
}
if (!this.renderer_) {
this.renderer_ = this.createRenderer();
}
在配置好framestate之后,调用了this.renderer_.renderFrame方法,最后派发的地图的POSTRENDER事件,而this.renderer_便是上次提到Map类实现的createRenderer创建的,它的代码我们会在下一节渲染器中讲。
4 小结
Map类主要做的事包括定义地图的Dom节点、监听地图改变事件,调用渲染器来渲染地图。其实还有一些在博客中没有说到,比如添加交互Interaction、叠加层Overlay、空间Control以及其添加、移除事件。下面是交互的一些相关代码。
this.interactions.addEventListener(
CollectionEventType.ADD,
/**
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
*/
function (event) {
event.element.setMap(this);
}.bind(this)
);
this.interactions.addEventListener(
CollectionEventType.REMOVE,
/**
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
*/
function (event) {
event.element.setMap(null);
}.bind(this)
);
......
this.interactions.forEach(
/**
* @param {import("./interaction/Interaction.js").default} interaction Interaction.
* @this {PluggableMap}
*/
function (interaction) {
interaction.setMap(this);
}.bind(this)
);