638 lines
22 KiB
Markdown
638 lines
22 KiB
Markdown
|
---
|
|||
|
title: 快速起步
|
|||
|
date: 2021-09-08
|
|||
|
author: ac
|
|||
|
tags:
|
|||
|
- OpenLayers
|
|||
|
categories:
|
|||
|
- GIS
|
|||
|
---
|
|||
|
|
|||
|
## 1. hello ol
|
|||
|
|
|||
|
> 其实学习的最好方式应该是官方文档,但可能会受限于个人的知识储备问题,“吸收”到的知识也会有所差别。
|
|||
|
|
|||
|
```html
|
|||
|
<!doctype html>
|
|||
|
<html lang="en">
|
|||
|
<head>
|
|||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/css/ol.css" type="text/css">
|
|||
|
<style>
|
|||
|
.map {
|
|||
|
height: 400px;
|
|||
|
width: 100%;
|
|||
|
}
|
|||
|
</style>
|
|||
|
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.4.3/build/ol.js"></script>
|
|||
|
<title>OpenLayers example</title>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<h2>My Map</h2>
|
|||
|
<div id="map" class="map"></div>
|
|||
|
<script type="text/javascript">
|
|||
|
/*
|
|||
|
* 地图表现:必备三要素,
|
|||
|
* 图层(Layer)
|
|||
|
* 视图(View)
|
|||
|
* 目标容器(target)
|
|||
|
*
|
|||
|
* 核心类:Map、Layer、Source、View
|
|||
|
* 渲染方式:ol3中有Canvas、WebGL、DOM
|
|||
|
* ol5中删除了DOM渲染方式,Canvas(由ol.renderer.Map实现)、
|
|||
|
* WebGL(由ol.renderer.Layer实现)
|
|||
|
*/
|
|||
|
var map = new ol.Map({
|
|||
|
target: 'map',
|
|||
|
layers: [
|
|||
|
new ol.layer.Tile({
|
|||
|
source: new ol.source.OSM()
|
|||
|
})
|
|||
|
],
|
|||
|
view: new ol.View({
|
|||
|
center: ol.proj.fromLonLat([37.41, 8.82]),
|
|||
|
zoom: 4
|
|||
|
})
|
|||
|
});
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
```
|
|||
|
|
|||
|
这是官网上的`quickstart`的中代码:
|
|||
|
|
|||
|
1. 采用传统的CDN的方式引入ol;
|
|||
|
2. 创建了一个id为map的div元素,作为地图的容器,并通过class指定元素大小;
|
|||
|
3. 再使用ol中Map类的构造器创建地图,配置参数中必须配置三个参数才能显示地图。其中`target`指定页面中的容器标签;`layers`配置地图的图层;`view`可以指定地图的中心位置和地图的缩放级别,还可以配置地图的投影等。
|
|||
|
|
|||
|
`ol`中没有在view里面配置投影的,默认使用的是Web墨卡托投影(`EPSG:3857`),投影相关的方法在`ol.proj`的命名空间下。`fromLonLat`方法是将经纬度的地理坐标转换为投影坐标,默认的目标投影是`EPSG:3857`。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## 2. 开发方式
|
|||
|
|
|||
|
除了上述的传统的直接使用`CND`引入`ol`的开发方式外,目前在前端开发最常用的还是安装`npm`包的形式。
|
|||
|
|
|||
|
> 需要安装Nodejs环境
|
|||
|
|
|||
|
前端工程化解决方案有很多,像`Webpack`、[`Parcel`](https://parceljs.org/)等。`ol`官方的教程使用的是`Parcel`。
|
|||
|
|
|||
|
下面我们使用`Parcel`工具来手动配置一个工程化的示例:
|
|||
|
|
|||
|
1. 安装`Parcel`
|
|||
|
|
|||
|
```shell
|
|||
|
#npm
|
|||
|
npm install -g parcel-bundler
|
|||
|
|
|||
|
#yarn
|
|||
|
yarn global add parcel-bundler
|
|||
|
```
|
|||
|
|
|||
|
2. 创建项目目录,目录名称为`pracelol`
|
|||
|
|
|||
|
```shell
|
|||
|
mkdir pracelol && cd pracelol
|
|||
|
```
|
|||
|
|
|||
|
3. 初始化项目,生成包管理文件`package.json`,安装`ol`
|
|||
|
|
|||
|
```shell
|
|||
|
npm init -y
|
|||
|
npm install ol
|
|||
|
```
|
|||
|
|
|||
|
4. 创建`index.html`文件和`main.js`文件:
|
|||
|
|
|||
|
`index.html`
|
|||
|
|
|||
|
```html
|
|||
|
<!DOCTYPE html>
|
|||
|
<html lang="en">
|
|||
|
<head>
|
|||
|
<meta charset="UTF-8">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
<title>Document</title>
|
|||
|
<script type="module" src="main.js"></script>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div id="map" style="width: 100%;height: 400px;"></div>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
```
|
|||
|
|
|||
|
> `script`标签中添加`type`属性,值为`module`,`Parcel`会将该标签引用的JS文件转码为ES5。
|
|||
|
|
|||
|
`mian.js`
|
|||
|
|
|||
|
```javascript
|
|||
|
import 'ol/ol.css';
|
|||
|
import Map from 'ol/Map';
|
|||
|
import View from 'ol/View';
|
|||
|
import OSM from 'ol/source/OSM';
|
|||
|
import TileLayer from 'ol/layer/Tile';
|
|||
|
|
|||
|
var map = new Map({
|
|||
|
layers: [
|
|||
|
new TileLayer({source: new OSM()})
|
|||
|
],
|
|||
|
view: new View({
|
|||
|
center: [0, 0],
|
|||
|
zoom: 4
|
|||
|
}),
|
|||
|
target: 'map'
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
5. 在`package.json`文件中配置脚本命令:
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"name": "pracelol",
|
|||
|
"version": "1.0.0",
|
|||
|
"description": "",
|
|||
|
"main": "index.js",
|
|||
|
"scripts": {
|
|||
|
"dev":"parcel index.html"
|
|||
|
},
|
|||
|
"keywords": [],
|
|||
|
"author": "",
|
|||
|
"license": "ISC",
|
|||
|
"dependencies": {
|
|||
|
"ol": "^6.7.0"
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
6. 运行`npm run dev`命令,会执行`parcel index.html`将`index.html`文件作为入口文件进行打包编译,会看到项目中做出了一个`dist`目录。并启动一个地址为`http://localhost:1234`的Web服务。
|
|||
|
|
|||
|
7. 效果:
|
|||
|
|
|||
|
<img src="./images/image-20210911112328085.png" alt="image-20210911112328085" style="zoom: 80%;" />
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## 3. 源码解析
|
|||
|
`Map.js`
|
|||
|
|
|||
|
```javascript
|
|||
|
/**
|
|||
|
* @module ol/Map
|
|||
|
*/
|
|||
|
import PluggableMap from './PluggableMap.js';
|
|||
|
import {defaults as defaultControls} from './control.js';
|
|||
|
import {defaults as defaultInteractions} from './interaction.js';
|
|||
|
import {assign} from './obj.js';
|
|||
|
import CompositeMapRenderer from './renderer/Composite.js';
|
|||
|
|
|||
|
/**
|
|||
|
* @classdesc
|
|||
|
* The map is the core component of OpenLayers. For a map to render, a view,
|
|||
|
* one or more layers, and a target container are needed:
|
|||
|
*
|
|||
|
* import Map from 'ol/Map';
|
|||
|
* import View from 'ol/View';
|
|||
|
* import TileLayer from 'ol/layer/Tile';
|
|||
|
* import OSM from 'ol/source/OSM';
|
|||
|
*
|
|||
|
* var map = new Map({
|
|||
|
* view: new View({
|
|||
|
* center: [0, 0],
|
|||
|
* zoom: 1
|
|||
|
* }),
|
|||
|
* layers: [
|
|||
|
* new TileLayer({
|
|||
|
* source: new OSM()
|
|||
|
* })
|
|||
|
* ],
|
|||
|
* target: 'map'
|
|||
|
* });
|
|||
|
*
|
|||
|
* The above snippet creates a map using a {@link module:ol/layer/Tile} to
|
|||
|
* display {@link module:ol/source/OSM~OSM} OSM data and render it to a DOM
|
|||
|
* element with the id `map`.
|
|||
|
*
|
|||
|
* The constructor places a viewport container (with CSS class name
|
|||
|
* `ol-viewport`) in the target element (see `getViewport()`), and then two
|
|||
|
* further elements within the viewport: one with CSS class name
|
|||
|
* `ol-overlaycontainer-stopevent` for controls and some overlays, and one with
|
|||
|
* CSS class name `ol-overlaycontainer` for other overlays (see the `stopEvent`
|
|||
|
* option of {@link module:ol/Overlay~Overlay} for the difference). The map
|
|||
|
* itself is placed in a further element within the viewport.
|
|||
|
*
|
|||
|
* Layers are stored as a {@link module:ol/Collection~Collection} in
|
|||
|
* layerGroups. A top-level group is provided by the library. This is what is
|
|||
|
* accessed by `getLayerGroup` and `setLayerGroup`. Layers entered in the
|
|||
|
* options are added to this group, and `addLayer` and `removeLayer` change the
|
|||
|
* layer collection in the group. `getLayers` is a convenience function for
|
|||
|
* `getLayerGroup().getLayers()`. Note that {@link module:ol/layer/Group~Group}
|
|||
|
* is a subclass of {@link module:ol/layer/Base}, so layers entered in the
|
|||
|
* options or added with `addLayer` can be groups, which can contain further
|
|||
|
* groups, and so on.
|
|||
|
*
|
|||
|
* @api
|
|||
|
*/
|
|||
|
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();
|
|||
|
}
|
|||
|
|
|||
|
super(options);
|
|||
|
}
|
|||
|
|
|||
|
createRenderer() {
|
|||
|
return new CompositeMapRenderer(this);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
export default Map;
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
我们可以在`ol.map`源码的注释中知道`map`是`Opnelayer`中核心的组件,一个`map`必须要有一个`View`实例、一个或多个图层`layers`和一个用于确定页面渲染`DOM`节点的`id`的`target`,这是地图表现的必备三要素(view、layers、target)。
|
|||
|
|
|||
|
地图初始化后,在id为`target`属性指定的`DOM`节点为容器生成了一系列的地图表现相关的标签。
|
|||
|
|
|||
|
<img src="./images/image-20201102105547517.png" alt="image-20201102105547517" style="zoom: 50%;" />
|
|||
|
|
|||
|
|
|||
|
|
|||
|
在`target`元素的位置内创建地图视口容器`class`属性为`ol-viewport`,可以通过`getViewport()`方法获取该节点。另外在`ol-viewport`里面创建用于图层渲染的`ol-layer`节点、用于在地图上添加注记图标的`ol-overlaycontainer`节点和用于展示地图控件的`ol-overlaycontainer-stopevent`节点。
|
|||
|
|
|||
|
- `map `:Map.target
|
|||
|
- `ol.viewport`:Map.view 视图
|
|||
|
- `ol-layers`:Map.getLayers() 图层组(集合)
|
|||
|
- `ol-layer`:图层,根据渲染方式创建Canvas元素
|
|||
|
- canvas :画布
|
|||
|
- `ol-overlaycontainer`:Map.getOverlays() 内容叠加层
|
|||
|
- `ol-overlaycontainer-stopevent`:Map.getControls() 控件层
|
|||
|
|
|||
|
> PC端页面视口的大小就是浏览器的大小,但这里`ol-viewport`的宽高大小设置都为100%作为地图的视口,是最近的父辈元素的容器大小,即`target`属性指定的`DOM`元素的大小。
|
|||
|
|
|||
|
图层组`Layers`是以图层数组的形式存储,与其他地图`API`不同,`ol`中没有必须的底图`basemap`,所有的图层都按照加载的顺序叠加显示,先添加的在下面,从底向上排列。
|
|||
|
|
|||
|
在`ol.Map`源码中,Map构造器作为主入口,接受参数,判断是否使用默认的控件和交互控件,其余渲染流程都在父类`PluggableMap`中。主要渲染流程如下:
|
|||
|
|
|||
|
1. 配置参数option,解析控件、交互组件、键盘事件DOM对象、叠加层和图层数组
|
|||
|
|
|||
|
```javascript
|
|||
|
const optionsInternal = createOptionsInternal(options);
|
|||
|
|
|||
|
/**
|
|||
|
* @param {MapOptions} options Map options.
|
|||
|
* @return {MapOptionsInternal} Internal map options.
|
|||
|
*/
|
|||
|
function createOptionsInternal(options) {
|
|||
|
|
|||
|
...
|
|||
|
|
|||
|
return {
|
|||
|
controls: controls, //控件
|
|||
|
interactions: interactions, //交互组件
|
|||
|
keyboardEventTarget: keyboardEventTarget, //键盘事件dom对象
|
|||
|
overlays: overlays, //叠加层
|
|||
|
values: values //图层数组
|
|||
|
};
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
2. 构建页面DOM元素,`ol-viewport`和子容器`ol-overlaycontainer`、`ol-overlaycontainer-stopevent`
|
|||
|
|
|||
|
```javascript
|
|||
|
/**
|
|||
|
* @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_.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_.className = 'ol-overlaycontainer-stopevent';
|
|||
|
this.viewport_.appendChild(this.overlayContainerStopEvent_);
|
|||
|
|
|||
|
/**
|
|||
|
* 绑定浏览器事件
|
|||
|
*/
|
|||
|
this.mapBrowserEventHandler_ = new MapBrowserEventHandler(this, options.moveTolerance);
|
|||
|
const handleMapBrowserEvent = this.handleMapBrowserEvent.bind(this);
|
|||
|
for (const key in MapBrowserEventType) {
|
|||
|
this.mapBrowserEventHandler_.addEventListener(MapBrowserEventType[key],
|
|||
|
handleMapBrowserEvent);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @private
|
|||
|
* @type {HTMLElement|Document}
|
|||
|
*/
|
|||
|
this.keyboardEventTarget_ = optionsInternal.keyboardEventTarget;
|
|||
|
|
|||
|
/**
|
|||
|
* @private
|
|||
|
* @type {?Array<import("./events.js").EventsKey>}
|
|||
|
*/
|
|||
|
this.keyHandlerKeys_ = null;
|
|||
|
|
|||
|
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
|
|||
|
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
|
|||
|
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent,
|
|||
|
PASSIVE_EVENT_LISTENERS ? {passive: false} : false);
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
3. 创建瓦片队列,添加图层、视图、SIZE、TARGET变化的处理事件
|
|||
|
|
|||
|
```javascript
|
|||
|
/**
|
|||
|
* @private
|
|||
|
* @type {TileQueue}
|
|||
|
*/
|
|||
|
this.tileQueue_ = new TileQueue(
|
|||
|
this.getTilePriority.bind(this),
|
|||
|
this.handleTileChange_.bind(this));
|
|||
|
|
|||
|
this.addEventListener(getChangeEventType(MapProperty.LAYERGROUP), this.handleLayerGroupChanged_);
|
|||
|
this.addEventListener(getChangeEventType(MapProperty.VIEW), this.handleViewChanged_);
|
|||
|
this.addEventListener(getChangeEventType(MapProperty.SIZE), this.handleSizeChanged_);
|
|||
|
this.addEventListener(getChangeEventType(MapProperty.TARGET), this.handleTargetChanged_);
|
|||
|
```
|
|||
|
|
|||
|
4. 解析控件参数,并绑定事件监听
|
|||
|
|
|||
|
```javascript
|
|||
|
this.controls.forEach(
|
|||
|
/**
|
|||
|
* @param {import("./control/Control.js").default} control Control.
|
|||
|
* @this {PluggableMap}
|
|||
|
*/
|
|||
|
function(control) {
|
|||
|
control.setMap(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
this.controls.addEventListener(CollectionEventType.ADD,
|
|||
|
/**
|
|||
|
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
|
|||
|
*/
|
|||
|
function(event) {
|
|||
|
event.element.setMap(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
this.controls.addEventListener(CollectionEventType.REMOVE,
|
|||
|
/**
|
|||
|
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
|
|||
|
*/
|
|||
|
function(event) {
|
|||
|
event.element.setMap(null);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
5. 解析交互参数,添加事件监听
|
|||
|
|
|||
|
```javascript
|
|||
|
`` this.interactions.forEach(
|
|||
|
/**
|
|||
|
* @param {import("./interaction/Interaction.js").default} interaction Interaction.
|
|||
|
* @this {PluggableMap}
|
|||
|
*/
|
|||
|
function(interaction) {
|
|||
|
interaction.setMap(this);
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
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));
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
6. 解析叠加层,添加事件监听
|
|||
|
|
|||
|
```javascript
|
|||
|
this.overlays_.forEach(this.addOverlayInternal_.bind(this));
|
|||
|
|
|||
|
this.overlays_.addEventListener(CollectionEventType.ADD,
|
|||
|
/**
|
|||
|
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
|
|||
|
*/
|
|||
|
function(event) {
|
|||
|
this.addOverlayInternal_(/** @type {import("./Overlay.js").default} */ (event.element));
|
|||
|
}.bind(this));
|
|||
|
|
|||
|
this.overlays_.addEventListener(CollectionEventType.REMOVE,
|
|||
|
/**
|
|||
|
* @param {import("./Collection.js").CollectionEvent} event CollectionEvent.
|
|||
|
*/
|
|||
|
function(event) {
|
|||
|
const overlay = /** @type {import("./Overlay.js").default} */ (event.element);
|
|||
|
const id = overlay.getId();
|
|||
|
if (id !== undefined) {
|
|||
|
delete this.overlayIdIndex_[id.toString()];
|
|||
|
}
|
|||
|
event.element.setMap(null);
|
|||
|
}.bind(this));
|
|||
|
```
|
|||
|
|
|||
|
7. 因为layer、target和view是构建map的必备要素,所以一定会触发`handleLayerGroupChanged_`、`handleTargetChanged_`和`handleViewChanged_`事件,从而执行`this.render()`函数,最后执行渲染的主函数`renderFrame_()`。
|
|||
|
|
|||
|
```javascript
|
|||
|
handleLayerGroupChanged_() {
|
|||
|
console.log("handleLayerGroupChanged_")
|
|||
|
if (this.layerGroupPropertyListenerKeys_) {
|
|||
|
this.layerGroupPropertyListenerKeys_.forEach(unlistenByKey);
|
|||
|
this.layerGroupPropertyListenerKeys_ = null;
|
|||
|
}
|
|||
|
const layerGroup = this.getLayerGroup();
|
|||
|
if (layerGroup) {
|
|||
|
this.layerGroupPropertyListenerKeys_ = [
|
|||
|
listen(
|
|||
|
layerGroup, ObjectEventType.PROPERTYCHANGE,
|
|||
|
this.render, this),
|
|||
|
listen(
|
|||
|
layerGroup, EventType.CHANGE,
|
|||
|
this.render, this)
|
|||
|
];
|
|||
|
}
|
|||
|
this.render();
|
|||
|
}
|
|||
|
|
|||
|
render() {
|
|||
|
console.log("render");
|
|||
|
if (this.renderer_ && this.animationDelayKey_ === undefined) {
|
|||
|
this.animationDelayKey_ = requestAnimationFrame(this.animationDelay_);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this.animationDelay_ = function() {
|
|||
|
console.log("animationDelay_")
|
|||
|
this.animationDelayKey_ = undefined;
|
|||
|
this.renderFrame_(Date.now());
|
|||
|
}.bind(this);
|
|||
|
|
|||
|
renderFrame_(time) {
|
|||
|
console.log("renderFrame_")
|
|||
|
const size = this.getSize();
|
|||
|
const view = this.getView();
|
|||
|
const previousFrameState = this.frameState_;
|
|||
|
/** @type {?FrameState} */
|
|||
|
let frameState = null;
|
|||
|
if (size !== undefined && hasArea(size) && view && view.isDef()) {
|
|||
|
const viewHints = view.getHints(this.frameState_ ? this.frameState_.viewHints : undefined);
|
|||
|
const viewState = view.getState();
|
|||
|
frameState = {
|
|||
|
animate: false,
|
|||
|
coordinateToPixelTransform: this.coordinateToPixelTransform_,
|
|||
|
declutterItems: previousFrameState ? previousFrameState.declutterItems : [],
|
|||
|
extent: getForViewAndSize(viewState.center, viewState.resolution, viewState.rotation, size),
|
|||
|
index: this.frameIndex_++,
|
|||
|
layerIndex: 0,
|
|||
|
layerStatesArray: this.getLayerGroup().getLayerStatesArray(),
|
|||
|
pixelRatio: this.pixelRatio_,
|
|||
|
pixelToCoordinateTransform: this.pixelToCoordinateTransform_,
|
|||
|
postRenderFunctions: [],
|
|||
|
size: size,
|
|||
|
tileQueue: this.tileQueue_,
|
|||
|
time: time,
|
|||
|
usedTiles: {},
|
|||
|
viewState: viewState,
|
|||
|
viewHints: viewHints,
|
|||
|
wantedTiles: {}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
this.frameState_ = frameState;
|
|||
|
this.renderer_.renderFrame(frameState);
|
|||
|
|
|||
|
if (frameState) {
|
|||
|
if (frameState.animate) {
|
|||
|
this.render();
|
|||
|
}
|
|||
|
Array.prototype.push.apply(this.postRenderFunctions_, frameState.postRenderFunctions);
|
|||
|
|
|||
|
if (previousFrameState) {
|
|||
|
const moveStart = !this.previousExtent_ ||
|
|||
|
(!isEmpty(this.previousExtent_) &&
|
|||
|
!equals(frameState.extent, this.previousExtent_));
|
|||
|
if (moveStart) {
|
|||
|
this.dispatchEvent(
|
|||
|
new MapEvent(MapEventType.MOVESTART, this, previousFrameState));
|
|||
|
this.previousExtent_ = createOrUpdateEmpty(this.previousExtent_);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const idle = this.previousExtent_ &&
|
|||
|
!frameState.viewHints[ViewHint.ANIMATING] &&
|
|||
|
!frameState.viewHints[ViewHint.INTERACTING] &&
|
|||
|
!equals(frameState.extent, this.previousExtent_);
|
|||
|
|
|||
|
if (idle) {
|
|||
|
this.dispatchEvent(new MapEvent(MapEventType.MOVEEND, this, frameState));
|
|||
|
clone(frameState.extent, this.previousExtent_);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//派发图层渲染的postrender事件
|
|||
|
this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
|
|||
|
|
|||
|
this.postRenderTimeoutHandle_ = setTimeout(this.handlePostRender.bind(this), 0);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @protected
|
|||
|
*/
|
|||
|
handlePostRender() {
|
|||
|
console.log("handlePostRender");
|
|||
|
const frameState = this.frameState_;
|
|||
|
|
|||
|
// Manage the tile queue
|
|||
|
// Image loads are expensive and a limited resource, so try to use them
|
|||
|
// efficiently:
|
|||
|
// * When the view is static we allow a large number of parallel tile loads
|
|||
|
// to complete the frame as quickly as possible.
|
|||
|
// * When animating or interacting, image loads can cause janks, so we reduce
|
|||
|
// the maximum number of loads per frame and limit the number of parallel
|
|||
|
// tile loads to remain reactive to view changes and to reduce the chance of
|
|||
|
// loading tiles that will quickly disappear from view.
|
|||
|
const tileQueue = this.tileQueue_;
|
|||
|
if (!tileQueue.isEmpty()) {
|
|||
|
let maxTotalLoading = this.maxTilesLoading_;
|
|||
|
let maxNewLoads = maxTotalLoading;
|
|||
|
if (frameState) {
|
|||
|
const hints = frameState.viewHints;
|
|||
|
if (hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]) {
|
|||
|
const lowOnFrameBudget = !IMAGE_DECODE && Date.now() - frameState.time > 8;
|
|||
|
maxTotalLoading = lowOnFrameBudget ? 0 : 8;
|
|||
|
maxNewLoads = lowOnFrameBudget ? 0 : 2;
|
|||
|
}
|
|||
|
}
|
|||
|
if (tileQueue.getTilesLoading() < maxTotalLoading) {
|
|||
|
tileQueue.reprioritize(); // FIXME only call if view has changed
|
|||
|
tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (frameState && this.hasListener(RenderEventType.RENDERCOMPLETE) && !frameState.animate &&
|
|||
|
!this.tileQueue_.getTilesLoading() && !this.getLoading()) {
|
|||
|
|
|||
|
//派发图层渲染的rendercomplete事件
|
|||
|
this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState);
|
|||
|
}
|
|||
|
|
|||
|
const postRenderFunctions = this.postRenderFunctions_;
|
|||
|
for (let i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
|
|||
|
postRenderFunctions[i](this, frameState);
|
|||
|
}
|
|||
|
postRenderFunctions.length = 0;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
## 参考文章
|
|||
|
|
|||
|
[1] Openlayers源码阅读 https://blog.csdn.net/u013240519/article/details/104997512
|