无法使用 JavaScript 和 Leaflet.TextPath 插件将文本插入传单地图

Can not insert text into a Leaflet Map using JavaScript and the Leaflet.TextPath plugin

我目前正在尝试使用 JavaScript 将一些线、圆和多边形绘制到传单地图中。

我已经能够插入线、圆和多边形,并且通过使用 ToolTip 文本被插入到 Leaflet 地图中的折线。我正在使用它来将这张地图嵌入到 Pyqt5 GUI 中。

我要实现的文字类型如图1,所以ToolTip不适合,只能得到图2的文字

目前已经试过了

我已经寻找其他解决方案并找到了 Leaflet.TextPath 插件。我尝试在这种情况下使用它,但我没有使用 JavaScript 的经验并且无法使其工作。

下面有3个文件,map.html、map.js和leaflet.textpath.js。 map.html 文件包括 Qt WebChannel、LeafLet 地图、JS 脚本和来自 GitHub https://github.com/makinacorpus/Leaflet.TextPath.

的 leaflet.textpath 文件

问题

当我尝试将文本设置为示例多段线时出现错误,所以这是在尝试使用插件。我认为错误在 map.html 文件中,但我对 JS 或 HTML 不够熟悉,无法捕获错误。我尝试使用其他插件作为传单旋转标记,但这个确实有效。

错误

js:未捕获类型错误:无法读取未定义的 属性 'setAttribute'

如有任何指导,我将不胜感激。

(1)

(2)

文件

map.html

<!DOCTYPE html>
                               <html>
                               <head>
                                   <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
                                   <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
                                   <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" />
                                   <script type="text/javascript" src="map.js"></script>
                                   <script src="leaflet.textpath.js"></script>
                                   <style>
                                       body { padding: 0; margin: 0; }
                                       html, body, #map { height: 100%; width=100%}
                                   </style> 
                               </head>
                               <body onload="initialize()">
                                   <div id="map"></div>
                               </body>
                               </html>

map.js

        var map;
        var coords = [[[-2.9116417837562043, -79.03574879233575], [-2.911946317883794, -79.03491894398161], [-2.912403030611765, -79.03362471656573]], [[-2.9113915485759168, -79.0344148954767], [-2.9115095650671665, -79.03409894066323], [-2.9117645016593343, -79.03334145899967]]];
        var arrows = [[[[-2.911800140520451, -79.03531727419156], [-2.911768021089238, -79.03535268947631], [-2.911788113588835, -79.03535004729815], [-2.911801770657676, -79.03536494151672]], [[-2.9121805563780936, -79.03425516236084], [-2.912148881780239, -79.03429097175606], [-2.9121689397158708, -79.03428808152826], [-2.9121827817497454, -79.03430280585212]]], [[[-2.9114567419946438, -79.03424035911497], [-2.9114244189203617, -79.03427559058679], [-2.9114445263105413, -79.0342730630545], [-2.911458097271369, -79.0342880349087]], [[-2.911642671195043, -79.03370344867531], [-2.911611523070874, -79.03373971200101], [-2.9116315366609524, -79.03373653221718], [-2.911645592328774, -79.03375105493932]]]];
        
        function initialize(){

        // ADD MAP
        map = L.map('map',{zoomSnap: 0, zoomControl: false, preferCanvas: true, inertia: false, doubleClickZoom: false, wheelPxPerZoomLevel: 58.35});

        //GET TILE
        L.tileLayer('http://a.tile.openstreetmap.org/{z}/{x}/{y}.png',
        {
        attribution: 'Map data: &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>' ,
        maxZoom: 10000, reuseTiles: true,

        }
        ).addTo(map);
        

        // ADD LINES
        var style = {color: "#F44336", weight: 5, opacity: 1 };
        for (let i of coords) {L.polyline(i, style).addTo(map);}

        // ADD CIRCLES
        for (let i = 0; i < coords.length; i++) {
        for (let j = 0; j < coords[i].length; j++){L.circle([coords[i][j][0],coords[i][j][1]], {color: '#00B4FF',fillColor: '#00B4FF',fillOpacity: 0.5,radius: 1.9}).addTo(map);}
        }
        
        // ADD ARROWS
        for (let i = 0; i < arrows.length; i++) {
        for (let j = 0; j < arrows[i].length; j++){L.polygon(arrows[i][j], {color: '#00B4FF',fillColor: '#00B4FF',fillOpacity: 0.5,radius: 5}).addTo(map);}
        }
        
       
        // TRY ADDING TEXT TO POLILYNE
        var plane = L.polyline([[-2.9116417837562043, -79.03574879233575], [-2.911946317883794, -79.03491894398161], [-2.912403030611765, -79.03362471656573]], {weight: 1, color: 'black', dashArray: '2, 2'}).addTo(map);
        plane.setText('TEXT', {repeat: true,offset: 8, attributes: {'font-weight': 'bold','font-size': '24'}}); //THIS LINES CAUSES AN ERROR
        

        // ADD SCALE
        var scale = L.control.scale();
        scale.addTo(map);

        //SET BOUNDS
        var southWest = new L.LatLng(-2.9125375550722334,-79.03576792871915),
        northEast = new L.LatLng(-2.9112634693443318,-79.03332005718264),
        bounds = new L.LatLngBounds(southWest, northEast);
        map.fitBounds(bounds, {padding: [0, 0]});

       }

leaflet.textpath.js 复制自 GitHub

/*
 * Leaflet.TextPath - Shows text along a polyline
 * Inspired by Tom Mac Wright article :
 * http://mapbox.com/osmdev/2012/11/20/getting-serious-about-svg/
 */

(function () {

var __onAdd = L.Polyline.prototype.onAdd,
    __onRemove = L.Polyline.prototype.onRemove,
    __updatePath = L.Polyline.prototype._updatePath,
    __bringToFront = L.Polyline.prototype.bringToFront;


var PolylineTextPath = {

    onAdd: function (map) {
        __onAdd.call(this, map);
        this._textRedraw();
    },

    onRemove: function (map) {
        map = map || this._map;
        if (map && this._textNode && map._renderer._container)
            map._renderer._container.removeChild(this._textNode);
        __onRemove.call(this, map);
    },

    bringToFront: function () {
        __bringToFront.call(this);
        this._textRedraw();
    },

    _updatePath: function () {
        __updatePath.call(this);
        this._textRedraw();
    },

    _textRedraw: function () {
        var text = this._text,
            options = this._textOptions;
        if (text) {
            this.setText(null).setText(text, options);
        }
    },

    setText: function (text, options) {
        this._text = text;
        this._textOptions = options;

        /* If not in SVG mode or Polyline not added to map yet return */
        /* setText will be called by onAdd, using value stored in this._text */
        if (!L.Browser.svg || typeof this._map === 'undefined') {
          return this;
        }

        var defaults = {
            repeat: false,
            fillColor: 'black',
            attributes: {},
            below: false,
        };
        options = L.Util.extend(defaults, options);

        /* If empty text, hide */
        if (!text) {
            if (this._textNode && this._textNode.parentNode) {
                this._map._renderer._container.removeChild(this._textNode);
                
                /* delete the node, so it will not be removed a 2nd time if the layer is later removed from the map */
                delete this._textNode;
            }
            return this;
        }

        text = text.replace(/ /g, '\u00A0');  // Non breakable spaces
        var id = 'pathdef-' + L.Util.stamp(this);
        var svg = this._map._renderer._container;
        this._path.setAttribute('id', id);

        if (options.repeat) {
            /* Compute single pattern length */
            var pattern = L.SVG.create('text');
            for (var attr in options.attributes)
                pattern.setAttribute(attr, options.attributes[attr]);
            pattern.appendChild(document.createTextNode(text));
            svg.appendChild(pattern);
            var alength = pattern.getComputedTextLength();
            svg.removeChild(pattern);

            /* Create string as long as path */
            text = new Array(Math.ceil(isNaN(this._path.getTotalLength() / alength) ? 0 : this._path.getTotalLength() / alength)).join(text);
        }

        /* Put it along the path using textPath */
        var textNode = L.SVG.create('text'),
            textPath = L.SVG.create('textPath');

        var dy = options.offset || this._path.getAttribute('stroke-width');

        textPath.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", '#'+id);
        textNode.setAttribute('dy', dy);
        for (var attr in options.attributes)
            textNode.setAttribute(attr, options.attributes[attr]);
        textPath.appendChild(document.createTextNode(text));
        textNode.appendChild(textPath);
        this._textNode = textNode;

        if (options.below) {
            svg.insertBefore(textNode, svg.firstChild);
        }
        else {
            svg.appendChild(textNode);
        }

        /* Center text according to the path's bounding box */
        if (options.center) {
            var textLength = textNode.getComputedTextLength();
            var pathLength = this._path.getTotalLength();
            /* Set the position for the left side of the textNode */
            textNode.setAttribute('dx', ((pathLength / 2) - (textLength / 2)));
        }

        /* Change label rotation (if required) */
        if (options.orientation) {
            var rotateAngle = 0;
            switch (options.orientation) {
                case 'flip':
                    rotateAngle = 180;
                    break;
                case 'perpendicular':
                    rotateAngle = 90;
                    break;
                default:
                    rotateAngle = options.orientation;
            }

            var rotatecenterX = (textNode.getBBox().x + textNode.getBBox().width / 2);
            var rotatecenterY = (textNode.getBBox().y + textNode.getBBox().height / 2);
            textNode.setAttribute('transform','rotate(' + rotateAngle + ' '  + rotatecenterX + ' ' + rotatecenterY + ')');
        }

        /* Initialize mouse events for the additional nodes */
        if (this.options.interactive) {
            if (L.Browser.svg || !L.Browser.vml) {
                textPath.setAttribute('class', 'leaflet-interactive');
            }

            var events = ['click', 'dblclick', 'mousedown', 'mouseover',
                          'mouseout', 'mousemove', 'contextmenu'];
            for (var i = 0; i < events.length; i++) {
                L.DomEvent.on(textNode, events[i], this.fire, this);
            }
        }

        return this;
    }
};

L.Polyline.include(PolylineTextPath);

L.LayerGroup.include({
    setText: function(text, options) {
        for (var layer in this._layers) {
            if (typeof this._layers[layer].setText === 'function') {
                this._layers[layer].setText(text, options);
            }
        }
        return this;
    }
});



})();

对于像我这样刚开始使用 JS 的人,由于我没有足够的 JS 知识,我会 post 我是如何解决这个问题的,但我真的不明白为什么会这样。

问题是地图的 preferCanvas: true 选项,我改变了添加地图和图块的方式,这解决了问题。

// ADD TILE
                var tile_opt = {attribution: 'Map data: &copy; <a href="https://developers.google.com/maps"> Google</a>, <a href="https://developers.google.com/maps/terms"> Terms of Use. </a>' , maxZoom: 10000, reuseTiles: true, noWrap: true }
                var _tile = L.tileLayer('http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}', tile_opt);
    
                // ADD MAP
                var map_opt = {zoomSnap: 0, zoomControl: false, inertia: false, doubleClickZoom: false, wheelPxPerZoomLevel: 58.35}
                var map = L.map('map', map_opt).fitWorld().addLayer(_tile);

此外,文本路径不是适合插入数千个文本的解决方案,因为平移和缩放变得非常慢。我最终使用了 ToolTip,因为它在非永久性文本时可以处理数千个文本。