原文链接及内容

示例介绍:地图上展示生态区域(ecoregions)数据,并支持通过单击和框选(拖拽框)选择地图上的要素(features),同时显示选中要素的生态名称(ECO_NAME)。

该示例代码官方进行的更新,加入了框选范围超过180度经线的情况的范围处理。

运行界面

此示例演示如何使用DragBox交互组件来选择要素,被选择的要素会被添加到选择交互的要素覆盖层(ol/Interactive/Select)中,用于高亮显示。按住Ctrl键并拖动(在Mac上按住Command键并拖动)可绘制出框。

main.js代码如下:

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
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
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import {platformModifierKeyOnly} from 'ol/events/condition.js';
import {getWidth} from 'ol/extent.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import DragBox from 'ol/interaction/DragBox.js';
import Select from 'ol/interaction/Select.js';
import VectorLayer from 'ol/layer/Vector.js';
import VectorSource from 'ol/source/Vector.js';
import Fill from 'ol/style/Fill.js';
import Stroke from 'ol/style/Stroke.js';
import Style from 'ol/style/Style.js';

const vectorSource = new VectorSource({
url: 'https://openlayers.org/data/vector/ecoregions.json',
format: new GeoJSON(),
});

const style = new Style({
fill: new Fill({
color: '#eeeeee',
}),
});

const map = new Map({
layers: [
new VectorLayer({
source: vectorSource,
background: '#1a2b39',
style: function (feature) {
const color = feature.get('COLOR_BIO') || '#eeeeee';
style.getFill().setColor(color);
return style;
},
}),
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
/**
* constrainRotation:旋转约束。
* 1. false 表示无约束。 true 表示无约束,但在接近零时自动对齐零值。
* 2. 数字类型会将旋转约束为特定数值。例如, 4 将把旋转角度限制为 0、90、180 和 270 度。
* 3. 当 enableRotation 为 false 时, constrainRotation 选项将失效。
*
* 这里将角度限制在360/16 = 22.5的倍数上。
*/
constrainRotation: 16,
}),
});

const selectedStyle = new Style({
fill: new Fill({
color: 'rgba(255, 255, 255, 0.6)',
}),
stroke: new Stroke({
color: 'rgba(255, 255, 255, 0.7)',
width: 2,
}),
});

// a normal select interaction to handle click
// 一个正常用于处理点击的选择交互组件
const select = new Select({
// style:动态样式函数,根据每个特征的 COLOR_BIO 属性设置填充颜色,默认为 #eeeeee
style: function (feature) {
const color = feature.get('COLOR_BIO') || '#eeeeee';
selectedStyle.getFill().setColor(color);
return selectedStyle;
},
});
map.addInteraction(select);

const selectedFeatures = select.getFeatures();

// 通过绘制框来选择要素的DragBox交互组件
const dragBox = new DragBox({
condition: platformModifierKeyOnly,
});

map.addInteraction(dragBox);

dragBox.on('boxend', function () {
const boxExtent = dragBox.getGeometry().getExtent();

// if the extent crosses the antimeridian process each world separately
// 如果范围跨越反经线,即180度经线,则分别单独处理每个“世界”,
//关于180度经线内容,请参考:https://en.wikipedia.org/wiki/180th_meridian
const worldExtent = map.getView().getProjection().getExtent();
const worldWidth = getWidth(worldExtent);
const startWorld = Math.floor((boxExtent[0] - worldExtent[0]) / worldWidth);
const endWorld = Math.floor((boxExtent[2] - worldExtent[0]) / worldWidth);

for (let world = startWorld; world <= endWorld; ++world) {
const left = Math.max(boxExtent[0] - world * worldWidth, worldExtent[0]);
const right = Math.min(boxExtent[2] - world * worldWidth, worldExtent[2]);
const extent = [left, boxExtent[1], right, boxExtent[3]];

const boxFeatures = vectorSource
.getFeaturesInExtent(extent)
.filter(
(feature) =>
!selectedFeatures.getArray().includes(feature) &&
feature.getGeometry().intersectsExtent(extent),
);

/**
* 与方框几何相交的要素将被添加到所选要素集合中,
* 如果(地图)视图没有倾斜旋转,则方框的几何图形与其范围相等,
* 因此可以将与方框相交的要素直接添加到集合中
*/
const rotation = map.getView().getRotation();
//是否能整除90度,能判断地图视图是否是倾斜的。
const oblique = rotation % (Math.PI / 2) !== 0;

/**
* 当(地图)视图倾斜旋转时,边界框范围将超出其几何形状,
* 因此需将边界框和候选要素的几何体围绕共同锚点旋转,
* 以确认在边界框几何体与其范围对齐的情况下,两者几何体是否相交
*/
if (oblique) {
const anchor = [0, 0];
const geometry = dragBox.getGeometry().clone();
geometry.translate(-world * worldWidth, 0);
geometry.rotate(-rotation, anchor);
const extent = geometry.getExtent();
boxFeatures.forEach(function (feature) {
const geometry = feature.getGeometry().clone();
geometry.rotate(-rotation, anchor);
if (geometry.intersectsExtent(extent)) {
selectedFeatures.push(feature);
}
});
} else {
// Collection.extend(arr):向集合中添加元素。这将把提供的数组中的每个项目push到集合的末尾。
selectedFeatures.extend(boxFeatures);
}
}
});

// 在绘制新选框时并点击地图时清除当前选中项
dragBox.on('boxstart', function () {
//Collection.clear(): 清空集合
selectedFeatures.clear();
});

const infoBox = document.getElementById('info');

selectedFeatures.on(['add', 'remove'], function () {
const names = selectedFeatures.getArray().map((feature) => {
return feature.get('ECO_NAME');
});
if (names.length > 0) {
infoBox.innerHTML = names.join(', ');
} else {
infoBox.innerHTML = 'None';
}
});

界面布局文件index.html代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Box Selection</title>
<link rel="stylesheet" href="node_modules/ol/ol.css">
<style>
.map {
width: 100%;
height: 400px;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<div>Selected regions: <span id="info">None</span></div>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<script src="https://cdn.jsdelivr.net/npm/elm-pep@1.0.6/dist/elm-pep.js"></script>
<script type="module" src="main.js"></script>
</body>
</html>

总结

  1. 关于DragBox类的condition属性,这里为:platformModifierKeyOnly,表示我们在用鼠标框选时,需要额外的按住键盘上的按键来辅助,Mac 上是 meta 键,其他系统是 ctrl 键,而其值的所有类型具体可以参考:https://openlayers.org/en/latest/apidoc/module-ol_events_condition.html
  2. constrainRotation:旋转约束。
    • false 表示无约束。 true 表示无约束,但在接近零时自动对齐零值
    • 数字类型会将旋转约束为特定数值。例如, 4 将把旋转角度限制为 0、90、180 和 270 度。
    • enableRotationfalse 时, constrainRotation 选项将失效。
    • 本例是将角度限制在360/16 = 22.5的倍数上。
  3. 其余属性或方法介绍看代码注释即可。