Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

  • geo

  • layer

    • marker

    • tile

    • vector

      • Renderer
      • Canvas
      • Path
      • Polyline
        • 概述
        • 源码分析
          • 源码实现
          • 源码详解
          • 1.类定义与选项
          • 2.数据转换与边界计算
          • 3.坐标投影与裁剪
          • 4.性能优化
          • 5.交互与查询方法
          • 6.渲染更新
          • 7.多线段支持
        • 总结
      • Polygon
      • Rectangle
      • Render.getRenderer
      • SVG
      • SVG.Util
      • SVG.VML
      • CircleMarker
      • Circle
    • Layer
    • LayerGroup
    • FeatureGroup
    • DivOverlay
    • Popup
    • Tooltip
    • ImageOverlay
    • SVGOverlay
    • VideoOverlay
    • GeoJSON
  • Map

  • 《Leaflet源码》笔记
  • layer
  • vector
东流
2025-04-15
目录

Polyline

# 概述

Leaflet中的Polyline类用于在地图上绘制折线和多段线,主要负责管理折线的地理数据,处理坐标转换、边界计算、裁剪简化,并与渲染器交互进行绘制,继承自Path类,拥有通用路径处理能力,并扩展了折线特有的功能,如最近点计算、动态添加点、性能优化等。

# 源码分析

# 源码实现

Polyline的源码实现如下:

export var Polyline = Path.extend({
  options: {
    smoothFactor: 1.0, // 简化因子,控制渲染性能与精度
    noClip: false, // 是否禁用裁剪,用于绘制超出地图边界的折线
  },
  initialize: function (latlng, options) {
    Util.setOptions(this, options); // 合并参数选项
    this._setLatLngs(latlngs); // 初始化折线经纬度的坐标
  },
  getLatLngs: function () {
    return this._latlngs;
  },
  setLatLngs: function (latlngs) {
    this._setLatLngs(latlngs);
    return this.redraw();
  },
  isEmpty: function () {
    return !this._latlngs.length;
  },
  closestLayerPoint: function (p) {
    var minDistance = Infinity,
      minPoint = null,
      closest = LineUtil._sqClosestPointOnSegment,
      p1,
      p2;

    for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
      var points = this._parts[j];

      for (var i = 1, len = points.length; i < len; i++) {
        p1 = points[i - 1];
        p2 = points[i];

        var sqDist = closest(p, p1, p2, true);

        if (sqDist < minDistance) {
          minDistance = sqDist;
          minPoint = closest(p, p1, p2);
        }
      }
    }
    if (minPoint) {
      minPoint.distance = Math.sqrt(minDistance);
    }
    return minPoint;
  },
  getCenter: function () {
    if (!this._map) {
      throw new Error("Must add layer to map before using getCenter()");
    }
    return LineUtil.polylineCenter(this._defaultShape(), this._map.options.crs);
  },
  getBounds: function () {
    return this._bounds;
  },
  addLatLng: function (latlng, latlngs) {
    latlngs = latlngs || this._defaultShape();
    latlng = toLatLng(latlng);
    latlngs.push(latlng);
    this._bounds.extend(latlng);
    return this.redraw();
  },
  _setLatLngs: function (latlngs) {
    this._bounds = new LatLngBounds();
    this._latlngs = this._convertLatLngs(latlngs);
  },
  _defaultShape: function () {
    return LineUtil.isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
  },
  _convertLatLngs: function (latlngs) {
    var result = [],
      flat = LineUtil.isFlat(latlngs);
    // 递归将输入转换为LatLng实例,并计算边界
    for (var i = 0, len = latlngs.length; i < len; i++) {
      if (flat) {
        result[i] = toLatLng(latlngs[i]);
        this._bounds.extend(result[i]); // 扩展计算新的地理边界
      } else {
        result[i] = this._convertLatLngs(latlngs[i]); // 递归处理嵌套结构
      } 
    }

    return result;
  },
  _project: function () {
    var pxBounds = new Bounds();
    this._rings = [];
    this._projectLatlngs(this._latlngs, this._rings, pxBounds);

    if (this._bounds.isValid() && pxBounds.isValid()) {
      this._rawPxBounds = pxBounds; // 投影后的像素边界
      this._updateBounds();
    }
  },
  _updateBounds: function () {
    var w = this._clickTolerance(),
      p = new Point(w, w);

    if (!this._rawPxBounds) {
      return;
    }

    this._pxBounds = new Bounds([
      this._rawPxBounds.min.subtract(p),
      this._rawPxBounds.max.add(p),
    ]);
  },
  _projectLatlngs: function (latlng, result, projectedBounds) {
    var flat = latlngs[0] instanceof LatLng,
      len = latlngs.length,
      i,
      ring;

    if (flat) {
      ring = [];
      for (i = 0; i < len; i++) {
        ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
        projectedBounds.extend(ring[i]);
      }
      result.push(ring);
    } else {
      for (i = 0; i < len; i++) {
        this._projectLatlngs(latlngs[i], result, projectedBounds);
      }
    }
  },
  _clipPoints: function () {
    var bounds = this._renderer._bounds;

    this._parts = [];
    if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
      return;
    }

    if (this.options.noClip) {
      this._parts = this._rings; // 不裁剪直接使用原始数据
      return;
    }

    var parts = this._parts,
      i,
      j,
      k,
      len,
      len2,
      segment,
      points;

    for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
      points = this._rings[i];

      for (j = 0, len2 = points.length; j < len2 - 1; j++) {
        // 使用线端裁剪算法分割折线
        segment = LineUtil.clipSegment(
          points[j],
          points[j + 1],
          bounds,
          j,
          true
        );

        if (!segment) {
          continue;
        }

        parts[k] = parts[k] || [];
        parts[k].push(segment[0]);

        if (segment[1] !== points[j + 1] || j === len2 - 2) {
          parts[k].push(segment[1]);
          k++;
        }
      }
    }
  },
  _simplifyPoints: function () {
    var parts = this._parts,
      tolerance = this.options.smoothFactor;

    for (var i = 0, len = parts.length; i < len; i++) {
      parts[i] = LineUtil.simplify(parts[i], tolerance); // 简化折线:采用道格拉斯-普克算法
    }
  },
  _update: function () {
    if (!this._map) {
      return;
    }

    this._clipPoints(); // 视口裁剪
    this._simplifyPoints();//简化折线
    this._updatePath(); //调用渲染器更新
  },
  _updatePath: function () {
    this._renderer._updatePoly(this); // 通知渲染器(如SVG或Canvas)绘制
  },
  _containsPoint: function (p, closed) {
    var i,
      j,
      k,
      len,
      len2,
      part,
      w = this._clickTolerance();

    if (!this._pxBounds || !this._pxBounds.contains(p)) {
      return false;
    }

    for (i = 0, len = this._parts.length; i < len; i++) {
      part = this._parts[i];

      for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
        if (!closed && j === 0) {
          continue;
        }

        if (LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
          return true;
        }
      }
    }
    return false;
  },
});

export function polyline(latlngs, options) {
  return new Polyline(latlngs, options);
}

Polyline._flat = LineUtil._flat; //兼容旧版本
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230

# 源码详解

# 1.类定义与选项

  • smoothFactor:折线简化系数,值越大渲染越快但精度越低
  • noClip:禁用自动裁剪,默认折线会根据视口裁剪以提升性能

# 2.数据转换与边界计算

  • 支持多维数组(如MultiPolyline),递归转换为LatLng实例
  • 计算并更新折线的地理边界_bounds,用于快速范围查询

# 3.坐标投影与裁剪

  • 投影:将经纬度转换为地图容器的像素坐标,存储在_rings中
  • 裁剪:_clipPoints根据渲染视口边界裁剪折线,减少渲染复杂度

# 4.性能优化

  • 简化折线:根据smoothFactor移除冗余点,平衡性能与折线形状

# 5.交互与查询方法

  • closestLayerPoint(p):计算点p到折线的最近距离,用于交互检测
  • getCenter():计算折线的质心,基于线段长度加权平均
  • containsPoint(p):检测点是否在折线附近(考虑点击容差)

# 6.渲染更新

  • _updatePath():折线数据变化时会调用渲染器触发重绘流程更新折线的绘制,如SVG或Canvas

# 7.多线段支持

  • 嵌套数组处理:通过递归允许传入多维数组,表示多条独立折线(MultiPolyline)

# 总结

Polyline类的核心职责包括:

​1.​数据管理​​:处理经纬度数据,支持简单折线和多段线。 ​2.​坐标转换​​:将地理坐标投影为像素坐标,处理视口裁剪。 ​​3.性能优化​​:通过简化和裁剪提升渲染效率。 ​​4.交互支持​​:实现最近点查询、点包含检测等功能。 ​​5.渲染协作​​:与具体渲染器(SVG/Canvas)交互,完成最终绘制。

其设计充分考虑了地理数据的复杂性、渲染性能及用户交互需求,是 Leaflet 矢量图层的基础组件之一。

编辑 (opens new window)
上次更新: 2025/05/09, 05:23:58
Path
Polygon

← Path Polygon→

最近更新
01
GeoJSON
05-08
02
Circle
04-15
03
CircleMarker
04-15
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式