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

news2024/12/23 7:15:35

概述

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通过计算所得.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[文献阅读] Unsupervised Deep Embedding for Clustering Analysis (无监督的深度嵌入式聚类)

文章目录 Abstract:摘要聚类深度聚类 KL散度深度嵌入式聚类(DEC)KL散度聚类软分配(soft assignment)KL散度损失训练编码器的初始化聚类中心的初始化 实验评估总结 Abstract: This week I read Unsupervised Deep Embedding for Clustering Analysis .It…

记录:virt-manager配置Ubuntu arm虚拟机

virt-manager(Virtual Machine Manager)是一个图形用户界面应用程序,通过libvirt管理虚拟机(即作为libvirt的图形前端) 因为要在Linux arm环境做测试,记录下virt-manager配置arm虚拟机的过程 先在VMWare中…

使用C语言编写UDP循环接收并打印消息的程序

使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…

Spring Boot 教程之三十六:实现身份验证

如何在 Spring Boot 中实现简单的身份验证? 在本文中,我们将学习如何使用 Spring设置和配置基本身份验证。身份验证是任何类型的安全性中的主要步骤之一。Spring 提供依赖项,即Spring Security,可帮助在 API 上建立身份验证。有很…

什么样的LabVIEW控制算自动控制?

自动控制是指系统通过预先设计的算法和逻辑,在无人工干预的情况下对被控对象的状态进行实时监测、决策和调整,达到预期目标的过程。LabVIEW作为一种图形化编程工具,非常适合开发自动控制系统。那么,什么样的LabVIEW控制算作“自动…

GFPS扩展技术原理(七)-音频切换消息流

音频切换消息流 Seeker和Provider通过消息流来同步音频切换能力,触发连接做切换,获取或设置音频切换偏好,通知连接状态等等。为此专门定义了音频切换消息流Message Group 为0x07,Message codes如下: MAC of Audio s…

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

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

机器人路径规划和避障算法matlab仿真,分别对比贪婪搜索,最安全距离,RPM以及RRT四种算法

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1贪婪搜索算法原理 4.2最安全距离算法原理 4.3RPM 算法原理 4.4 RRT 算法原理 5.完整程序 1.程序功能描述 机器人路径规划和避障算法matlab仿真,分别对比贪婪搜索,最安全距离,RPM以及R…

【论文笔记】Visual Alignment Pre-training for Sign Language Translation

🍎个人主页:小嗷犬的个人主页 🍊个人网站:小嗷犬的技术小站 🥭个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。 基本信息 标题: Visual Alignment Pre-tra…

【附源码】Electron Windows桌面壁纸开发中的 CommonJS 和 ES Module 引入问题以及 Webpack 如何处理这种兼容

背景 在尝试让 ChatGPT 自动开发一个桌面壁纸更改的功能时,发现引入了一个 wallpaper 库,这个库的入口文件是 index.js,但是 package.json 文件下的 type:"module",这样造成了无论你使用 import from 还是 require&…

Apache解析漏洞(apache_parsingCVE-2017-15715)

apache_parsing 到浏览器中访问网站 http://8.155.8.239:81/ 我们写一个木马 1.php.jpg 我们将写好的木马上传 会得到我们上传文件的路径 我们访问一下 发现上传成功 发现木马运行成功,接下来使用蚁剑连接我们的图片马 获取 shell 成功 CVE-2013-454 我们还是到…

C++-----函数与库

数学中的函数与编程中的函数对比 数学中的函数 - 数学函数是一种映射关系,例如,函数\(y f(x)x^{2}\),对于每一个输入值\(x\),都有唯一确定的输出值\(y\)。它侧重于描述变量之间的数量关系,通常通过公式来表示这种关系…

带着国标充电器出国怎么办? 适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern) 适配器模式适配器模式(Adapter Pattern)概述talk is cheap, show you my code总结 适配器模式 适配器模式(Adapter Pattern)是面向对象软件设计中的一种结构型设计…

SKETCHPAD——允许语言模型生成中间草图,在几何、函数、图算法和游戏策略等所有数学任务中持续提高基础模型的性能

概述 论文地址:https://arxiv.org/pdf/2406.09403 素描是一种应用广泛的有效工具,包括产生创意和解决问题。由于素描能直接传达无法用语言表达的视觉和空间信息,因此从古代岩画到现代建筑图纸,素描在世界各地被用于各种用途。儿童…

初等函数整理

1.幂函数 2.指数函数 3.对数函数

【C/C++】手搓项目中常用小工具:日志、sqlit数据库、Split切割、UUID唯一标识

每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论​: 本章将写到一些手搓常用工具,方便在项目中的使用,并且在手搓的过程中一些函数如:日志 宏中的__VA_ARGS__接收可变参…

路径规划之启发式算法之二十一:狼群算法(Wolf Pack Algorithm,WPA)

狼群算法(Wolf Pack Algorithm,WPA)是一种模拟狼群捕食行为及其猎物分配方式的群体智能优化算法。它由吴虎胜等人在2013年提出,算法采用了基于人工狼主体的自下而上的设计方法和基于职责分工的协作式搜索路径结构。它通过抽象狼群搜索、围攻以及更新换代的三种行为方式来实…

Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门任务创建【入门二】

继上一篇的hello world: 【Linux下基于最新稳定版ESP-IDF5.3.2开发esp32s3入门hello world输出【入门一】-CSDN博客】 这一篇我们开始任务的创建。 工程还是用上一篇的hello world作为模板,hello world就不再app_main函数中输出,改成在任务…

用音乐与自我对话 ——澄迈漓岛音乐节x草台回声

四季循环,昼夜往复,在相对恒定的日常中,音乐是扇打量世界又内观本心的双向窗户。难以描述的触动,透过音乐语言转换为温热且真实的吟唱,一次又一次记录与释放。 除却生浪主舞台中的声音玩具乐队以及STOLEN秘密行动&…

基础元器件的学习

1、二极管 1.1二极管的符号 ZD是稳压二极管 VD、V、D是普通二极管的符号。 1.2二极管的反向恢复时间 首先交流电为上正下负,然后下正上负。当二极管接到反向电压,二极管存在寄生电容,电压不能立刻突变,当输入频率变高时&#…