原文链接及内容

运行界面

此示例解析一个 KML 文件,并将要素以聚类形式渲染到矢量图层上。本示例的样式设计较为复杂。单个地震点(渲染为五角星)的大小与其震级相关。聚类点的透明度与聚类中包含的要素数量相关,其大小反映了组成聚类的要素的地理范围。当点击或悬停在聚类点上时,会显示组成该聚类的单个要素。

为实现这一效果,我们广泛使用了样式函数。

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import KML from 'ol/format/KML.js';
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import {
Circle as CircleStyle,
Fill,
RegularShape,
Stroke,
Style,
Text,
} from 'ol/style.js';
import {Cluster, Stamen, Vector as VectorSource} from 'ol/source.js';
import {
Select,
defaults as defaultInteractions,
} from 'ol/interaction.js';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer.js';
import {createEmpty, extend, getHeight, getWidth} from 'ol/extent.js';

const earthquakeFill = new Fill({
color: 'rgba(255, 153, 0, 0.8)',
});
const earthquakeStroke = new Stroke({
color: 'rgba(255, 204, 0, 0.2)',
width: 1,
});
const textFill = new Fill({
color: '#fff',
});
const textStroke = new Stroke({
color: 'rgba(0, 0, 0, 0.6)',
width: 3,
});
const invisibleFill = new Fill({
color: 'rgba(255, 255, 255, 0.01)',
});

/**
* 为单个地震点生成五角星样式,基于震级调整大小。
*/
function createEarthquakeStyle(feature) {
/**
* 2012_Earthquakes_Mag5.kml 在每个 Placemark 中的非标准<magnitude>标签中存储了每次地震的震级。
* 我们从 Placemark 的名称中提取这些信息。
*/
const name = feature.get('name');
// magnitude是震级是意思
const magnitude = parseFloat(name.substr(2));
//根据震级确定圆的半径
const radius = 5 + 20 * (magnitude - 5);

return new Style({
geometry: feature.getGeometry(),
image: new RegularShape({
// 当提供 radius 时将生成正多边形,同时提供 radius 和 radius2 时则生成星形。
radius: radius,
radius1: 3,
points: 5,
angle: Math.PI,
fill: earthquakeFill,
stroke: earthquakeStroke,
}),
});
}

let maxFeatureCount;
let vector = null;
/**
* 计算聚类点的半径和最大点要素数量,基于地图分辨率。
*/
const calculateClusterInfo = function (resolution) {
maxFeatureCount = 0;
const features = vector.getSource().getFeatures();
let feature, radius;
for (let i = features.length - 1; i >= 0; --i) {
feature = features[i];
const originalFeatures = feature.get('features');
const extent = createEmpty();
let j, jj;
for (j = 0, jj = originalFeatures.length; j < jj; ++j) {
extend(extent, originalFeatures[j].getGeometry().getExtent());
}
maxFeatureCount = Math.max(maxFeatureCount, jj);
radius = (0.25 * (getWidth(extent) + getHeight(extent))) / resolution;
feature.set('radius', radius);
}
};

let currentResolution;
/**
* 为聚类点或单个点生成样式,动态调整透明度和半径。
*/
function styleFunction(feature, resolution) {
if (resolution != currentResolution) {
calculateClusterInfo(resolution);
currentResolution = resolution;
}
let style;
const size = feature.get('features').length;
if (size > 1) {
style = new Style({
image: new CircleStyle({
radius: feature.get('radius'),
fill: new Fill({
color: [255, 153, 0, Math.min(0.8, 0.4 + size / maxFeatureCount)],
}),
}),
text: new Text({
text: size.toString(),
fill: textFill,
stroke: textStroke,
}),
});
} else {
const originalFeature = feature.get('features')[0];
style = createEarthquakeStyle(originalFeature);
}
return style;
}

/**
* 为选中(悬停或点击)的点生成样式,显示所有原始点。
*/
function selectStyleFunction(feature) {
const styles = [
new Style({
image: new CircleStyle({
radius: feature.get('radius'),
fill: invisibleFill,
}),
}),
];
const originalFeatures = feature.get('features');
let originalFeature;
for (let i = originalFeatures.length - 1; i >= 0; --i) {
originalFeature = originalFeatures[i];
styles.push(createEarthquakeStyle(originalFeature));
}
return styles;
}

vector = new VectorLayer({
source: new Cluster({
distance: 40,
source: new VectorSource({
url: 'data/kml/2012_Earthquakes_Mag5.kml',
format: new KML({
extractStyles: false,//忽略 KML 文件中的样式
}),
}),
}),
style: styleFunction,
});

const raster = new TileLayer({
source: new Stamen({
layer: 'toner',
}),
});

const map = new Map({
layers: [raster, vector],
interactions: defaultInteractions().extend([
new Select({
condition: function (evt) {
return evt.type == 'pointermove' || evt.type == 'singleclick';
},
style: selectStyleFunction,
}),
]),
target: 'map',
view: new View({
center: [0, 0],
zoom: 2,
}),
});

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Earthquake Clusters</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>
<!-- 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>