0%

OpenLayers6中对feature的点击事件

在最近的业务中,需要对 OpenLayers 地图上的 feature 要素点击后触发某些操作,任务完成过程中走过了一些坑,总结一下。

前置代码

放置一个实例更容易看明白过程,从官网的实例Hit Tolerance中截取了一段代码。
首先准备了一个 OSM 的地图底图,再写一个线段 feature 用于触发事件,最后在最上方放置一个 idstatus 的 span 用于展示点击结果。

1
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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/css/ol.css"
type="text/css"
/>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/build/ol.js"></script>
<title>feature点击事件</title>
<style>
html,
body,
.map {
height: 100%;
width: 100%;
}
</style>
</head>

<body>
<span id="status">没有要素被点中</span>

<div id="map" class="map"></div>

<script>
let statusElement = document.getElementById('status');

let raster = new ol.layer.Tile({
source: new ol.source.OSM()
});

let style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'black',
width: 1
})
});

let feature = new ol.Feature(
new ol.geom.LineString([
[-4000000, 0],
[4000000, 0]
])
);

let vector = new ol.layer.Vector({
source: new ol.source.Vector({
features: [feature]
}),
style: style
});

let map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
</script>
</body>
</html>

on(‘click’)

按照一个前端的想法,最直接的触发一个点击事件的方式就是给 feature 添加一个 on('click')事件,查询一下 ol.featureAPI

的确有一个 on 方法,也是用来监听事件的,但是试验了一下,on("click")是不生效的,往上翻 API,发现还有几行:

只有 Fires 中的的事件类型才可以生效,吃了英语差的亏,浪费了时间,猜测原因应该是 OpenLayers 生成的时候直接整体在一个 Canvas 中渲染,内部 layers 相互重叠,并不能判断出用户点击的是哪一层。所以 on("click")不行,还得看其他办法。

ol.interaction.Select

第二个直观的想法是 ol 中自带有交互,是否可以从 ol.interaction 挑一种交互类型来实现触发点击。浏览后,发现select 交互的 API 如下:

toggleCondition 中可设置一个回调函数,函数中添加要做的事情,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let map = new ol.Map({
interactions: ol.interaction.defaults().extend([
new ol.interaction.Select({
// 点击误差20px
hitTolerance: 20,
toggleCondition: () => {
statusElement.innerHTML = ' 一个要素被选中了!';
return false;
}
})
]),
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});

代码中,触发 select 交互时,把 span 标签中的内容修改为了“一个要素被选中了!”。

forEachFeatureAtPixel

虽然使用 interaction 可以实现目的,但是 ol.interaction 会自动新添加一个交互托管层,并且带有默认的交互样式,仅仅为了触发一个点击事件就去使用 ol.interaction 实在是太重了。

ol.mapAPI 中有一个 forEachFeatureAtPixel(pixel, callback, opt_options)的方法:

在这个方法中,传入一个像素位置,返回给该像素位置对应的 feature 要素,并且还可以区分 layer,弥补了不能直接给 feature 添加点击事件的缺陷。同时配合 map.on("singleclick"),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
map.on('singleclick', e => {
style.getStroke().setColor('black');
statusElement.innerHTML = ' 没有要素被选中.';
map.forEachFeatureAtPixel(
e.pixel,
() => {
style.getStroke().setColor('green');
statusElement.innerHTML = ' 一个要素被选中了!';
},
{
hitTolerance: 20
}
);
feature.changed();
});

代码中首先使用 map.on("singleclick")触发一个 map 的点击事件,回调参数为 e,再使用 forEachFeatureAtPixel 配合 e.pixel 读取该像素下的要素,如果有就把颜色改为绿色,并把内容改为“一个要素被选中了!”,再使用 changed()方法派发一个 change 事件,触发修改后内容的渲染,最后结果如下:

👆 全文结束,棒槌时间到 👇