# 五.动画效果
# 1.标记动画
查看代码详情
<template>
<div>
<div ref="map" class="map"></div>
<label for="speed">
speed:
<input id="speed" type="range" min="10" max="999" step="10" value="60" />
</label>
<button id="start-animation">开始</button>
</div>
</template>
<script>
export default {
async mounted() {
let {
Feature,
geom: { Point },
format: { Polyline },
Map,
View,
layer: { Tile: TileLayer, Vector: VectorLayer },
source: { XYZ, Vector: VectorSource },
style: { Circle: CircleStyle, Fill, Icon, Stroke, Style },
render: { getVectorContext },
} = ol;
const map = new Map({
target: this.$refs.map,
view: new View({
center: [-5639523.95, -3501274.52],
zoom: 10,
minZoom: 2,
maxZoom: 19,
}),
layers: [
new TileLayer({
source: new XYZ({
attributions: "",
url: "http://map.geoq.cn/ArcGIS/rest/services/ChinaOnlineCommunityENG/MapServer/tile/{z}/{y}/{x}",
tileSize: 512,
}),
}),
],
});
let res = await this.$axios.get(
this.$withBase("/data/polyline/route.json")
);
const polyline = res.data.routes[0].geometry;
const route = new Polyline({
factor: 1e6,
}).readGeometry(polyline, {
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857",
});
const routeFeature = new Feature({
type: "route",
geometry: route,
});
const startMarker = new Feature({
type: "icon",
geometry: new Point(route.getFirstCoordinate()),
});
const endMarker = new Feature({
type: "icon",
geometry: new Point(route.getLastCoordinate()),
});
const position = startMarker.getGeometry().clone();
const geoMarker = new Feature({
type: "geoMarker",
geometry: position,
});
const styles = {
route: new Style({
stroke: new Stroke({
width: 6,
color: [237, 212, 0, 0.8],
}),
}),
icon: new Style({
image: new Icon({
anchor: [0.5, 1],
src: this.$withBase("/data/icon.png"),
}),
}),
geoMarker: new Style({
image: new CircleStyle({
radius: 7,
fill: new Fill({ color: "black" }),
stroke: new Stroke({
color: "white",
width: 2,
}),
}),
}),
};
const vectorLayer = new VectorLayer({
source: new VectorSource({
features: [routeFeature, geoMarker, startMarker, endMarker],
}),
style: function (feature) {
return styles[feature.get("type")];
},
});
map.addLayer(vectorLayer);
const speedInput = document.getElementById("speed");
const startButton = document.getElementById("start-animation");
let animating = false;
let distance = 0;
let lastTime;
function moveFeature(event) {
const speed = Number(speedInput.value);
const time = event.frameState.time;
const elapsedTime = time - lastTime;
distance = (distance + (speed * elapsedTime) / 1e6) % 2;
lastTime = time;
const currentCoordinate = route.getCoordinateAt(
distance > 1 ? 2 - distance : distance
);
position.setCoordinates(currentCoordinate);
const vectorContext = getVectorContext(event);
vectorContext.setStyle(styles.geoMarker);
vectorContext.drawGeometry(position);
map.render();
}
function startAnimation() {
animating = true;
lastTime = Date.now();
startButton.textContent = "停止动画";
vectorLayer.on("postrender", moveFeature);
geoMarker.setGeometry(null);
}
function stopAnimation() {
animating = false;
startButton.textContent = "开始动画";
geoMarker.setGeometry(position);
vectorLayer.un("postrender", moveFeature);
}
startButton.addEventListener("click", function () {
if (animating) {
stopAnimation();
} else {
startAnimation();
}
});
},
};
</script>
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
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
# 2.飞行动画
如何使用 postrender 和 vectorContext 为航班设置动画。使用 arc.js 计算两个机场之间的大圆弧,然后使用 postrender 对飞行路径进行动画处理
查看代码详情
<template>
<div ref="map" class="map"></div>
</template>
<script>
export default {
mounted() {
let {
Feature,
geom: { LineString },
Map,
View,
layer: { Tile: TileLayer, Vector: VectorLayer },
source: { Stamen, Vector: VectorSource },
style: { Stroke, Style },
render: { getVectorContext },
extent: { getWidth },
} = ol;
const tileLayer = new TileLayer({
source: new Stamen({
layer: "toner",
}),
});
const map = new Map({
layers: [tileLayer],
target: this.$refs.map,
view: new View({
center: [-11000000, 4600000],
zoom: 2,
}),
});
const style = new Style({
stroke: new Stroke({
color: "#EAE911",
width: 2,
}),
});
const flightsSource = new VectorSource({
attributions:
"Flight data by " +
'<a href="https://openflights.org/data.html">OpenFlights</a>,',
loader: async function () {
const url = this.$withBase("/data/openflights/flights.json");
let res = await this.$axios.get(
this.$withBase("/data/openflights/flights.json")
);
const flightsData = res.data.flights;
for (let i = 0; i < flightsData.length; i++) {
const flight = flightsData[i];
const from = flight[0];
const to = flight[1];
const arcGenerator = new arc.GreatCircle(
{ x: from[1], y: from[0] },
{ x: to[1], y: to[0] }
);
const arcLine = arcGenerator.Arc(100, { offset: 10 });
const features = [];
arcLine.geometries.forEach(function (geometry) {
const line = new LineString(geometry.coords);
line.transform("EPSG:4326", "EPSG:3857");
features.push(
new Feature({
geometry: line,
finished: false,
})
);
});
addLater(features, i * 50);
}
tileLayer.on("postrender", animateFlights);
}.bind(this),
});
const flightsLayer = new VectorLayer({
source: flightsSource,
style: function (feature) {
if (feature.get("finished")) {
return style;
} else {
return null;
}
},
});
map.addLayer(flightsLayer);
const pointsPerMs = 0.02;
function animateFlights(event) {
const vectorContext = getVectorContext(event);
const frameState = event.frameState;
vectorContext.setStyle(style);
const features = flightsSource.getFeatures();
for (let i = 0; i < features.length; i++) {
const feature = features[i];
if (!feature.get("finished")) {
const coords = feature.getGeometry().getCoordinates();
const elapsedTime = frameState.time - feature.get("start");
if (elapsedTime >= 0) {
const elapsedPoints = elapsedTime * pointsPerMs;
if (elapsedPoints >= coords.length) {
feature.set("finished", true);
}
const maxIndex = Math.min(elapsedPoints, coords.length);
const currentLine = new LineString(coords.slice(0, maxIndex));
const worldWidth = getWidth(
map.getView().getProjection().getExtent()
);
const offset = Math.floor(
map.getView().getCenter()[0] / worldWidth
);
currentLine.translate(offset * worldWidth, 0);
vectorContext.drawGeometry(currentLine);
currentLine.translate(worldWidth, 0);
vectorContext.drawGeometry(currentLine);
}
}
}
map.render();
}
function addLater(features, timeout) {
window.setTimeout(function () {
let start = Date.now();
features.forEach(function (feature) {
feature.set("start", start);
flightsSource.addFeature(feature);
const duration =
(feature.getGeometry().getCoordinates().length - 1) / pointsPerMs;
start += duration;
});
}, timeout);
}
},
};
</script>
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
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
# 3.自定义动画
此示例展示了如何使用 postrender 和 vectorContext 为功能添加动画效果。在这里,每当将功能添加到图层时,我们都会选择做一个闪光动画
查看代码详情
<template>
<div ref="map" class="map"></div>
</template>
<script>
export default {
mounted() {
let {
Feature,
Map,
geom: { Point },
proj: { fromLonLat },
easing: { easeOut },
render: { getVectorContext },
Observable: unByKey,
View,
layer: { Tile: TileLayer, Vector: VectorLayer },
source: { OSM, Vector: VectorSource },
style: { Circle: CircleStyle, Stroke, Style },
} = ol;
const tileLayer = new TileLayer({
source: new OSM({
wrapX: false,
}),
});
const source = new VectorSource({
wrapX: false,
});
const vector = new VectorLayer({
source: source,
});
const map = new Map({
layers: [tileLayer, vector],
target: this.$refs.map,
view: new View({
center: [12579156, 3274244],
zoom: 1,
multiWorld: true,
}),
});
function addRandomFeature() {
const x = Math.random() * 360 - 180;
const y = Math.random() * 170 - 85;
const geom = new Point(fromLonLat([x, y]));
const feature = new Feature(geom);
source.addFeature(feature);
}
const duration = 3000;
function flash(feature) {
const start = Date.now();
const flashGeom = feature.getGeometry().clone();
const listenerKey = tileLayer.on("postrender", animate);
function animate(event) {
const frameState = event.frameState;
const elapsed = frameState.time - start;
if (elapsed >= duration) {
unByKey(listenerKey);
return;
}
const vectorContext = getVectorContext(event);
const elapsedRatio = elapsed / duration;
const radius = easeOut(elapsedRatio) * 25 + 5;
const opacity = easeOut(1 - elapsedRatio);
const style = new Style({
image: new CircleStyle({
radius: radius,
stroke: new Stroke({
color: "rgba(255, 0, 0, " + opacity + ")",
width: 0.25 + opacity,
}),
}),
});
vectorContext.setStyle(style);
vectorContext.drawGeometry(flashGeom);
map.render();
}
}
source.on("addfeature", function (e) {
flash(e.feature);
});
window.setInterval(addRandomFeature, 1000);
},
};
</script>
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
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