Skip to content
目录

引线图层

有时业务里需要再地图上做个引线效果,尤其是大屏上

v2-bc57c69de340df4b18e5beeac53509a8_1440w.jpg

难点分析

  • 引线的坐标点包含地理坐标
  • 引线的坐标里还得包含像素坐标的,因为引线出来的东西(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);

案例

最近江苏足球联赛比较火,就拿其作为素材来实战下,我们的目标是完成下面这样的效果图

D%7B)KPMSZ%7BM6JE1%5BNAB0%24%7D4S.png

苏超 LinkLineLayer demo

实战步骤:

  • 准备相关地理数据,这个我就不介绍了

  • 页面的布局我就不说了

  • 创建对应的引线图层

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

maptalks教程 document auto generated by mdpress and vitepress