Post

leaflet渲染大量点 + 旋转点

Icon2CircleMarker 类

十万点加载需要比较久

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import Icon2CircleMarker from "./service/Icon2CircleMarker.js";


 icon2Canvas() {
  if (this.markerLayer) {
	this.map.removeLayer(this.markerLayer);
  }
  const icm = new Icon2CircleMarker();
  const max = 1000;
  this.markerLayer = L.layerGroup();
  const zoom = this.map.getZoom();
  for (let i = 0; i < max; i++) {
	const icon = L.icon({
	  iconUrl: miniship,
	  iconSize: [38, 75],
	  iconAnchor: [22, 94],
	  popupAnchor: [-3, -76],
	});
	const iconPoint = L.icon({
	  iconUrl: point,
	  iconSize: [20, 20],
	  iconAnchor: [10, 10],
	});
	icon.options["angle"] = Math.random() * 100; //角度
	icon.options["string"] = "xxxsxxx";
	icon.options["fontcolor"] = "white";
	const marker = icm.getMarker(
	  [
		this.center[0] + Math.random() * 0.01,
		this.center[1] + Math.random() * 0.01,
	  ],
	  {
		popupAnchor: icon.options.popupAnchor,
		radius: 10,
		icon: zoom > 15 ? icon : iconPoint,
	  }
	);
	this.markerLayer.addLayer(marker);
  }
  // 一次性将所有标记添加到 LayerGroup
  this.markerLayer.addTo(this.map); // 添加到地图
},

参考

Icon2CircleMarker.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"use strict";
/*
L.Iconを渡すとアイコン付きのL.CircleMarkerが返ってくる。
*/
export default class Icon2CircleMarker {
  constructor() {
    //既存クラスのメソッド上書き
    this.icon2Canvas = L.CircleMarker.extend({
      _containsPoint: function (makerPoint) {
        //L.IconのoffsetをCircleMarkerへ適用
        const offset = this.options.icon.options.pointOffset;
        if (offset) {
          return L.CircleMarker.prototype._containsPoint.call(
            this,
            makerPoint.subtract([offset.x, offset.y])
          );
        }
      },

      _updatePath() {
        const op = this.options.icon.options;
        if (op.element) {
          const ctx = this._renderer._ctx,
            p = this._point.round();
          p.x += op.offset[0];
          p.y += op.offset[1];
          //Canvasへ描画開始
          ctx.save();
          ctx.translate(p.x, p.y);
          //アイコンの回転を適用
          if (op.angle) {
            ctx.rotate(op.angle * 0.0174); //Math.PI / 180;
          }
          const x = op.iconSize[0],
            y = op.iconSize[1];
          //drawImageでは描画位置は整数にすること (https://developer.mozilla.org/ja/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas)
          ctx.drawImage(
            op.element,
            -Math.floor(x / 2),
            -Math.floor(y / 2),
            Math.floor(x),
            Math.floor(y)
          );
          //文字は前面に出すため、drawImageの後に追加
          if (op.string) {
            const count = op.string.length;
            //文字を中央にするため苦戦(アイコン、フォントによって変わるため適宜変更のこと)
            const fontsize = x / count < x / 2 ? x / count + 4 : x / 2;
            ctx.font = fontsize + "px sans serif";
            const offset = Math.floor((count * fontsize * 1.3) / 4);
            ctx.fillStyle = op.fontcolor;
            ctx.fillText(op.string, -offset, 0);
          }
          ctx.restore();
        } else {
          const element = document.createElement("img");
          //element.onload = () => this.redraw();//redrawしないと画像が切れることがあるが、パフォーマンス優先
          element.src = op.iconUrl; //キャッシュが効かないと重いため、なるべく同一画像を
          op.element = element;
        }
      },
    });
  }

  getMarker(latlng, options) {
    const op = options.icon.options;
    op.offset = [
      op.iconSize[0] / 2 - op.iconAnchor[0],
      op.iconSize[1] / 2 - op.iconAnchor[1],
    ];
    op.popupAnchor = [0, 0];
    return new this.icon2Canvas(latlng, options);
  }
}

This post is licensed under CC BY 4.0 by the author.