Icon类
# 概述
在 Openlayers 中,Icon
类用于创建图标,通常用于在地图上显示标记或图像元素。这个类可以通过设置图标的各种属性(如大小、图像源、锚点等)来控制图标的外观和位置。
Icon
类继承于ImageStyle
类,关于ImageStyle
类,可以参考这篇文章。
# 源码分析
# Icon
类源码实现
Icon
类源码实现如下:
class Icon extends ImageStyle {
constructor(options) {
options = options || {};
// 透明度
const opacity = options.opacity !== undefined ? options.opacity : 1;
// 旋转角度
const rotation = options.rotation !== undefined ? options.rotation : 0;
// 缩放大小
const scale = options.scale !== undefined ? options.scale : 1;
//是否跟随地图旋转
const rotateWithView =
options.rotateWithView !== undefined ? options.rotateWithView : false;
super({
opacity: opacity,
rotation: rotation,
scale: scale,
displacement:
options.displacement !== undefined ? options.displacement : [0, 0],
rotateWithView: rotateWithView,
declutterMode: options.declutterMode,
});
//锚点相关
//图标相对锚点的位置
this.anchor_ = options.anchor !== undefined ? options.anchor : [0.5, 0.5];
this.normalizedAnchor_ = null;
//锚点的原点
this.anchorOrigin_ =
options.anchorOrigin !== undefined ? options.anchorOrigin : "top-left";
// 锚点水平方向的单位
this.anchorXUnits_ =
options.anchorXUnits !== undefined ? options.anchorXUnits : "fraction";
// 锚点垂直方向的位置
this.anchorYUnits_ =
options.anchorYUnits !== undefined ? options.anchorYUnits : "fraction";
// 用于设置跨域请求的策略,默认为null
this.crossOrigin_ =
options.crossOrigin !== undefined ? options.crossOrigin : null;
// 图标的图像
const image = options.img !== undefined ? options.img : null;
// 图像的唯一标识
let cacheKey = options.src;
assert(
!(cacheKey !== undefined && image),
"`image` and `src` cannot be provided at the same time"
);
if ((cacheKey === undefined || cacheKey.length === 0) && image) {
cacheKey = /** @type {HTMLImageElement} */ (image).src || getUid(image);
}
assert(
cacheKey !== undefined && cacheKey.length > 0,
"A defined and non-empty `src` or `image` must be provided"
);
assert(
!(
(options.width !== undefined || options.height !== undefined) &&
options.scale !== undefined
),
"`width` or `height` cannot be provided together with `scale`"
);
let imageState; //图像状态
if (options.src !== undefined) {
imageState = ImageState.IDLE;
} else if (image !== undefined) {
if ("complete" in image) {
if (image.complete) {
imageState = image.src ? ImageState.LOADED : ImageState.IDLE;
} else {
imageState = ImageState.LOADING;
}
} else {
imageState = ImageState.LOADED;
}
}
// 图标的颜色
this.color_ = options.color !== undefined ? asArray(options.color) : null;
this.iconImage_ = getIconImage(
image,
cacheKey,
this.crossOrigin_,
imageState,
this.color_
);
// 图标的偏移量
this.offset_ = options.offset !== undefined ? options.offset : [0, 0];
// 偏移量的原点
this.offsetOrigin_ =
options.offsetOrigin !== undefined ? options.offsetOrigin : "top-left";
this.origin_ = null;
// 图标的尺寸
this.size_ = options.size !== undefined ? options.size : null;
this.initialOptions_;
// 如果用户同时提供了 width 或 height,则会根据图标的原始尺寸和目标尺寸进行缩放计算
if (options.width !== undefined || options.height !== undefined) {
let width, height;
if (options.size) {
[width, height] = options.size;
} else {
const image = this.getImage(1);
if (image.width && image.height) {
width = image.width;
height = image.height;
} else if (image instanceof HTMLImageElement) {
this.initialOptions_ = options;
const onload = () => {
this.unlistenImageChange(onload);
if (!this.initialOptions_) {
return;
}
const imageSize = this.iconImage_.getSize();
this.setScale(
calculateScale(
imageSize[0],
imageSize[1],
options.width,
options.height
)
);
};
this.listenImageChange(onload);
return;
}
}
if (width !== undefined) {
this.setScale(
calculateScale(width, height, options.width, options.height)
);
}
}
}
clone() {
let scale, width, height;
if (this.initialOptions_) {
width = this.initialOptions_.width;
height = this.initialOptions_.height;
} else {
scale = this.getScale();
scale = Array.isArray(scale) ? scale.slice() : scale;
}
return new Icon({
anchor: this.anchor_.slice(),
anchorOrigin: this.anchorOrigin_,
anchorXUnits: this.anchorXUnits_,
anchorYUnits: this.anchorYUnits_,
color:
this.color_ && this.color_.slice
? this.color_.slice()
: this.color_ || undefined,
crossOrigin: this.crossOrigin_,
offset: this.offset_.slice(),
offsetOrigin: this.offsetOrigin_,
opacity: this.getOpacity(),
rotateWithView: this.getRotateWithView(),
rotation: this.getRotation(),
scale,
width,
height,
size: this.size_ !== null ? this.size_.slice() : undefined,
src: this.getSrc(),
displacement: this.getDisplacement().slice(),
declutterMode: this.getDeclutterMode(),
});
}
getAnchor() {
let anchor = this.normalizedAnchor_;
if (!anchor) {
anchor = this.anchor_;
const size = this.getSize();
if (
this.anchorXUnits_ == "fraction" ||
this.anchorYUnits_ == "fraction"
) {
if (!size) {
return null;
}
anchor = this.anchor_.slice();
if (this.anchorXUnits_ == "fraction") {
anchor[0] *= size[0];
}
if (this.anchorYUnits_ == "fraction") {
anchor[1] *= size[1];
}
}
if (this.anchorOrigin_ != "top-left") {
if (!size) {
return null;
}
if (anchor === this.anchor_) {
anchor = this.anchor_.slice();
}
if (
this.anchorOrigin_ == "top-right" ||
this.anchorOrigin_ == "bottom-right"
) {
anchor[0] = -anchor[0] + size[0];
}
if (
this.anchorOrigin_ == "bottom-left" ||
this.anchorOrigin_ == "bottom-right"
) {
anchor[1] = -anchor[1] + size[1];
}
}
this.normalizedAnchor_ = anchor;
}
const displacement = this.getDisplacement();
const scale = this.getScaleArray();
return [
anchor[0] - displacement[0] / scale[0],
anchor[1] + displacement[1] / scale[1],
];
}
setAnchor(anchor) {
this.anchor_ = anchor;
this.normalizedAnchor_ = null;
}
getColor() {
return this.color_;
}
getImage(pixelRatio) {
return this.iconImage_.getImage(pixelRatio);
}
getPixelRatio(pixelRatio) {
return this.iconImage_.getPixelRatio(pixelRatio);
}
getImageSize() {
return this.iconImage_.getSize();
}
getImageState() {
return this.iconImage_.getImageState();
}
getHitDetectionImage() {
return this.iconImage_.getHitDetectionImage();
}
getOrigin() {
if (this.origin_) {
return this.origin_;
}
let offset = this.offset_;
if (this.offsetOrigin_ != "top-left") {
const size = this.getSize();
const iconImageSize = this.iconImage_.getSize();
if (!size || !iconImageSize) {
return null;
}
offset = offset.slice();
if (
this.offsetOrigin_ == "top-right" ||
this.offsetOrigin_ == "bottom-right"
) {
offset[0] = iconImageSize[0] - size[0] - offset[0];
}
if (
this.offsetOrigin_ == "bottom-left" ||
this.offsetOrigin_ == "bottom-right"
) {
offset[1] = iconImageSize[1] - size[1] - offset[1];
}
}
this.origin_ = offset;
return this.origin_;
}
getSrc() {
return this.iconImage_.getSrc();
}
getSize() {
return !this.size_ ? this.iconImage_.getSize() : this.size_;
}
getWidth() {
const scale = this.getScaleArray();
if (this.size_) {
return this.size_[0] * scale[0];
}
if (this.iconImage_.getImageState() == ImageState.LOADED) {
return this.iconImage_.getSize()[0] * scale[0];
}
return undefined;
}
getHeight() {
const scale = this.getScaleArray();
if (this.size_) {
return this.size_[1] * scale[1];
}
if (this.iconImage_.getImageState() == ImageState.LOADED) {
return this.iconImage_.getSize()[1] * scale[1];
}
return undefined;
}
setScale(scale) {
delete this.initialOptions_;
super.setScale(scale);
}
listenImageChange(listener) {
this.iconImage_.addEventListener(EventType.CHANGE, listener);
}
load() {
this.iconImage_.load();
}
unlistenImageChange(listener) {
this.iconImage_.removeEventListener(EventType.CHANGE, listener);
}
ready() {
return this.iconImage_.ready();
}
}
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# Icon
类的构造函数
Icon
类构造函数的核心作用是初始化 Icon 实例,并根据传入的 options 配置来设置图标的各种属性,如透明度、旋转角度、缩放比例、图像来源等。它还会处理图标图像的加载、偏移、锚点等相关设置。
# Icon
类的主要方法
clone()
方法:复制一个Icon
对象,内部就是实例化一个Icon
类,并返回实例对象。getAnchor()
方法:计算并返回图标的锚点位置,getAnchor
方法首先会检查normalizedAnchor_
属性;若已经计算郭锚点位置,则直接返回该值;否则计算基于尺寸的锚点;计算的依据就是锚点的X
或者Y
单位以及锚点的位置。计算后会保存锚点,以便下次直接使用,避免重复计算;最后会计算位移以及缩放,返回最终的锚点。setAnchor(anchor)
方法:接受一个数组anchor
,并将它赋值给this.anchor
,然后将this.normalizedAnchor
置空null
getColor()
方法:获取this.color_
属性getImage(pixelRatio)
方法:返回与图标相关的图像对象,pixelRatio
参数是像素比率getPixelRatio(pixelRatio)
方法:返回图标图像的像素比率getImageSize()
方法:返回图标图像的尺寸(宽度和高度),形如[宽度,高度]
getImageState()
方法:这个方法返回图像的当前加载状态。(ImageState.IDLE
:图像处于空闲状态,尚未加载、ImageState.LOADING
:图像正在加载中、ImageState.LOADED
:图像加载完成、ImageState.ERROR
:图像加载失败)getHitDetectionImage()
方法:用于点击检测的图像getOrigin()
方法:根据设置的偏移量、原点位置和图标大小来计算并返回图标或标注的“原点”位置。getSrc()
方法:返回图标图片的源 URLgetSize()
方法:获取图标的尺寸getWidth()
方法:获取图标的宽度getHeight()
方法:获取图标的高度setScale(scale)
方法: 设置图标的缩放比例listenImageChange(listener)
方法:监听图标图像的变化事件load()
方法:加载图标图像unlistenImageChange(listener)
方法:移除监听图标图像变化的事件处理函数ready()
方法:检查图标图像是否已准备好
# 总结
Icon
类主要就是管理图标包括其位置、尺寸、颜色、加载状态和缩放等。