源码分析之Openlayers中ZoomSlider滑块缩放控件

概述

ZoomSlider滑块缩放控件就是Zoom缩放控件的异形体,通过滑块的拖动或者点击滑槽,实现地图的缩放;另外其他方式控制地图缩放时,也会引起滑块在滑槽中的位置改变;即ZoomSlider滑块缩放控件会监听地图的缩放级别,当级别发生改变时,也会触发ZoomSlider中注册的事件,从而改变滑块的相对位置。

本文主要介绍 Openlayers 中ZoomSlider滑块缩放控件的源码实现和核心逻辑分析。

源码分析

ZoomSlider源码实现

ZoomSlider类控件继承于Control类,关于Control类,可以参考这篇文章源码分析之Openlayers中的控件篇Control基类介绍。

ZoomSlider类的源码如下:

class ZoomSlider extends Control {
  constructor(options) {
    options = options ? options : {};

    super({
      target: options.target,
      element: document.createElement("div"),
      render: options.render,
    });

    this.dragListenerKeys_ = [];

    this.currentResolution_ = undefined;

    this.direction_ = Direction.VERTICAL;

    this.dragging_;

    this.heightLimit_ = 0;

    this.widthLimit_ = 0;

    this.startX_;

    this.startY_;

    this.thumbSize_ = null;

    this.sliderInitialized_ = false;

    this.duration_ = options.duration !== undefined ? options.duration : 200;

    const className =
      options.className !== undefined ? options.className : "ol-zoomslider";
    const thumbElement = document.createElement("button");
    thumbElement.setAttribute("type", "button");
    thumbElement.className = className + "-thumb " + CLASS_UNSELECTABLE;
    const containerElement = this.element;
    containerElement.className =
      className + " " + CLASS_UNSELECTABLE + " " + CLASS_CONTROL;
    containerElement.appendChild(thumbElement);

    containerElement.addEventListener(
      PointerEventType.POINTERDOWN,
      this.handleDraggerStart_.bind(this),
      false
    );
    containerElement.addEventListener(
      PointerEventType.POINTERMOVE,
      this.handleDraggerDrag_.bind(this),
      false
    );
    containerElement.addEventListener(
      PointerEventType.POINTERUP,
      this.handleDraggerEnd_.bind(this),
      false
    );

    containerElement.addEventListener(
      EventType.CLICK,
      this.handleContainerClick_.bind(this),
      false
    );
    thumbElement.addEventListener(EventType.CLICK, stopPropagation, false);
  }

  setMap(map) {
    super.setMap(map);
    if (map) {
      map.render();
    }
  }

  initSlider_() {
    const container = this.element;
    let containerWidth = container.offsetWidth;
    let containerHeight = container.offsetHeight;
    if (containerWidth === 0 && containerHeight === 0) {
      return (this.sliderInitialized_ = false);
    }

    const containerStyle = getComputedStyle(container);
    containerWidth -=
      parseFloat(containerStyle["paddingRight"]) +
      parseFloat(containerStyle["paddingLeft"]);
    containerHeight -=
      parseFloat(containerStyle["paddingTop"]) +
      parseFloat(containerStyle["paddingBottom"]);
    const thumb = /** @type {HTMLElement} */ (container.firstElementChild);
    const thumbStyle = getComputedStyle(thumb);
    const thumbWidth =
      thumb.offsetWidth +
      parseFloat(thumbStyle["marginRight"]) +
      parseFloat(thumbStyle["marginLeft"]);
    const thumbHeight =
      thumb.offsetHeight +
      parseFloat(thumbStyle["marginTop"]) +
      parseFloat(thumbStyle["marginBottom"]);
    this.thumbSize_ = [thumbWidth, thumbHeight];

    if (containerWidth > containerHeight) {
      this.direction_ = Direction.HORIZONTAL;
      this.widthLimit_ = containerWidth - thumbWidth;
    } else {
      this.direction_ = Direction.VERTICAL;
      this.heightLimit_ = containerHeight - thumbHeight;
    }
    return (this.sliderInitialized_ = true);
  }

  handleContainerClick_(event) {
    const view = this.getMap().getView();

    const relativePosition = this.getRelativePosition_(
      event.offsetX - this.thumbSize_[0] / 2,
      event.offsetY - this.thumbSize_[1] / 2
    );

    const resolution = this.getResolutionForPosition_(relativePosition);
    const zoom = view.getConstrainedZoom(view.getZoomForResolution(resolution));

    view.animateInternal({
      zoom: zoom,
      duration: this.duration_,
      easing: easeOut,
    });
  }

  handleDraggerStart_(event) {
    if (!this.dragging_ && event.target === this.element.firstElementChild) {
      const element = /** @type {HTMLElement} */ (
        this.element.firstElementChild
      );
      this.getMap().getView().beginInteraction();
      this.startX_ = event.clientX - parseFloat(element.style.left);
      this.startY_ = event.clientY - parseFloat(element.style.top);
      this.dragging_ = true;

      if (this.dragListenerKeys_.length === 0) {
        const drag = this.handleDraggerDrag_;
        const end = this.handleDraggerEnd_;
        const doc = this.getMap().getOwnerDocument();
        this.dragListenerKeys_.push(
          listen(doc, PointerEventType.POINTERMOVE, drag, this),
          listen(doc, PointerEventType.POINTERUP, end, this)
        );
      }
    }
  }

  handleDraggerDrag_(event) {
    if (this.dragging_) {
      const deltaX = event.clientX - this.startX_;
      const deltaY = event.clientY - this.startY_;
      const relativePosition = this.getRelativePosition_(deltaX, deltaY);
      this.currentResolution_ =
        this.getResolutionForPosition_(relativePosition);
      this.getMap().getView().setResolution(this.currentResolution_);
    }
  }

  handleDraggerEnd_(event) {
    if (this.dragging_) {
      const view = this.getMap().getView();
      view.endInteraction();

      this.dragging_ = false;
      this.startX_ = undefined;
      this.startY_ = undefined;
      this.dragListenerKeys_.forEach(unlistenByKey);
      this.dragListenerKeys_.length = 0;
    }
  }

  setThumbPosition_(res) {
    const position = this.getPositionForResolution_(res);
    const thumb = /** @type {HTMLElement} */ (this.element.firstElementChild);

    if (this.direction_ == Direction.HORIZONTAL) {
      thumb.style.left = this.widthLimit_ * position + "px";
    } else {
      thumb.style.top = this.heightLimit_ * position + "px";
    }
  }

  getRelativePosition_(x, y) {
    let amount;
    if (this.direction_ === Direction.HORIZONTAL) {
      amount = x / this.widthLimit_;
    } else {
      amount = y / this.heightLimit_;
    }
    return clamp(amount, 0, 1);
  }

  getResolutionForPosition_(position) {
    const fn = this.getMap().getView().getResolutionForValueFunction();
    return fn(1 - position);
  }

  getPositionForResolution_(res) {
    const fn = this.getMap().getView().getValueForResolutionFunction();
    return clamp(1 - fn(res), 0, 1);
  }

  render(mapEvent) {
    if (!mapEvent.frameState) {
      return;
    }
    if (!this.sliderInitialized_ && !this.initSlider_()) {
      return;
    }
    const res = mapEvent.frameState.viewState.resolution;
    this.currentResolution_ = res;
    this.setThumbPosition_(res);
  }
}

ZoomSlider构造函数

ZoomSlider构造函数接受的参数对象options除了包含常规的控件属性rendertargetclassName外,还有个属性duration,不传的话,该属性值默认为200毫秒,表示地图视图动画的持续时长。

ZoomSlider构造函数除了创建控件元素外,还给控件元素添加了几个监听事件,如下:

//鼠标按键按下时触发,pointerdown相当于mousedown
containerElement.addEventListener(
  PointerEventType.POINTERDOWN,
  this.handleDraggerStart_.bind(this),
  false
);

//鼠标按键移动时触发,pointermove相当于mousemove
containerElement.addEventListener(
  PointerEventType.POINTERMOVE,
  this.handleDraggerDrag_.bind(this),
  false
);

//鼠标按键抬起时触发,pointerup相当于mouseup
containerElement.addEventListener(
  PointerEventType.POINTERUP,
  this.handleDraggerEnd_.bind(this),
  false
);

ZoomSlider主要方法

  • setMap方法:这个方法就是调用父类的setMap方法,然后判断,若map存在,则调用map.render,这个操作着实有点多余,因为父类中也有这个逻辑.

  • initSlider_方法:滑动缩放控件可以是水平方向也可以是垂直方向,这个方法就是初始化滑动控件的显示,确保滑块滑动时始终在滑槽内.

  • render方法:render方法主要用于更新滑块的位置,当地图的postrender类型触发时,会执行这个函数;获取当前地图视图状态的分辨率,调用setThumbPosition设置滑块的位置.

  • getPositionForResolution_方法:获取给定的分辨率下,滑块的相对位置

  • getResolutionForPosition_方法:通过滑块的相对位置,计算出相对应的分辨率

  • getRelativePosition_方法:通过xy计算出相对位置,该值在[0,1]之间

  • setThumbPosition_方法:该方法作用就是用于设置滑块的相对位置,通过当前地图视图的分辨率计算出滑块的相对偏移值,然后设置其lefttop属性值

  • handleDraggerEnd_方法

在构造函数中初始化了一个全局变量this.dragging_,用来标识当前滑块是否处于拖动状态;当鼠标停止拖动抬起时,会触发该方法;该方法内部会先判断,若this.dragging_true,则表明前一刻的鼠标是拖动状态,会先结束地图视图的交互,然后重置一些状态变量this.dragging_,this.startX_this.startY_,最后清除一些在拖动开始时注册的监听;否则不是,不执行任何逻辑.

  • handleDraggerDrag_方法

handleDraggerDrag_方法会在鼠标拖动滑块时调用,同样地,会先判断,若this.dragging_true,则计算出滑块的相对偏移值,然后根据偏移值调用this.getRelativePosition获取相对的位置偏移量,再通过this.getResolutionForPosition_得出当前得分辨率,最后调用地图视图的setResolution设置地图的分辨率,这就实现了拖动滑块时地图实时进行缩放动作的效果.

  • handleDraggerStart_方法

handleDraggerStart_方法就是在滑块拖动时进行一些初始化操作,设置一些状态量,以及调用beginInteraction开始交互,还会给地图容器注册一些鼠标移动和抬起的监听,这在触屏设备有用.

  • handleContainerClick_方法

ZoomSlider滑块缩放控件除了拖动滑块可以实现地图的缩放,还可以通过点击滑槽实现地图的缩放.后者的功能就是handleContainerClick_方法提供的.该方法内部就是先获取点击位置的坐标,然后通过该坐标计算出相对位置,再通过相对位置调用this.getResolutionForPosition计算出相对分辨率,然后调用view.getZoomForResolution获取缩放级别,最后调用view.animateInternal设置地图的缩放级别,这和滑块拖动缩放的最后调用的方法不同,这种会有动画效果.

总结

本文主要介绍了 Openlayers 中ZoomSlider滑块缩放控件的实现,主要是滑块在滑槽中的相对位置对应着当前地图的分辨率在分辨率区间的映射关系,这一关系可以基于view通过计算所得.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/940917.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ARM 处理器平台 Ethernet Compliance 测试流程示例

By Toradex秦海 1). 简介 为了保证基于IEEE 802.3 协议设计的以太网设备接口可以互相兼容互联互通,需要进行 Ethernet Compliance 一致性测试,相关的技术原理说明请参考如下文章,本文就不赘述,主要展示基于 NXP i.MX8M Mini ARM…

门控循环单元(GRU):深度学习中的序列数据处理利器

目录 ​编辑 引言 GRU的诞生背景 GRU的核心机制 GRU的计算过程 GRU的数学公式 GRU的应用领域 代码示例:PyTorch中的GRU GRU与LSTM的比较 参数比较 GRU的技术发展 BiGRU(双向GRU) BiGRU的实现示例 GRU与CNN的结合 GRU的应用案例…

C#都可以找哪些工作?

在国内学习C#,可以找的工作主要是以下4个: 1、游戏开发 需要学习C#编程、Unity引擎操作、游戏设计和3D图形处理等。 2、PC桌面应用开发 需要学习C#编程、WinForm框架/WPF框架、MVVM设计模式和UI/UX设计等。 3、Web开发 需要学习C#编程、ASP.NET框架…

视频直播点播平台EasyDSS与无人机技术的森林防火融合应用

随着科技的飞速发展,无人机技术以其独特的优势在各个领域得到了广泛应用,特别是在森林防火这一关键领域,EasyDSS视频平台与无人机技术的融合应用更是为传统森林防火手段带来很大的变化。 一、无人机技术在森林防火中的优势 ‌1、快速响应与高…

【编译原理】编译原理知识点汇总·词法分析器(正则式到NFA、NFA到DFA、DFA最小化)

🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀编译原理_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

SAP抓取外部https报错SSL handshake处理方法

一、问题描述 SAP执行报表抓取https第三方数据,数据获取失败。 报错消息: SSL handshake with XXX.COM:449 failed: SSSLERR_SSL_READ (-58)#SAPCRYPTO:SSL_read() failed##SapSSLSessionStartNB()==SSSLERR_SSL_READ# SSL:SSL_read() failed (536875120/0x20001070)# …

java栈

前言 java实现数据结构栈:用顺序表存储的栈和数组存储的栈。 本文源代码网址:https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/stack https://gitee.com/zfranklin/java/tree/master/dataStructure/src/com/njupt/stack 栈…

「Mac畅玩鸿蒙与硬件45」UI互动应用篇22 - 评分统计工具

本篇将带你实现一个评分统计工具,用户可以对多个选项进行评分。应用会实时更新每个选项的评分结果,并统计平均分。这一功能适合用于问卷调查或评分统计的场景。 关键词 UI互动应用评分统计状态管理数据处理多目标评分 一、功能说明 评分统计工具允许用…

使用 AI 辅助开发一个开源 IP 信息查询工具:一

本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 写在前面 在写代码时,总是会遇到一些有趣的机缘巧合。前几天,我在翻看自己之前的开源项目时,又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程&am…

高效处理PDF文件的终极工具:构建一个多功能PDF转换器

在日常工作中,处理PDF文件几乎是每个人都不可避免的任务。无论是从PDF中提取数据、合并多个PDF文件,还是处理文件中的敏感信息和图像,PDF文件的处理都可能成为繁琐且耗时的工作。如果你是数据分析师、工程师,或者从事文档管理的工…

差分矩阵(Difference Matrix)与累计和矩阵(Running Sum Matrix)的概念与应用:中英双语

本文是学习这本书的笔记: https://web.stanford.edu/~boyd/vmls/ 差分矩阵(Difference Matrix)与累计和矩阵(Running Sum Matrix)的概念与应用 在线性代数和信号处理等领域中,矩阵运算常被用来表示和计算各种数据变换…

【java面向对象编程】第七弹----Object类、类变量与类方法

笔上得来终觉浅,绝知此事要躬行 🔥 个人主页:星云爱编程 🔥 所属专栏:javase 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 目录 一、Object类 1.1equa…

zookeeper分布式锁模拟12306买票

未加锁时容易出现重复买票情况 代码 public class Ticket12306 implements Runnable{// 票数private int ticketNums 10;Overridepublic void run() {while (true){if (ticketNums>0){System.out.println(Thread.currentThread() "抢到了第" ticketNums &qu…

iterm2 focus时灰色蒙层出现的解决办法

问题描述: 当前我的iterm2版本是3.5.10,是我最近才更新的,然后就出现以下页面显示问题,如图所示: 我个人对终端、编辑器等使用存在洁癖,尤其是页面显示效果不满意更是不能忍受,之前找了很久没有…

【Qt】输入类控件:QLineEdit、QTextEdit、QComboBox、QSpinBox、QDateTimeEdit、QDial、QSlider

目录 QLineEdit 例子: 正则表达式对象、验证器对象 例子: 例子: QTextEdit 例子: QComboBox 例子: QSpinBox 例子: QDateTimeEdit 例子: QDial 例子: QSlider 例子&…

【HarmonyOS应用开发】购物商城的实现【合集】

目录 😋环境配置:华为HarmonyOS开发者 📺演示效果: 📖实验步骤及方法: 1. 在src/main/ets文件中创建components文件夹并在其中创建Home.ets和HomeProduct.ets文件。​编辑 2. 在Home.ets文件中定义 Ho…

智能体实战(需求分析助手)二、需求分析助手第一版实现(支持需求提取、整理、痛点分析、需求分类、优先级分析、需求文档生成等功能)

基于提供的调用 qwen-plus 大模型的实战代码,我将对需求分析助手的第一迭代功能目标进行实现设计。以下是基于该示例代码的第一迭代功能实现细化方案: 功能 1:用户与需求分析助手交互界面(文本交互) 实现步骤&#xf…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是,则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时,Mihomo网卡会消失,所以提示找不到网卡…

malloc 分配大堆块(128KB)的一次探索

前言 一次意外执行了 malloc(0x5000)&#xff0c;结构使用 gdb 调试发现其分配的位置在 TLS 区域&#xff0c;这令我不解&#xff08;&#xff1a;最后去看了下 malloc 源码和 mmap 源码实现&#xff0c;发现似乎可能是 gdb 插件的问题&#xff0c;乐 场景复现 #include <…

大数据机器学习算法和计算机视觉应用07:机器学习

Machine Learning Goal of Machine LearningLinear ClassificationSolutionNumerical output example: linear regressionStochastic Gradient DescentMatrix Acceleration Goal of Machine Learning 机器学习的目标 假设现在有一组数据 x i , y i {x_i,y_i} xi​,yi​&…