最近做项目遇到一个需求,要显示采集的用户的行动轨迹,类似导航软件的规划路径,有起点终点,路径上通过箭头指向到终点位置。前端通过openlayers实现。
具体的实现思路:
- 将路径坐标数据组装为MultiLineString
- 通过Openlayers的styleFunction为线段添加轨迹的效果
- 添加起点,终点的图标
- 添加中间路径的箭头
下面是具体代码:
创建路径要素
const locationList = new Array()
// 添加所有的坐标数据到坐标列表中 如: locationList.push({logitude:113.233,latitude21.4333})
// 创建多线段
const multiLineString = new MultiLineString([])
// 依次添加坐标点,构成路线
for (let i=0;i<locationList.length-1;i++){
const p1 = [locationList[i].longitude,locationList[i].latitude]
const p2 = [locationList[i+1].longitude,locationList[i+1].latitude]
const lineCoords = [p1,p2]
const lineString = new LineString(lineCoords)
multiLineString.appendLineString(lineString)
}
//创建要素
const f = new Feature(multiLineString)
// 设置样式
f.setStyle(travelPathStyleFunction(map.getView().getProjection().getMetersPerUnit()))
//添加要素 到图层中
layer.getSource().addFeature(f)
路径的styleFunction,可以设定轨迹颜色,轨迹宽度,箭头密度,箭头颜色,箭头大小等参数动态获取styleFunction,满足不同要求。
/**
* 轨迹样式方法
* @param {number} [metersPerUnit] - 地图每单位代表的米数
* @param {string} [pathColor] - 轨迹颜色
* @param {number } [pathWidth] - 轨迹宽度
* @param {number} [arrowDensity] - 箭头密度。间隔多少厘米显示一个箭头
* @param {string} [arrowColor] - 箭头颜色
* @param {number} [arrowImgSize] - 箭头图片的大小
* @param {number} [arrowWidth] - 箭头线宽大小
* */
export function travelPathStyleFunction(metersPerUnit=1,pathColor=DEFAULT_PATH_COLOR,pathWidth=DEFAULT_PATH_WIDTH,arrowDensity = DEFAULT_PATH_ARROW_DENSITY,
arrowColor=DEFAULT_ARROW_COLOR,arrowImgSize=DEFAULT_ARROW_IMG_SIZE,arrowWidth=DEFAULT_ARROW_WIDTH) {
//画向右的箭头。水平向右则旋转的角度刚好为0,方便箭头旋转时的角度计算
const arrowCanvas = document.createElement('canvas')
arrowCanvas.width = arrowImgSize
arrowCanvas.height = arrowImgSize
const ctx = arrowCanvas.getContext('2d')
ctx.strokeStyle = arrowColor
ctx.lineWidth = arrowWidth;
// 箭头底边
ctx.moveTo(0, 0);
ctx.lineTo(arrowImgSize * 0.7, arrowImgSize * 0.5);
ctx.lineTo(0, arrowImgSize);
ctx.stroke();
// 返回真实的 styleFunction
return (feature,resolution)=>{
const styleArr = [
new Style({
fill:new Fill({
color:pathColor,
}),
stroke:new Stroke({
color:pathColor,
width:pathWidth
})
})
]
//resolution 代表每像素的单位距离
//由轨迹的指向标,每个的角度不同,需要单独生成
const lines = feature.getGeometry().getLineStrings()
//每英寸像素值
const dpi = window.devicePixelRatio * 96;
//1英寸 = 2.54 厘米。1厘米=0.39370079 英寸
// metersPerUnit * resolution 即为每像素代表多少米 * dpi 代表每英寸多少米 /2.54 代表屏幕每厘米多少米
const arrowDistance = arrowDensity * (resolution * metersPerUnit * dpi / 2.54)
let lastLineRetainLen = 0 //上条线结余长度。每个线都会判断是否添加箭头,最后肯定有一小部分长度是在上一个箭头之后。下条线判断时加上此长度,才能保证箭头按设置的长度均匀分布
for (let line of lines){
let lineLen = line.getLength() * metersPerUnit
if (lineLen + lastLineRetainLen>=arrowDistance) {
let coords = line.getCoordinates();
let start = coords[0];
let end = coords[1];
let dx = end[0] - start[0];
let dy = end[1] - start[1];
let rotation = Math.atan2(dy, dx);
let offset = 0;
let tempLen = lastLineRetainLen + lineLen;
while (tempLen > arrowDistance) {
let curLen = arrowDistance - lastLineRetainLen;
let rate = (curLen + offset) / lineLen;
let ddx = dx * rate;
let ddy = dy * rate;
let arrowCoord = [start[0] + ddx, start[1] + ddy];
//箭头坐标
styleArr.push(new Style({
geometry: new Point(arrowCoord),
image:new Icon({
img: arrowCanvas,
crossOrigin: 'anonymous',
imgSize:[arrowImgSize,arrowImgSize],
anchor: [0.75, 0.5],
rotateWithView: true,
rotation: -rotation
})
}));
lastLineRetainLen = 0;
offset = offset + curLen;
tempLen = tempLen - arrowDistance;
}
lastLineRetainLen = lineLen - offset
}else{
lastLineRetainLen += lineLen
}
}
//添加起始终止点标记
styleArr.push(
new Style({
image:new Icon({src:startPointImage}),
geometry:new Point(feature.getGeometry().getFirstCoordinate())
})
)
styleArr.push(
new Style({
image:new Icon({src:endPointImage}),
geometry:new Point(feature.getGeometry().getLastCoordinate())
})
)
return styleArr
}
}
最终效果图: