Leaflet地图 — 绘制台风风圈

Leaflet -- 绘制台风风圈

          • 实现效果
          • 实现思路
          • 核心代码
          • 全部代码
          • 结束语
实现效果

台风风圈实现效果

实现思路

我们先来看一下,后端返回的数据格式。下面是台风轨迹列表中的一条记录,红框的字段就是画台风风圈的关键字段:se、se、ne、nw分别代表当前点位台风风圈的东南、西南、东北、西北四个方向的影响半径。
台风轨迹列表点位数据详情
html的图形的绘制,svg和canvas。最开始选择的是canvas,绘制好了之后,在地图的位移时要重新计算和绘制实现效果不理想。
在这里插入图片描述

之后看了下leaflet的源码发现L.Polygon 是继承自L.Path对象。而L.Path本身又是,基于SVG实现的。那就好办了,我们继承L.Polygon再写个类L.Typhoon
在这里插入图片描述
L.Typhoon类中,两个最核心方法:一个是计算风圈四个方向半径,一个是绘制台风风圈: 基于SVG的path元素来实现的,下面是代码:

核心代码
  1. 计算风圈半径:台风中心经纬度和半径计算屏幕坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
projectLatlngs: function () {
     try {
       var e = this._latlng;
       this._point = this._map.latLngToLayerPoint(e);
       var t_northeast = this._getLngRadius(this._getLatRadius(this._circle.ne * 1000)),
           i_northeast = this._map.latLngToLayerPoint([e.lat, e.lng - t_northeast]);
       this._radius_northeast = Math.max(this._point.x - i_northeast.x, 1);
       var t_southeast = this._getLngRadius(this._getLatRadius(this._circle.se * 1000)),
           i_southeast = this._map.latLngToLayerPoint([e.lat, e.lng - t_southeast]);
       this._radius_southeast = Math.max(this._point.x - i_southeast.x, 1);
       var t_southwest = this._getLngRadius(this._getLatRadius(this._circle.sw * 1000)),
           i_southwest = this._map.latLngToLayerPoint([e.lat, e.lng - t_southwest]);
       this._radius_southwest = Math.max(this._point.x - i_southwest.x, 1);
       var t_northwest = this._getLngRadius(this._getLatRadius(this._circle.nw * 1000)),
           i_northwest = this._map.latLngToLayerPoint([e.lat, e.lng - t_northwest]);
       this._radius_northwest = Math.max(this._point.x - i_northwest.x, 1)
     } catch (e) {
       this._radius_northeast = null;
       this._radius_southeast = null;
       this._radius_southwest = null;
       this._radius_northwest = null
     }
   },
  1. 绘制台风风圈:
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
getTyphoonPath: function () {
      if (this._radius_northeast && this._radius_southeast && this._radius_southwest && this._radius_northwest) {
        var t = this._point;
        var e_northeast = this._radius_northeast;
        var path_svg = "M" + t.x + "," + (t.y - e_northeast);
        var path_vml = "M" + t.x + "," + (t.y - e_northeast);
        path_svg += "A" + e_northeast + "," + e_northeast + ",0,0,1," + (t.x + e_northeast) + "," + t.y;
        path_vml += " ae " + t.x + "," + t.y + " " + e_northeast + "," + e_northeast + " " + 65535 * 450 + "," + -5898150;
        var e_southeast = this._radius_southeast;
        path_svg += "L" + (t.x + e_southeast) + "," + t.y;
        path_svg += "A" + e_southeast + "," + e_southeast + ",0,0,1," + t.x + "," + (t.y + e_southeast);
        path_vml += " ae " + t.x + "," + t.y + " " + e_southeast + "," + e_southeast + " " + 65535 * 360 + "," + -5898150;
        var e_southwest = this._radius_southwest;
        path_svg += "L" + t.x + "," + (t.y + e_southwest);
        path_svg += "A" + e_southwest + "," + e_southwest + ",0,0,1," + (t.x - e_southwest) + "," + t.y;
        path_vml += " ae " + t.x + "," + t.y + " " + e_southwest + "," + e_southwest + " " + 65535 * 270 + "," + -5898150;
        var e_northwest = this._radius_northwest;
        path_svg += "L" + (t.x - e_northwest) + "," + t.y;
        path_svg += "A" + e_northwest + "," + e_northwest + ",0,0,1," + t.x + "," + (t.y - e_northwest) + "z";
        path_vml += " ae " + t.x + "," + t.y + " " + e_northwest + "," + e_northwest + " " + 65535 * 180 + "," + -5898150 + "X";
        this.svgPath = L.Browser.svg ? path_svg : path_vml
        return L.Browser.svg ? path_svg : path_vml
      }
      return ""
    },

全部代码
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
76
77
78
79
80
81
82
83
84
85
86
87
(function () {
  L.Typhoon = L.Polygon.extend({
    initialize: function (t, e, i) {
      L.Polygon.prototype.initialize.call(this, e), this._latlng = L.latLng(t), this._circle = e,this._style = i;
    },
    options: {fill: !0},
    projectLatlngs: function () {
      try {
        var e = this._latlng;
        this._point = this._map.latLngToLayerPoint(e);
        var t_northeast = this._getLngRadius(this._getLatRadius(this._circle.ne * 1000)),
            i_northeast = this._map.latLngToLayerPoint([e.lat, e.lng - t_northeast]);
        this._radius_northeast = Math.max(this._point.x - i_northeast.x, 1);
        var t_southeast = this._getLngRadius(this._getLatRadius(this._circle.se * 1000)),
            i_southeast = this._map.latLngToLayerPoint([e.lat, e.lng - t_southeast]);
        this._radius_southeast = Math.max(this._point.x - i_southeast.x, 1);
        var t_southwest = this._getLngRadius(this._getLatRadius(this._circle.sw * 1000)),
            i_southwest = this._map.latLngToLayerPoint([e.lat, e.lng - t_southwest]);
        this._radius_southwest = Math.max(this._point.x - i_southwest.x, 1);
        var t_northwest = this._getLngRadius(this._getLatRadius(this._circle.nw * 1000)),
            i_northwest = this._map.latLngToLayerPoint([e.lat, e.lng - t_northwest]);
        this._radius_northwest = Math.max(this._point.x - i_northwest.x, 1)
      } catch (e) {
        this._radius_northeast = null;
        this._radius_southeast = null;
        this._radius_southwest = null;
        this._radius_northwest = null
      }
    },
    getTyphoonPath: function () {
      if (this._radius_northeast && this._radius_southeast && this._radius_southwest && this._radius_northwest) {
        var t = this._point;
        var e_northeast = this._radius_northeast;
        var path_svg = "M" + t.x + "," + (t.y - e_northeast);
        var path_vml = "M" + t.x + "," + (t.y - e_northeast);
        path_svg += "A" + e_northeast + "," + e_northeast + ",0,0,1," + (t.x + e_northeast) + "," + t.y;
        path_vml += " ae " + t.x + "," + t.y + " " + e_northeast + "," + e_northeast + " " + 65535 * 450 + "," + -5898150;
        var e_southeast = this._radius_southeast;
        path_svg += "L" + (t.x + e_southeast) + "," + t.y;
        path_svg += "A" + e_southeast + "," + e_southeast + ",0,0,1," + t.x + "," + (t.y + e_southeast);
        path_vml += " ae " + t.x + "," + t.y + " " + e_southeast + "," + e_southeast + " " + 65535 * 360 + "," + -5898150;
        var e_southwest = this._radius_southwest;
        path_svg += "L" + t.x + "," + (t.y + e_southwest);
        path_svg += "A" + e_southwest + "," + e_southwest + ",0,0,1," + (t.x - e_southwest) + "," + t.y;
        path_vml += " ae " + t.x + "," + t.y + " " + e_southwest + "," + e_southwest + " " + 65535 * 270 + "," + -5898150;
        var e_northwest = this._radius_northwest;
        path_svg += "L" + (t.x - e_northwest) + "," + t.y;
        path_svg += "A" + e_northwest + "," + e_northwest + ",0,0,1," + t.x + "," + (t.y - e_northwest) + "z";
        path_vml += " ae " + t.x + "," + t.y + " " + e_northwest + "," + e_northwest + " " + 65535 * 180 + "," + -5898150 + "X";
        this.svgPath = L.Browser.svg ? path_svg : path_vml
        return L.Browser.svg ? path_svg : path_vml
      }
      return ""
    },
    beforeAdd: function (map) {
      this._renderer = map.getRenderer(this);
    },
    onAdd: function (map) {
      this.projectLatlngs();
      this.getTyphoonPath();
      this._renderer._initPath(this);
      this._reset();
      this._path.setAttribute('d',this.svgPath);
      this._renderer._addPath(this);
      this._setStyle(this._style);
    },

    _setStyle: function (style) {
      L.setOptions(this, style);
      if (this._renderer) {
        this._renderer._updateStyle(this);
      }
      return this;
    },
    _getLatRadius: function (r) {
      return r / 40075017 * 360
    },
    _getLngRadius: function (lr) {
      return lr / Math.cos(Math.PI / 180 * this._latlng.lat)
    }
  });

  L.typhoon = function (t, e, i) {
    return new L.Typhoon(t, e, i)
  }
 
})();
结束语

以上就是台风风圈的绘制实现及代码。本文是基于leaflet v1.6.0的完成的,低版本的leaflet是否可以用并未进行测试,请自行进行测试或对代码进行修改。