源码分析之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…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
day36-多路IO复用
一、基本概念 (服务器多客户端模型) 定义:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力 作用:应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标…...
