实现一组动画,即根据一组只有起止点坐标的线段,实现点在这些线段上较为平滑的移动,移动速度和平滑程度均可控制。
下面的代码仅作为思路参考,还欠缺很多细节,比如在进行插值计算时,还需要判断经纬度坐标差,选择差值大的作为已知项计算插值,这样会避免一些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的IDtarget: '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) {//如果动画是激活的就隐藏geoMarkerif (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 = '结束运动';//隐藏geoMarkerfor (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>