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)
  • 核心基类

  • Control控件篇

    • Control基类介绍
    • 默认Controls控件渲染过程
    • Zoom缩放控件源码分析
    • Rotate旋转控件源码分析
      • 概述
      • 源码分析
        • Rotate控件的参数
        • Rotate类的方法
        • render方法触发机制
      • 总结
    • ZoomToExtent控件源码分析
    • ZoomSlider滑块缩放控件源码分析
    • ScaleLine比例尺控件源码分析
    • Attribution属性控件源码分析
    • FullScreen全屏控件源码分析
    • OverviewMap鹰眼控件源码分析
    • MousePosition鼠标位置控件源码分析
  • Geom几何图形篇

  • Layer图层篇

  • Renderer篇

  • Feature篇

  • style样式篇

  • 《Openlayers源码》笔记
  • Control控件篇
东流
2024-12-11
目录

Rotate旋转控件源码分析

# 概述

Openlayers 中的Rotate旋转控件始终指向正北方向,当地图旋转时,控件也会旋转同样角度和方向;另外还可以通过点击控件重置地图的旋转角度。

本文主要介绍Rotate控件的实现原理。

# 源码分析

Rotate继承于Control类,除了响应用户点击外,还需要监听地图的旋转,其实现如下

class Rotate extends Control {
  constructor(options) {
    options = options ? options : {};
    super({
      element: document.createElement("div"),
      render: options.render,
      target: options.target,
    });
    const className =
      options.className !== undefined ? options.className : "ol-rotate";

    const label = options.label !== undefined ? options.label : "\u21E7";

    const compassClassName =
      options.compassClassName !== undefined
        ? options.compassClassName
        : "ol-compass";
    this.label_ = null;

    if (typeof label === "string") {
      this.label_ = document.createElement("span");
      this.label_.className = compassClassName;
      this.label_.textContent = label;
    } else {
      this.label_ = label;
      this.label_.classList.add(compassClassName);
    }

    const tipLabel = options.tipLabel ? options.tipLabel : "Reset rotation";

    const button = document.createElement("button");
    button.className = className + "-reset";
    button.setAttribute("type", "button");
    button.title = tipLabel;
    button.appendChild(this.label_);

    button.addEventListener(
      EventType.CLICK,
      this.handleClick_.bind(this),
      false
    );

    const cssClasses =
      className + " " + CLASS_UNSELECTABLE + " " + CLASS_CONTROL;
    const element = this.element;
    element.className = cssClasses;
    element.appendChild(button);

    this.callResetNorth_ = options.resetNorth ? options.resetNorth : undefined;
    this.duration_ = options.duration !== undefined ? options.duration : 250;
    this.autoHide_ = options.autoHide !== undefined ? options.autoHide : true;
    this.rotation_ = undefined;

    if (this.autoHide_) {
      this.element.classList.add(CLASS_HIDDEN);
    }
  }
  handleClick_(event) {
    event.preventDefault();
    if (this.callResetNorth_ !== undefined) {
      this.callResetNorth_();
    } else {
      this.resetNorth_();
    }
  }
  resetNorth_() {
    const map = this.getMap();
    const view = map.getView();
    if (!view) {
      return;
    }
    const rotation = view.getRotation();
    if (rotation !== undefined) {
      if (this.duration_ > 0 && rotation % (2 * Math.PI) !== 0) {
        view.animate({
          rotation: 0,
          duration: this.duration_,
          easing: easeOut,
        });
      } else {
        view.setRotation(0);
      }
    }
  }
  render(mapEvent) {
    const frameState = mapEvent.frameState;
    if (!frameState) {
      return;
    }
    const rotation = frameState.viewState.rotation;
    if (rotation != this.rotation_) {
      const transform = "rotate(" + rotation + "rad)";
      if (this.autoHide_) {
        const contains = this.element.classList.contains(CLASS_HIDDEN);
        if (!contains && rotation === 0) {
          this.element.classList.add(CLASS_HIDDEN);
        } else if (contains && rotation !== 0) {
          this.element.classList.remove(CLASS_HIDDEN);
        }
      }
      this.label_.style.transform = transform;
    }
    this.rotation_ = rotation;
  }
}
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

# Rotate控件的参数

Rotate控件的构造函数接受一个参数options对象,该对象有如下属性

  • className:控件的类名,默认为ol-rotate
  • label:控件的显示标签图标,默认为\u21E7
  • compassClassName:控件图标类名,默认为ol-compass
  • tipLabel:控件hover时的显示,默认为Reset rotation
  • resetNorth:点击控件后的回调事件,默认为undefined
  • duration: 地图动画的持续时长,默认为250,表示250 毫秒
  • autoHide: 自动隐藏,默认为true,初始化时隐藏控件

Rotate类的构造函数内部会调用父类super({ element: document.createElement('div'),render: options.render,target: options.target,}),默认情况下,render和target都不存在,然后采用上述的默认值,创建一个按钮及其符号,当调用setMap方法时会将它添加到overlay container中;通过元素的addEventListener注册Rotate控件的点击handleClick_事件;默认情况下this.callRestNorth_为undefined,初始化this.rotation_为undefined;最后判断this.autoHide_是否为true,默认为true,就会给控件加上一个类名ol-hidden。

# Rotate类的方法

Rotate类的方法也不多,有如下三个:

  • handleClick_方法

handleClick_方法会判断this.callResetNorth是否为true,因为开发时可以通过参数options.resetNorth自定义点击事件,默认情况下,点击会执行resetNorth_方法

  • resetNorth_方法

resetNorth_方法内部就是获取当前视图的旋转角度,默认情况下会调用view.animate调整地图视图旋转角度为0;若this.duration_动画持续时长为0或者旋转的角度为360°倍数,则调用view.setRotation重置地图视图。

  • render方法

render方法会覆盖Rotate的父类Control中的render方法,该方法就是用于渲染控件的;默认情况下frameState.viewState.rotation为0,即此时地图未旋转,而this.rotation_为undefined,因此rotation不等于this.rotation_,继续判断this.autoHide,默认为true,判断控件的类名是否包含ol-hide,若不包含,且rotation为0,则给控件加上类名ol-hide,隐藏控件;反之,若控件的类名包含ol-hide且rotation不为0,则移除类名ol-hide,显示控件;然后将控件的label图标进行旋转,角度即为rotation;最后将rotation赋值给this.rotation_。

前面提到Rotate控件会跟随地图视图的旋转,继而控件图标也跟随同方向旋转同样角度,就是调用的render方法,那么render方法是如何调用的呢?

# render方法触发机制

render方法实际上是在父类Control类中调用的,在Control类一文中提过 Control类的setMap方法调用后,会判断this.render是否是个空方法,显然Rotate控件中重新定义了render方法,于是 Openlayers 会调用listen方法注册postrender类型的监听事件,而回调方法就是this.render;而调用map.render()方法进行渲染时,会调用animationDelay_方法,在该方法中又会调用renderFrame_方法,在renderFrame_方法中会执行这行代码this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));进行postrender类型的注册事件的派发,这就会执行this.render方法,从而实现Rotate控件的实时同步旋转。

# 总结

Rotate控件是基于Control类实现的,而其实时同步旋转的逻辑还是addEventListener(listen中调用注册的方法)和dispatchEvent那一套机制。

编辑 (opens new window)
上次更新: 2024/12/11, 10:30:03
Zoom缩放控件源码分析
ZoomToExtent控件源码分析

← Zoom缩放控件源码分析 ZoomToExtent控件源码分析→

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