源码分析之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
除了包含常规的控件属性render
、target
和className
外,还有个属性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_
方法:通过x
和y
计算出相对位置,该值在[0,1]
之间 -
setThumbPosition_
方法:该方法作用就是用于设置滑块的相对位置,通过当前地图视图的分辨率计算出滑块的相对偏移值,然后设置其left
或top
属性值 -
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
通过计算所得.
相关文章:
源码分析之Openlayers中ZoomSlider滑块缩放控件
概述 ZoomSlider滑块缩放控件就是Zoom缩放控件的异形体,通过滑块的拖动或者点击滑槽,实现地图的缩放;另外其他方式控制地图缩放时,也会引起滑块在滑槽中的位置改变;即ZoomSlider滑块缩放控件会监听地图的缩放级别&…...

在Win11系统上安装Android Studio
诸神缄默不语-个人CSDN博文目录 下载地址:https://developer.android.google.cn/studio?hlzh-cn 官方安装教程:https://developer.android.google.cn/studio/install?hlzh-cn 点击Next,默认会同时安装Android Studio和Android虚拟机&#…...

华为ensp--BGP路径选择-AS_Path
学习新思想,争做新青年,今天学习的是BGP路径选择-AS_Path 实验目的: 理解AS_Path属性的概念 理解通过AS_Path属性进行选路的机制 掌握修改AS_Path属性的方法 实验内容: 本实验模拟了一个运营商网络场景,所有路由器都运行BGP协议ÿ…...
Android Java Ubuntu系统如何编译出 libopencv_java4.so
Cmake: cd ~ wget https://github.com/Kitware/CMake/releases/download/v3.30.3/cmake-3.30.3-linux-x86_64.tar.gztar -xzvf cmake-3.30.3-linux-x86_64.tar.gz sudo ln -sf $(pwd)/cmake-3.30.3-linux-x86_64/bin/* /usr/bin/cmake --versionAndroid NDK: wget https://…...

WPF Binding 绑定
绑定是 wpf 开发中的精髓,有绑定才有所谓的数据驱动。 1 . 背景 目前 wpf 界面可视化的控件,继承关系如下, 控件的数据绑定,基本上都要借助于 FrameworkElement 的 DataContext 属性。 只有先设置了控件的 DataContext 属性&…...

算法笔记—前缀和(动态规划)
【模板】前缀和_牛客题霸_牛客网 (nowcoder.com) #include <initializer_list> #include <iostream> #include <vector> using namespace std;int main() {//输入数据int n,q;cin>>n>>q;vector<int> arr;arr.resize(n1);for(int i1;i<…...
将HTML转换为PDF:使用Spire.Doc的详细指南(二)无水印版
目录 引言 一、准备工作 1. 下载Spire.Doc for Java破解版 2. 将JAR包安装到本地Maven (1) 打开命令提示符 (2) 输入安装命令 (3) 在pom.xml中导入依赖 二、实现HTML到PDF的转换 1. 创建Java类 2. 完整代码示例 3. 代码解析 4. 处理图像 5. 性能优化 6. 错误处理…...
V900新功能-电脑不在旁边,通过手机给PLC远程调试网关配置WIFI联网
您使用BDZL-V900时,是否遇到过以下这种问题? 去现场配置WIFI发现没带电脑,无法联网❌ 首次配置WIFI时需使用网线连电脑,不够快捷❌ 而博达智联为解决该类问题,专研了一款网关配网工具,实现用户现场使用手机…...

prober.php探针
raw.githubusercontent.com/kmvan/x-prober/master/dist/prober.php...

esp8266_TFTST7735语音识别UI界面虚拟小助手
文章目录 一 实现思路1 项目简介1.1 项目效果1.2 实现方式 2 项目构成2.1 软硬件环境2.2 完整流程总结(重点整合)(1) 功能逻辑图(2) 接线(3) 使用esp8266控制TFT屏(4)TFT_espI库配置方法(5) TFT_esp库常用代码详解(6)TFT屏显示图片(7) TFT屏显示汉字(8) …...

【CSS in Depth 2 精译_086】14.3:CSS 剪切路径(clip-path)的用法
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第四部分 视觉增强技术 ✔️【第 14 章 蒙版、形状与剪切】 ✔️ 14.1 滤镜 14.1.1 滤镜的类型14.1.2 背景滤镜 14.2 蒙版 14.2.1 带渐变效果的蒙版特效14.2.2 基于亮度来定义蒙版14.2.3 其他蒙版属…...

【服务器】MyBatis是如何在java中使用并进行分页的?
MyBatis 是一个支持普通 SQL 查询、存储过程和高级映射的持久层框架。它消除了几乎所有的 JDBC 代码和参数的手动设置以及结果集的检索。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 …...

vue 文本域 展示的内容格式要和填写时保持一致
文本域 展示的内容格式要和填写时保持一致 <el-inputtype"textarea":rows"5"placeholder"请输入内容"v-model"formCredit.point"style"width:1010px;" > </el-input> 样式加个: white-space: pre-w…...

linux-----进程及基本操作
进程的基本概念 定义:在Linux系统中,进程是正在执行的一个程序实例,它是资源分配和调度的基本单位。每个进程都有自己独立的地址空间、数据段、代码段、栈以及一组系统资源(如文件描述符、内存等)。进程的组成部分&am…...

[Python学习日记-73] 面向对象实战1——答题系统
[Python学习日记-73] 面向对象实战1——答题系统 简介 需求模型——5w1h8c 领域模型 设计模型 实现模型 案例:年会答题系统 简介 在学习完面向对象之后你会发现,你还是不会自己做软件做系统,这是非常正常的,这是因为计算机软…...

Win10将WindowsTerminal设置默认终端并添加到右键(无法使用微软商店)
由于公司内网限制,无法通过微软商店安装 Windows Terminal,本指南提供手动安装和配置新版 Windows Terminal 的步骤,并添加右键菜单快捷方式。 1. 下载新版终端安装包: 访问 Windows Terminal 的 GitHub 发布页面:https://githu…...

AOI外观缺陷检测机
主要功能: 快速检测产品装配缺陷,包括螺丝、元器件、端子排线、二维码、一维条码、识别读码、产品外观 Logo缺陷以及产品标签、字符缺陷检测等产品的缺陷检测。 设备优势:1.采用轻型可移动支架,可以快速对接产线工艺工序&am…...

精读 84页华为BLM战略规划方法论
这篇文档主要介绍了华为的BLM战略规划方法论,该方法论旨在帮助企业制定战略规划,并确保战略规划的可执行性和有效性。以下是该文档的核心知识点和重点需要关注的内容: 战略规划的定义:战略规划是企业依据企业外部环境和企业自身的…...
工业摄像机基于电荷耦合器件的相机
工业摄像机系列产品及其识别技术的详细介绍: 一、工业摄像机概述 工业摄像机是利用光学成像技术获取视觉信息,并通过图像处理算法分析这些信息的设备。它通常具有高图像稳定性、高传输能力和高抗干扰能力等特性,适用于各种复杂的工业环境。 …...
13.罗意文面试
1、工程化与架构设计(考察项目管理和架构能力) 1.1 你负责的可视化编排项目中,如何设计组件的数据结构来支持"拖拉拽"功能?如何处理组件间的联动关系? // 组件数据结构示例 {components: [{id: comp1,type…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...

Ubuntu Cursor升级成v1.0
0. 当前版本低 使用当前 Cursor v0.50时 GitHub Copilot Chat 打不开,快捷键也不好用,当看到 Cursor 升级后,还是蛮高兴的 1. 下载 Cursor 下载地址:https://www.cursor.com/cn/downloads 点击下载 Linux (x64) ,…...