使用百度地图api实现的渐变色轨迹线,效果如下:
实现方法:
1.百度地图api使用
首先引入百度地图api的依赖文件
<script type="text/javascript" src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=1XjLLEhZhQNUzd93EjU5nOGQ"></script>
创建地图的实例:
var SMap = new BMap.Map("map", {
enableMapClick: false //禁用点击事件
});
SMap.centerAndZoom(new BMap.Point(112.9353, 29.6952), 8); // 初始化地图,设置中心点坐标和地图级别
SMap.enableScrollWheelZoom(); //启用滚轮放大缩小
SMap.setMapStyle({
styleJson: [{
"featureType": "road",
"elementType": "all",
"stylers": {
"color": "#ffffff",
"visibility": "off"
}
}]
});
2.数据准备
需要在地图上打点的坐标数据(点连成轨迹),数据太长只展示部分,数据的第三项为权值:
let pos = [
[106.596514, 29.574914, 70],
[106.632446, 29.620136, 70],
[106.686345, 29.569259, 70],
[106.745705, 29.594136, 70],
[106.805208, 29.598407, 17],
[106.850483, 29.583457, 17],
[106.850483, 29.583457, 17]
]
百度地图api不支持列表形式的坐标点,因此需要转换成point格式的数据:
let totalPoints = [];
//将坐标点转化成point格式
for (let i = 0; i < pos.length; i++) {
let bp = new BMap.Point(pos[i][0], pos[i][1]);
totalPoints.push(bp);
}
3.绘制轨迹
绘制这种有一定宽度的轨迹线的原理大致就是在百度地图的图层之上再添加一个canvas图层然后在绘制矩形填充颜色边框箭头之类的。
添加图层的api需要依赖一个第三方代码,下载链接在文章底部:
<script type="text/javascript" src="../src/CanvasLayer.js"></script>
绘制渐变颜色轨迹:
//**********渐变颜色轨迹*************
this.canvasLayer = new this.CanvasLayer({
map: this.SMap,
update,
});
function update() {
const ctx = self.canvasLayer.canvas.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (totalPoints.length !== 0) { // 绘制带速度颜色的轨迹
for (let i = 0, len = totalPoints.length; i < len - 2; i += 1) {
const pixel = self.SMap.pointToPixel(totalPoints[i]);
///
const nextPixel = self.SMap.pointToPixel(totalPoints[i + 1]);
ctx.beginPath();
ctx.moveTo(pixel.x, pixel.y);
ctx.lineCap = 'round';
ctx.lineWidth = FLOODLINEWIDTH;
const grd = ctx.createLinearGradient(pixel.x, pixel.y, nextPixel.x,
nextPixel.y);
const nowValue = pos[i][2]
const nextValue = pos[i + 1][2]
grd.addColorStop(0, self.getColorByValue(nowValue));
grd.addColorStop(1, self.getColorByValue(nextValue));
ctx.strokeStyle = grd;
ctx.lineTo(nextPixel.x, nextPixel.y);
ctx.stroke();
}
}
}
根据权值转换成颜色的函数:
//将值转成十六进制颜色
//0,180,0 --> 200, 200, 0 --> 200, 0, 0
//value 0-100
function getColorByValue(value) {
let r = g = b = 0
if (value <= 50) {
g = 200
r = parseInt(((value / 100) * 200))
} else {
r = 200
g = parseInt(((100 - value) / 100) * 200)
}
let t1 = r.toString(16)
t1 = t1.length == 1 ? ("0" + t1) : t1
let t2 = g.toString(16)
t2 = t2.length == 1 ? ("0" + t2) : t2
let t3 = b.toString(16)
t3 = t3.length == 1 ? ("0" + t3) : t3
return '#' + t1 + t2 + t3
}
添加轨迹箭头:
//**********轨迹箭头*************
this.canvasLayerPointer = new this.CanvasLayer({
map: this.SMap,
update: updatePointer,
});
// 箭头
function updatePointer() {
const ctx = self.canvasLayerPointer.canvas.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (totalPoints.length !== 0) {
const lineObj = {};
let pixelPart = 0;
const pixelPartUnit = 40;
for (let i = 0, len = totalPoints.length; i < len - 1; i += 1) {
const pixel = self.SMap.pointToPixel(totalPoints[i]);
const nextPixel = self.SMap.pointToPixel(totalPoints[i + 1]);
pixelPart += (((nextPixel.x - pixel.x) ** 2) + ((nextPixel.y - pixel.y) **
2)) ** 0.5;
if (pixelPart <= pixelPartUnit) {
// continue;
}
pixelPart = 0;
ctx.beginPath();
// 根据渲染像素距离渲染箭头
if (Math.abs(nextPixel.x - pixel.x) > 10 || Math.abs(nextPixel.y - pixel
.y) > 10) {
// 箭头一共需要5个点:起点、终点、中心点、箭头端点1、箭头端点2
const midPixel = new self.BMap.Pixel(
(pixel.x + nextPixel.x) / 2,
(pixel.y + nextPixel.y) / 2,
);
// 起点终点距离
const distance = (((nextPixel.x - pixel.x) ** 2) +
((nextPixel.y - pixel.y) ** 2)) ** 0.5;
// 箭头长度
const pointerLong = 4;
const aPixel = {};
const bPixel = {};
if (nextPixel.x - pixel.x === 0) {
if (nextPixel.y - pixel.y > 0) {
aPixel.x = midPixel.x - (pointerLong * (0.5 ** 0.5));
aPixel.y = midPixel.y - (pointerLong * (0.5 ** 0.5));
bPixel.x = midPixel.x + (pointerLong * (0.5 ** 0.5));
bPixel.y = midPixel.y - (pointerLong * (0.5 ** 0.5));
} else if (nextPixel.y - pixel.y < 0) {
aPixel.x = midPixel.x - (pointerLong * (0.5 ** 0.5));
aPixel.y = midPixel.y + (pointerLong * (0.5 ** 0.5));
bPixel.x = midPixel.x + (pointerLong * (0.5 ** 0.5));
bPixel.y = midPixel.y + (pointerLong * (0.5 ** 0.5));
} else {
// continue;
}
} else {
const k0 = (
(
(-(2 ** 0.5) * distance * pointerLong) +
(2 * (nextPixel.y - pixel.y) * midPixel.y)
) / (2 * (nextPixel.x - pixel.x))) + midPixel.x;
const k1 = -((nextPixel.y - pixel.y) / (nextPixel.x - pixel.x));
const a = (k1 ** 2) + 1;
const b = (2 * k1 * (k0 - midPixel.x)) - (2 * midPixel.y);
const c = (((k0 - midPixel.x) ** 2) + (midPixel.y ** 2)) - (
pointerLong ** 2);
aPixel.y = (-b + (((b * b) - (4 * a * c)) ** 0.5)) / (2 * a);
bPixel.y = (-b - (((b * b) - (4 * a * c)) ** 0.5)) / (2 * a);
aPixel.x = (k1 * aPixel.y) + k0;
bPixel.x = (k1 * bPixel.y) + k0;
}
ctx.moveTo(aPixel.x, aPixel.y);
ctx.lineWidth = 2;
ctx.strokeStyle = '#eee';
ctx.lineTo(midPixel.x, midPixel.y);
ctx.lineTo(bPixel.x, bPixel.y);
ctx.lineCap = 'round';
ctx.stroke();
}
}
}
}
添加边框:
//**********轨迹边框*************
this.canvasLayerBack = new this.CanvasLayer({
map: this.SMap,
update: updateBack,
});
// 边框
function updateBack() {
const nextArray = [];
const ctx = self.canvasLayerBack.canvas.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (totalPoints.length !== 0) {
for (let i = 0, len = totalPoints.length; i < len - 2; i += 1) {
const pixel = self.SMap.pointToPixel(totalPoints[i]);
const nextPixel = self.SMap.pointToPixel(totalPoints[i + 1]);
ctx.beginPath();
ctx.moveTo(pixel.x, pixel.y);
ctx.lineWidth = FLOODLINEWIDTH + 3;
ctx.strokeStyle = '#8b8b89';
ctx.lineTo(nextPixel.x, nextPixel.y);
ctx.lineCap = 'round';
ctx.stroke();
}
}
}
4.项目源代码
参考文章:
https://www.dazhuanlan.com/2019/10/11/5d9f6a1b1a71e/