Rotate旋转控件源码分析
# 概述
Openlayers 中的Rotate旋转控件始终指向正北方向,当地图旋转时,控件也会旋转同样角度和方向;另外还可以通过点击控件重置地图的旋转角度。
本文主要介绍Rotate控件的实现原理。
# 源码分析
Rotate继承于Control类,除了响应用户点击外,还需要监听地图的旋转,其实现如下
class Rotate extends Control {
constructor(options) {
options = options ? options : {};
super({
element: document.createElement("div"),
render: options.render,
target: options.target,
});
const className =
options.className !== undefined ? options.className : "ol-rotate";
const label = options.label !== undefined ? options.label : "\u21E7";
const compassClassName =
options.compassClassName !== undefined
? options.compassClassName
: "ol-compass";
this.label_ = null;
if (typeof label === "string") {
this.label_ = document.createElement("span");
this.label_.className = compassClassName;
this.label_.textContent = label;
} else {
this.label_ = label;
this.label_.classList.add(compassClassName);
}
const tipLabel = options.tipLabel ? options.tipLabel : "Reset rotation";
const button = document.createElement("button");
button.className = className + "-reset";
button.setAttribute("type", "button");
button.title = tipLabel;
button.appendChild(this.label_);
button.addEventListener(
EventType.CLICK,
this.handleClick_.bind(this),
false
);
const cssClasses =
className + " " + CLASS_UNSELECTABLE + " " + CLASS_CONTROL;
const element = this.element;
element.className = cssClasses;
element.appendChild(button);
this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
this.duration_ = options.duration !== undefined ? options.duration : 250;
this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
this.rotation_ = undefined;
if (this.autoHide_) {
this.element.classList.add(CLASS_HIDDEN);
}
}
handleClick_(event) {
event.preventDefault();
if (this.callResetNorth_ !== undefined) {
this.callResetNorth_();
} else {
this.resetNorth_();
}
}
resetNorth_() {
const map = this.getMap();
const view = map.getView();
if (!view) {
return;
}
const rotation = view.getRotation();
if (rotation !== undefined) {
if (this.duration_ > 0 && rotation % (2 * Math.PI) !== 0) {
view.animate({
rotation: 0,
duration: this.duration_,
easing: easeOut,
});
} else {
view.setRotation(0);
}
}
}
render(mapEvent) {
const frameState = mapEvent.frameState;
if (!frameState) {
return;
}
const rotation = frameState.viewState.rotation;
if (rotation != this.rotation_) {
const transform = "rotate(" + rotation + "rad)";
if (this.autoHide_) {
const contains = this.element.classList.contains(CLASS_HIDDEN);
if (!contains && rotation === 0) {
this.element.classList.add(CLASS_HIDDEN);
} else if (contains && rotation !== 0) {
this.element.classList.remove(CLASS_HIDDEN);
}
}
this.label_.style.transform = transform;
}
this.rotation_ = rotation;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# Rotate控件的参数
Rotate控件的构造函数接受一个参数options对象,该对象有如下属性
className:控件的类名,默认为ol-rotatelabel:控件的显示标签图标,默认为\u21E7compassClassName:控件图标类名,默认为ol-compasstipLabel:控件hover时的显示,默认为Reset rotationresetNorth:点击控件后的回调事件,默认为undefinedduration: 地图动画的持续时长,默认为250,表示250 毫秒autoHide: 自动隐藏,默认为true,初始化时隐藏控件
Rotate类的构造函数内部会调用父类super({ element: document.createElement('div'),render: options.render,target: options.target,}),默认情况下,render和target都不存在,然后采用上述的默认值,创建一个按钮及其符号,当调用setMap方法时会将它添加到overlay container中;通过元素的addEventListener注册Rotate控件的点击handleClick_事件;默认情况下this.callRestNorth_为undefined,初始化this.rotation_为undefined;最后判断this.autoHide_是否为true,默认为true,就会给控件加上一个类名ol-hidden。
# Rotate类的方法
Rotate类的方法也不多,有如下三个:
handleClick_方法
handleClick_方法会判断this.callResetNorth是否为true,因为开发时可以通过参数options.resetNorth自定义点击事件,默认情况下,点击会执行resetNorth_方法
resetNorth_方法
resetNorth_方法内部就是获取当前视图的旋转角度,默认情况下会调用view.animate调整地图视图旋转角度为0;若this.duration_动画持续时长为0或者旋转的角度为360°倍数,则调用view.setRotation重置地图视图。
render方法
render方法会覆盖Rotate的父类Control中的render方法,该方法就是用于渲染控件的;默认情况下frameState.viewState.rotation为0,即此时地图未旋转,而this.rotation_为undefined,因此rotation不等于this.rotation_,继续判断this.autoHide,默认为true,判断控件的类名是否包含ol-hide,若不包含,且rotation为0,则给控件加上类名ol-hide,隐藏控件;反之,若控件的类名包含ol-hide且rotation不为0,则移除类名ol-hide,显示控件;然后将控件的label图标进行旋转,角度即为rotation;最后将rotation赋值给this.rotation_。
前面提到Rotate控件会跟随地图视图的旋转,继而控件图标也跟随同方向旋转同样角度,就是调用的render方法,那么render方法是如何调用的呢?
# render方法触发机制
render方法实际上是在父类Control类中调用的,在Control类一文中提过 Control类的setMap方法调用后,会判断this.render是否是个空方法,显然Rotate控件中重新定义了render方法,于是 Openlayers 会调用listen方法注册postrender类型的监听事件,而回调方法就是this.render;而调用map.render()方法进行渲染时,会调用animationDelay_方法,在该方法中又会调用renderFrame_方法,在renderFrame_方法中会执行这行代码this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));进行postrender类型的注册事件的派发,这就会执行this.render方法,从而实现Rotate控件的实时同步旋转。
# 总结
Rotate控件是基于Control类实现的,而其实时同步旋转的逻辑还是addEventListener(listen中调用注册的方法)和dispatchEvent那一套机制。