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
      • Polygon
      • Rectangle
      • Render.getRenderer
      • SVG
      • SVG.Util
      • SVG.VML
      • CircleMarker
      • Circle
        • 概述
        • 源码分析
          • 源码实现
          • 源码解析
          • 投影转换_project
          • 与其他模块的关系
          • 注意事项
        • ​总结​​
    • Layer
    • LayerGroup
    • FeatureGroup
    • DivOverlay
    • Popup
    • Tooltip
    • ImageOverlay
    • SVGOverlay
    • VideoOverlay
    • GeoJSON
  • Map

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

Circle

# 概述

Circle是用于在地图上绘制圆形覆盖物的类,继承自CircleMarker类,二者有所差异:CircleMarker类是固定像素半径,而Circle则是以地理单位(米)为半径,在投影转换上是不同的。

# 源码分析

# 源码实现

Circle的源码实现如下:

export var Circle = CircleMarker.extend({
  initialize: function (latlng, options, legacyOptions) {
    if (typeof options === "number") {
      options = Util.extend({}, legacyOptions, { radius: options });
    }
    Util.setOptions(this, options);
    this._latlng = toLatLng(latlng);
    if (isNaN(this.options.radius)) {
      throw new Error("Circle radius cannot be NaN");
    }
    this._mRadius = this.options.radius;
  },
  // 设置地理半径(米)
  setRadius: function (radius) {
    this._mRadius = radius;
    return this.redraw(); //触发重投影和重绘
  },
  // 获取地理半径(米)
  getRadius: function () {
    return this.mRadius;
  },
  // 边界计算
  getBounds: function () {
    // 基于像素半径计算地理边界
    var half = [this._radius, this._radiusY || this._radius];

    return new LatLngBounds(
      this._map.layerPointToLatLng(this._point.subtract(half)),
      this._map.layerPointToLatLng(this._point.add(half))
    );
  },
  setStyle: Path.prototype.setStyle,
  // 投影转换
  _project: function () {
    var lng = this._latlng.lng,
      lat = this._latlng.lat,
      map = this._map,
      crs = map.options.crs; // 获取当前地图的参考坐标系
     
     // 判断当前是否是EPSG:4326坐标系
    if (crs.distance === Earth.distance) {
      // 复杂数学计算,考虑地球曲率;计算南北极点投影;横向半径修正(高纬度变形补偿等)
      var d = Math.PI / 180,
        latR = this._mRadius / Earth.R / d,
        top = map.project([lat + latR, lng]),
        bottom = map.project([lat - latR, lng]),
        p = top.add(bottom).divideBy(2),
        lat2 = map.unproject(p).lat,
        lngR =
          Math.acos(
            (Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
              (Math.cos(lat * d) * Math.cos(lat2 * d))
          ) / d;

      if (isNaN(lngR) || lngR === 0) {
        lngR = latR / Math.cos((Math.PI / 180) * lat); // Fallback for edge case, #2425
      }

      this._point = p.subtract(map.getPixelOrigin()); // 圆心像素坐标
      this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
      this._radiusY = p.y - top.y;
    } else {
      // 其他坐标系计算圆心坐标和半径
      var latlng2 = crs.unproject(
        crs.project(this._latlng).subtract([this._mRadius, 0])
      );

      this._point = map.latLngToLayerPoint(this._latlng);
      this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
    }

    this._updateBounds(); //更新包围盒
  },
});

export function circle(latlng, options, legacyOptions) {
  return new Circle(latlng, options, legacyOptions);
}
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

# 源码解析

# 投影转换_project

  • 地球坐标系(WGS84/EPSG:4326 )

    • 纵向半径:直接通过纬度差计算
    • 横向半径:使用三角函数补偿高纬度地区投影拉伸
    • 修正逻辑:当纬度接近极地时(cos(lat) → 0),使用备用公式避免除零错误
  • 投影坐标系(Web Mercator)

    • 直接通过投影坐标差计算像素半径

# 与其他模块的关系

模块 关系
CircleMarker 继承自该模块,复用像素级渲染逻辑,重写半径计算和投影方法
Path 继承链的一环,提供样式管理、事件系统和基础渲染生命周期
CRS.Earth 依赖其地球半径常数(Earth.R)进行WGS84坐标系的半径计算

# 注意事项

​​1.投影失真​​:在高纬度地区,地理圆形可能显示为椭圆(因墨卡托投影拉伸)。 ​​2.性能影响​​:频繁调用 setRadius 会触发重投影,建议批量更新。 ​​3.坐标系限制​​:非地球坐标系下(如 EPSG:3857)的计算精度可能不同。 ​

# ​总结​​

Leaflet 的 Circle 类通过以下设计实现地理半径圆形:

1.​​动态投影转换​​:实时计算像素半径,适配不同坐标系。 ​​2.数学修正​​:处理地球坐标系的纬度变形问题。 ​​3.兼容性层​​:支持新旧 API 参数格式。 ​​4.性能优化​​:缓存计算结果,减少重复投影。

该模块是 Leaflet 处理地理空间圆形覆盖物的核心实现,兼顾数学精确性与渲染性能。

编辑 (opens new window)
上次更新: 2025/05/08, 10:32:25
CircleMarker
Layer

← CircleMarker Layer→

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