实现一组动画,即根据一组只有起止点坐标的线段,实现点在这些线段上较为平滑的移动,移动速度和平滑程度均可控制。
下面的代码仅作为思路参考,还欠缺很多细节,比如在进行插值计算时,还需要判断经纬度坐标差,选择差值大的作为已知项计算插值,这样会避免一些bug并让计算的插值数据更平滑。还有如何把示例中的圆点改为箭头,并计算箭头的方向与线的走向一致等等一些问题。如果有时间,后期会整理一个更加具体的,可以直接移植使用的demo。
运行结果
代码如下
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>添加箭头动画</title>
<link href="ol/ol.css" rel="stylesheet" type="text/css"/>
<script src="ol/ol.js" type="text/javascript"></script>
<style type="text/css">
#mapCon {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<!-- 地图容器 -->
<div id="mapCon"></div>
<div style="position: absolute;top: 20px;left: 50%;">
运动速度:<input id="speed" type="range" min="1" max="10" step="1" value="5"/>
<button id="start-animation">开始</button>
</div>
<script type="text/javascript">
var key = "4689fc6b9bc0fdc8c48298f751ebfb41";//天地图密钥
//ol.layer.Tile:是一个瓦片图层类,用于显示瓦片资源。
//source是必填项,用于为图层设置来源。
//ol.source.XYZ:
//创建天地图矢量图层
var TiandiMap_vec = new ol.layer.Tile({
title: "天地图矢量图层",
source: new ol.source.XYZ({
url: "http://t{0-7}.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=" + key,
wrapX: false
})
});
//创建天地图矢量注记图层
var TiandiMap_cva = new ol.layer.Tile({
title: "天地图矢量注记图层",
source: new ol.source.XYZ({
url: "http://t{0-7}.tianditu.com/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=" + key,
})
});
//实例化Map对象加载地图
var map = new ol.Map({
//地图容器div的ID
target: 'mapCon',
//地图容器中加载的图层
layers: [TiandiMap_vec, TiandiMap_cva],
//地图视图设置
view: new ol.View({
//地图初始中心点(经纬度)
center: [118.37, 37.14],
//地图初始显示级别
zoom: 12,
projection: "EPSG:4326"
})
});
/**
* 线性插值
* @param start 线段开始点的坐标
* @param end 结束点坐标
* @param step 步长
* @returns {*[]}
*/
function interpolate(start, end, step) {
const x = start[0] + step * (end[0] - start[0]);
let num = Math.ceil(Math.abs(end[0] - start[0]) / step);
let xArr = [];
setp = end[0] > start[0] ? step : 0 - step;
for (let i = 1; i < num; i++) {
xArr.push(start[0] + setp * i);
}
//插入起点
let lonlat = [start];
for (let i = 0; i < xArr.length; i++) {
let y = start[1] + (end[1] - start[1]) / (end[0] - start[0]) * (xArr[i] - start[0]);
lonlat.push([xArr[i], y]);
}
//加入终点
lonlat.push(end);
return lonlat;
}
//构建一组离散化的点(一组线段的起止点)
var Coordinates = [
{
lonlat: [[118.37, 37.14], [118.48, 37.05]]
},
{
lonlat: [[118.46, 37.04], [118.51, 37.06]]
}
];
//遍历线段,并根据起止点,计算插值数据,用于动画
let routes = [];//路径对象数组
for (let i = 0; i < Coordinates.length; i++) {
const item = Coordinates[i];
//获取插值数据
let lonlatArr = interpolate(item.lonlat[0], item.lonlat[1], 0.01);
//将插值数据构建为Line对象
let route = new ol.geom.LineString(lonlatArr);
//获取直线的坐标
let routeCoords = route.getCoordinates();
let routeLength = routeCoords.length;
let routeFeature = new ol.Feature({
type: 'route',
geometry: route
});
let geoMarker = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords[0])
});
routes.push(
{
route: route,
routeCoords: routeCoords,
routeLength: routeLength,
routeFeature: routeFeature,
geoMarker, geoMarker,
index: 0
}
)
}
var styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: "../../images/stationicon.png"
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({color: 'black'}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating = false;
var speed, now;
var speedInput = document.getElementById('speed');
var startButton = document.getElementById('start-animation');
//添加用于展示动画的层
var vectorSource = new ol.source.Vector();
var vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: function (feature) {
//如果动画是激活的就隐藏geoMarker
if (animating && feature.get('type') === 'geoMarker') {
return null;
}
return styles[feature.get('type')];
}
});
map.addLayer(vectorLayer);
for (let i = 0; i < routes.length; i++) {
let item = routes[i];
vectorSource.addFeature(item.routeFeature);
vectorSource.addFeature(item.geoMarker);
}
var moveFeature = function (event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating) {
//通过增加速度,来获得lineString坐标
for (let i = 0; i < routes.length; i++) {
const item = routes[i];
var elapsedTime = frameState.time - item.now;
item.index = Math.round(speed * elapsedTime / 1000);
if (item.index >= item.routeLength) {
//重置时间和各个线段的下标,用于循环
item.now = new Date().getTime();
item.index = 0;
//执行下面两行代码 自动结束动画
//stopAnimation(true);
//return;
}
var currentPoint = new ol.geom.Point(item.routeCoords[item.index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature, styles.geoMarker);
}
}
//继续动画效果
map.render();
};
function startAnimation() {
if (animating) {
stopAnimation(false);
} else {
animating = true;
speed = speedInput.value;
startButton.textContent = '结束运动';
//隐藏geoMarker
for (let i = 0; i < routes.length; i++) {
const item = routes[i];
item.geoMarker.setStyle(null);
item.now = new Date().getTime();
}
//设置显示范围
// map.getView().setCenter(center);
map.on('postcompose', moveFeature);
map.render();
}
}
/**
* @param {boolean}结束动画
*/
function stopAnimation(ended) {
animating = false;
startButton.textContent = '开始运动';
for (let i = 0; i < routes.length; i++) {
const item = routes[i];
//如果动画取消就开始动画
var coord = ended ? item.routeCoords[item.routeLength - 1] : item.routeCoords[0];
//将各个线段上的点重新定位到起始位置
(item.geoMarker.getGeometry()).setCoordinates(coord);
}
//移除监听
map.un('postcompose', moveFeature);
}
startButton.addEventListener('click', startAnimation, false);
</script>
</body>
</html>