引线图层
有时业务里需要再地图上做个引线效果,尤其是大屏上
难点分析
- 引线的坐标点包含地理坐标
- 引线的坐标里还得包含像素坐标的,因为引线出来的东西(dom)不变的,其不像地图坐标随着地图的视角改变而改变
- 目前maptalks体系内还没有对应的图层或者插件
解决方法
既然maptalks里没有对应的图层,我们就造一个:
为了更好的适配maptalk和工作量最小 我们要尽可能的利用maptalks提供现成的图层和图形
具体体现在:
- 继承VectorLayer并重写其渲染逻辑
- 引线的图形直接复用LineString
- LineString里携带地理坐标和像素坐标
- 为了更好的区分地理坐标和像素坐标,我们需要这两种坐标区分开来,以免乱了
- 像素坐标(vertexs)我们直接放到symbol里了,目的还是最好不要创造出一个新的图形,直接复用LineStirng
- 相对于原生的LineString只是多了个 vertexs 而已,可以保持LineString的使用习惯的
这里我直接贴代码了,因为只有你对maptalks的源码和渲染流程足够了解才能看懂为啥要这样做,这个需要你深入研究maptalks
业务里可以直接拿来用的
js
const VectorLayer = maptalks.VectorLayer;
const options = {
geometryEvents: false,
hitDetect: false,
enableAltitude: true,
forceRenderOnMoving: true,
forceRenderOnZooming: true,
forceRenderOnRotating: true,
};
class LinkLineLayer extends VectorLayer {}
LinkLineLayer.mergeOptions(options);
const OverlayLayerCanvasRenderer = maptalks.renderer.OverlayLayerCanvasRenderer;
const LineString = maptalks.LineString;
class LinkLineLayerCanvasRenderer extends OverlayLayerCanvasRenderer {
draw() {
this._drawLines();
return this;
}
drawOnInteracting() {
this._drawLines();
return this;
}
_drawLines() {
const layer = this.layer;
if (!layer) {
return this;
}
const map = this.layer.getMap();
if (!map) {
return this;
}
this.prepareCanvas();
if (!this.context) {
return this;
}
const lines = this.layer._geoList.filter((g) => {
if (g instanceof LineString) {
g._getPainter();
return true;
}
return false;
});
lines.forEach((line) => {
this._drawLinkLine(line);
});
this.completeRender();
return this;
}
_drawLinkLine(line) {
const map = this.layer.getMap();
const coords = line.getCoordinates();
if (!coords || !coords.length) {
return this;
}
if (!line._painter) {
return this;
}
const symbolizer = line._painter.symbolizers;
const lineSymbol = symbolizer[0];
if (!lineSymbol) {
return this;
}
const altitude = line._painter.getAltitude();
const {
lineColor,
lineWidth,
shadowBlur,
shadowColor,
vertexs,
offsetX,
offsetY,
} = lineSymbol.symbol || {};
const renderPoints = line._painter.getRenderPoints()[0][0];
const glRes = map.getGLRes();
const pixel = map._pointAtResToContainerPoint(
renderPoints,
glRes,
altitude,
);
const ctx = this.context;
ctx.strokeStyle = lineColor || "#000";
ctx.lineWidth = lineWidth || 1;
if (shadowBlur) {
ctx.shadowBlur = shadowBlur;
ctx.shadowColor = shadowColor || "#fff";
} else {
ctx.shadowBlur = 0;
// delete ctx.shadowColor;
}
// ctx.shadowColor = shadowColor;
ctx.beginPath();
ctx.moveTo(pixel.x + (offsetX || 0), pixel.y + (offsetY || 0));
if (Array.isArray(vertexs)) {
for (let i = vertexs.length - 1; i >= 0; i--) {
const vertex = vertexs[i];
if (Array.isArray(vertex)) {
const [x, y] = vertex;
ctx.lineTo(x, y);
}
}
}
ctx.stroke();
return this;
}
}
LinkLineLayer.registerRenderer("canvas", LinkLineLayerCanvasRenderer);
案例
最近江苏足球联赛比较火,就拿其作为素材来实战下,我们的目标是完成下面这样的效果图
实战步骤:
准备相关地理数据,这个我就不介绍了
页面的布局我就不说了
创建对应的引线图层
js
const linkLayer = new LinkLineLayer("link").addTo(map);
- 创建引线LineString,注意我们要携带 vertexs 数据的,这时和原生的LineString唯一的区别
js
function updateLink() {
const center = map.coordinateToContainerPoint(map.getCenter());
const titleList = document.querySelectorAll(".panel .item .title");
Array.prototype.forEach.call(titleList, (dom) => {
const title = dom.textContent;
const itemDom = dom.parentNode;
const rect = itemDom.getBoundingClientRect();
// console.log(rect);
const { bottom, right, left, top } = rect;
const coordinate = findPointByName(title).getCoordinates().toArray();
let y = (top + bottom) / 2;
let x = right;
const p1 = new maptalks.Point(right, y);
const p2 = new maptalks.Point(left, y);
if (p1.distanceTo(center) > p2.distanceTo(center)) {
x = p2.x;
y = p2.y;
}
const vs = [x, y];
const symbol = Object.assign({}, lineSymbol, { vertexs: [vs] });
let line = findLineByName(title);
if (!line) {
line = new maptalks.LineString([coordinate], {
symbol,
properties: {
name: title,
},
}).addTo(linkLayer);
} else {
line.setSymbol(symbol);
}
});
}
TIP
当我们构造 vertexs数据时要尽可能的去动态的读取需要连接dom的位置,这里我直接使用了 getBoundingClientRect
- 不要人工的去构造像素坐标点,太烦了还容易出错
- 当地图大小改变或者引线连接的dom大小或者位置改变记得更新 vertexs