Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 框架

  • core模块

  • dom模块

  • control

  • geometry

    • Point
    • Bounds
    • Transformation
    • LineUtil
    • PolyUtil
      • 概述
      • 源码分析
        • 源码实现
        • 源码详解
        • 多边形形裁剪函数clipPolygon(points,bounds,round)
        • 多边形中心计算polygonCenter(latlngs,crs)
        • 辅助函数centroid(coords)
        • 使用场景​​
      • 总结
  • geo

  • layer

  • Map

  • 《Leaflet源码》笔记
  • geometry
东流
2025-04-09
目录

PolyUtil

# 概述

PolyUtil 是一个用于处理多边形的工具类,它提供了一些常用的方法,用于多边形裁剪与中心点计算等。

# 源码分析

# 源码实现

PolyUtil 的源码实现如下:

export function clipPolygon(points, bounds, round) {
  var clippedPoints,
    edges = [1, 4, 2, 8],
    i,
    j,
    k,
    a,
    b,
    len,
    edge,
    p;

  for (i = 0, len = points.length; i < len; i++) {
    points[i]._code = LineUtil._getBitCode(points[i], bounds);
  }

  // for each edge (left, bottom, right, top)
  for (k = 0; k < 4; k++) {
    edge = edges[k];
    clippedPoints = [];

    for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
      a = points[i];
      b = points[j];

      // if a is inside the clip window
      if (!(a._code & edge)) {
        // if b is outside the clip window (a->b goes out of screen)
        if (b._code & edge) {
          p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
          p._code = LineUtil._getBitCode(p, bounds);
          clippedPoints.push(p);
        }
        clippedPoints.push(a);

        // else if b is inside the clip window (a->b enters the screen)
      } else if (!(b._code & edge)) {
        p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
        p._code = LineUtil._getBitCode(p, bounds);
        clippedPoints.push(p);
      }
    }
    points = clippedPoints;
  }

  return points;
}

export function polygonCenter(latlngs, crs) {
  var i, j, p1, p2, f, area, x, y, center;

  if (!latlngs || latlngs.length === 0) {
    throw new Error("latlngs not passed");
  }

  if (!LineUtil.isFlat(latlngs)) {
    console.warn("latlngs are not flat! Only the first ring will be used");
    latlngs = latlngs[0];
  }

  var centroidLatLng = toLatLng([0, 0]);

  var bounds = toLatLngBounds(latlngs);
  var areaBounds =
    bounds.getNorthWest().distanceTo(bounds.getSouthWest()) *
    bounds.getNorthEast().distanceTo(bounds.getNorthWest());
  // tests showed that below 1700 rounding errors are happening
  if (areaBounds < 1700) {
    // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
    centroidLatLng = centroid(latlngs);
  }

  var len = latlngs.length;
  var points = [];
  for (i = 0; i < len; i++) {
    var latlng = toLatLng(latlngs[i]);
    points.push(
      crs.project(
        toLatLng([
          latlng.lat - centroidLatLng.lat,
          latlng.lng - centroidLatLng.lng,
        ])
      )
    );
  }

  area = x = y = 0;

  // polygon centroid algorithm;
  for (i = 0, j = len - 1; i < len; j = i++) {
    p1 = points[i];
    p2 = points[j];

    f = p1.y * p2.x - p2.y * p1.x;
    x += (p1.x + p2.x) * f;
    y += (p1.y + p2.y) * f;
    area += f * 3;
  }

  if (area === 0) {
    // Polygon is so small that all points are on same pixel.
    center = points[0];
  } else {
    center = [x / area, y / area];
  }

  var latlngCenter = crs.unproject(toPoint(center));
  return toLatLng([
    latlngCenter.lat + centroidLatLng.lat,
    latlngCenter.lng + centroidLatLng.lng,
  ]);
}

export function centroid(coords) {
  var latSum = 0;
  var lngSum = 0;
  var len = 0;
  for (var i = 0; i < coords.length; i++) {
    var latlng = toLatLng(coords[i]);
    latSum += latlng.lat;
    lngSum += latlng.lng;
    len++;
  }
  return toLatLng([latSum / len, lngSum / len]);
}
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

# 源码详解

# 多边形形裁剪函数clipPolygon(points,bounds,round)

作用:用矩形边界裁剪多边形,返回裁剪后的顶点数组(基于Sutherland-Hodgman算法)

关键步骤

  1. 预计算点的位编码 为每个顶点标记其相对于边界的方位(左/右/上/下),使用 4 位二进制编码

  2. 按顺序处理四条边 依次处理左(1)、下(4)、右(2)、上(8)四条边界,逐步裁剪多边形

  3. 逐边裁剪逻辑 对每条边,遍历多边形的每条边 a-b,根据顶点位置关系添加交点或保留点:

  • ​Case 1​​: a 在内侧,b 在外侧 → 添加交点。
  • Case 2​​: a 在外侧,b 在内侧 → 添加交点。
  • Case 3​​: 两点均在内侧 → 保留 a。

# 多边形中心计算polygonCenter(latlngs,crs)

作用:计算多边形的几何中心质心,基于多边形面积加权法

算法原理:

  1. 坐标平移:将多边形平移到原点附近,减少浮点误差。
  2. 质心公式: 通过遍历多边形的所有边,累加面积和加权坐标

# 辅助函数centroid(coords)

作用:计算多边形顶点的算术平均中心(非面积加权,仅坐标平均)

对比​​:

  • 算术平均中心​​:简单平均,不考虑顶点分布密度。
  • ​面积加权中心​​:更精确的几何中心,适用于不规则多边形。

# 使用场景​​

  • 地图渲染优化​​:裁剪不可见部分的多边形,提升性能。
  • 信息标注​​:在多边形中心显示标签或图标。
  • 交互计算​​:判断多边形是否在视野内,或计算聚焦位置。

# 总结

PolyUtil 提供了高效的多边形裁剪与中心点计算方法,在 Leaflet 中广泛应用于地图渲染与交互,实现了高效的多边形几何操作,支撑了复杂地理数据的可视化与交互。

编辑 (opens new window)
上次更新: 2025/04/18, 07:17:03
LineUtil
CRS

← LineUtil CRS→

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