meface/docs/article/gis/openlayers/trackline.md

5.3 KiB

title date author tags categories
Openlayers轨迹播放 2020-12-25 ac
轨迹
GIS

轨迹播放

tracking

轨迹播放是非常常见的需求,像旅游路线、徒步轨迹或项目中的巡检人员的巡检轨迹的复盘等,可以回顾户外运动的轨迹,分析和规划最优的行进路线。

实现思路

轨迹通常是收集移动端发送回来的点集,将点集组织成线,渲染到地图上;

  • 获取轨迹数据,可以是线也可以是点集,将其渲染到地图上;

  • 创建移动点图层,将轨迹中的第一个点作为点要素的geometry

  • 通过设置步长对点集进行插值,得到轨迹播放的轨迹点集合;

  • 设置总时间,使用定时器setTimeout,在每个点的平均时间内,不断地改变移动点的geometry对象(将轨迹点集合中的点,设置到要素中)

我们先在http://geojson.io/上绘制要使用的轨迹点,这里我们使用的是线要素LineString

image-20201224114043053

初始化地图,将轨迹数据展示在地图上。如果是点集也可以在获取到数据后组织成线要素进行展示。

 var map = new ol.Map({
     target: "map",
     layers:[
         new ol.layer.Tile({
             source: new ol.source.OSM()
         })
     ],
     view: new ol.View({
         // center: ol.proj.fromLonLat([113,23]),
         center: [113.9537, 22.5024],
         zoom: 14,
         projection:"EPSG:4326"
     })
 });

//获取轨迹数据并展示在地图上
var trackSource = new ol.source.Vector();
var geojsonFormat = new ol.format.GeoJSON();
fetch("data/geojson/trackData.json").then(function(response){
    return response.json();
}).then(function(result){
    var features = geojsonFormat.readFeatures(result);
    trackSource.addFeatures(features);
    //showCarLayer(trackSource);//获取轨迹数据后展示小车移动效果
});
var trackLine = new ol.layer.Vector({
    source: trackSource,
    style: new ol.style.Style({
        stroke: new ol.style.Stroke({
            color: "#0096c7",
            width: 5
        })
    })
});
map.addLayer(trackLine);

在获取到轨迹数据后,就可以对其进行插值,增加移动点图层,设置图标样式。

var distance = 5;//步长
var totalTime = 60*1000; //总时间
var srcUri = "./img/car.png";
function showCarLayer(source){
    //设置汽车图标样式
    var carStyle = new ol.style.Style({
        image: new ol.style.Icon({
            src: srcUri,
            scale: 0.2,
            offset: [-18,40]
        })
    });

    //获取轨迹线段的所有坐标点数组
    var coordinates = source.getFeatures()[0].getGeometry().getCoordinates();
    var firstPoint = new ol.geom.Point(coordinates[0]);
    var focusFeature = new ol.Feature(firstPoint);
    focusFeature.setStyle(carStyle);
    var carLayer = new ol.layer.Vector({
        source: new ol.source.Vector({
            features:[focusFeature]
        })
    });
    map.addLayer(carLayer);

    //计算轨迹点点集
    var pointArr = [];
    pointArr.push(coordinates[0]);
    for(var i=1;i<coordinates.length;i++){
        var pointArrLast = pointArr[pointArr.length - 1];
        var dd = calcDistance(pointArrLast,coordinates[i]);
        if(dd > distance){
            var ratio = distance/dd;
            var endX = pointArrLast[0] + ratio*(coordinates[i][0] - pointArrLast[0]);
            var endY = pointArrLast[1] + ratio*(coordinates[i][1] - pointArrLast[1]);
            pointArr.push([endX,endY]);
            i--;
        }else{
            pointArr.push(coordinates[i]);
        }
    }

    //展示小车移动
    var stopTime = totalTime/pointArr.length;
    for(let j=1;j<pointArr.length-1;j++){
        setTimeout(function () {
            let focusPoint = new ol.geom.Point(pointArr[j]);
            let angle = calcAngle(pointArr[j - 1],pointArr[j])
            focusFeature.setGeometry(focusPoint);
            focusFeature.getStyle().getImage().setRotation(angle)
        },stopTime*j);
    }
}

其中的calcDistancecalcAngle是用来计算移动点间的距离和角度的。

function calcDistance(preCoor,postCoor){
     var projPreCoor = ol.proj.fromLonLat(preCoor);
     var projPostCoor = ol.proj.fromLonLat(postCoor);
     return Math.sqrt((projPostCoor[0]-projPreCoor[0])*(projPostCoor[0]-projPreCoor[0])+
                      (projPostCoor[1]-projPreCoor[1])*(projPostCoor[1]-projPreCoor[1]));
 }

/**
 * 计算小车旋转的角度,单位是弧度
 * @param preCoor
 * @param postCoor
 * @returns {number}
 */
function calcAngle(preCoor,postCoor) {
    var startx = preCoor[0],
        starty = preCoor[1],
        endx = postCoor[0],
        endy = postCoor[1];
    var tan = 0;
    if (endx === startx) {
        // tan = Math.atan(0) * 180 / Math.PI
        tan = Math.atan(0) ;
    } else {
        tan = Math.atan(Math.abs((endy - starty) / (endx - startx))) ;
    }

    if (endx >= startx && endy >= starty)//第一象限
    {
        return -tan;
    } else if (endx > startx && endy < starty)//第四象限
    {
        return tan;
    } else if (endx < startx && endy > starty)//第二象限
    {
        return tan - Math.PI;
    } else {
        return Math.PI - tan;  //第三象限
    }
}