五十六、openlayers官网示例Magnify解析——在地图上实现放大镜效果

官网demo地址:

Magnify 

 这篇讲了如何在地图上添加放大镜效果。

首先加载底图

   const layer = new TileLayer({source: new StadiaMaps({layer: "stamen_terrain_background",}),});const container = document.getElementById("map");const map = new Map({layers: [layer],target: container,view: new View({center: fromLonLat([-109, 46.5]),zoom: 6,}),});

鼠标移动的时候,调用render方法,触发postrender事件。

container.addEventListener("mousemove", function (event) {mousePosition = map.getEventPixel(event);map.render();});container.addEventListener("mouseout", function () {mousePosition = null;map.render();});

postrender事件中可以获取到鼠标移动的位置,实时绘制圆形和放大后的图像。

先用getRenderPixel将地理坐标转换为屏幕坐标,通过勾股定理(直角三角形的两条直角边的平方和等于斜边的平方)算出半径。         

 layer.on("postrender", function (event) {if (mousePosition) {const pixel = getRenderPixel(event, mousePosition);const offset = getRenderPixel(event, [mousePosition[0] + radius,mousePosition[1],]);//计算半径const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2));}});

获取放大镜范围内所需要的图像。

 //从画布上下文中提取放大镜区域的图像数据:const context = event.context;const centerX = pixel[0];const centerY = pixel[1];//正方形左边的顶点const originX = centerX - half;const originY = centerY - half;//计算直径const size = Math.round(2 * half + 1);const sourceData = context.getImageData(originX,originY,size,size).data;//获取正方形范围下所有的像素点const dest = context.createImageData(size, size);const destData = dest.data;

 然后开始创建放大后的图像数据。

// 创建放大后的图像数据for (let j = 0; j < size; ++j) {for (let i = 0; i < size; ++i) {//dI 和 dJ 是相对于中心的偏移const dI = i - half;const dJ = j - half;//点到中心的距离const dist = Math.sqrt(dI * dI + dJ * dJ);let sourceI = i;let sourceJ = j;//如果 dist 小于 half,根据偏移和缩放因子计算新的像素位置if (dist < half) {sourceI = Math.round(half + dI / 2);sourceJ = Math.round(half + dJ / 2);}const destOffset = (j * size + i) * 4;const sourceOffset = (sourceJ * size + sourceI) * 4;destData[destOffset] = sourceData[sourceOffset];destData[destOffset + 1] = sourceData[sourceOffset + 1];destData[destOffset + 2] = sourceData[sourceOffset + 2];destData[destOffset + 3] = sourceData[sourceOffset + 3];}}

要看懂这段代码我们需要来好好分析一下。

放大的关键在于 dI / 2dJ / 2 的计算。实际上是将像素距离中心点的偏移量减半,从而将像素“拉近”到中心点。放大镜区域内的像素将被集中在更小的区域内,看起来像是被放大了 。

简单来说,如果我们的圆形下本来有16个像素格子,每个格子展示不同的像素,放大效果就是让两个、三个、或者四个格子都展示同一个像素,那看起来中间部分就会比较大。

我们通过一个简单的 4x4 像素的例子来详细说明这段代码是如何实现放大镜效果的。

假设这是图像的像素点。

1  2  3  4
5  6  7  8
9  10 11 12
13 14 15 16

每个点用坐标表示就是这样:

(0,0) (1,0) (2,0) (3,0)
(0,1) (1,1) (2,1) (3,1)
(0,2) (1,2) (2,2) (3,2)
(0,3) (1,3) (2,3) (3,3)
for (let j = 0; j < size; ++j) {for (let i = 0; i < size; ++i) {const dI = i - half;const dJ = j - half;const dist = Math.sqrt(dI * dI + dJ * dJ);let sourceI = i;let sourceJ = j;if (dist < half) {sourceI = Math.round(half + dI / 2);sourceJ = Math.round(half + dJ / 2);}}}

假设 half 是 2,我们要遍历 4x4 区域的所有像素,计算每个像素在放大镜效果下的新位置。 

循环第一行 (i = 0, j = 0 到 3)
  • (0, 0)

    • dI = 0 - 2 = -2
    • dJ = 0 - 2 = -2
    • dist = Math.sqrt((-2)^2 + (-2)^2) = Math.sqrt(8) ≈ 2.83
    • 因为 dist > 2,所以 sourceI = 0sourceJ = 0
    • 拷贝 (0, 0) 位置的像素数据
  • (1, 0)

    • dI = 1 - 2 = -1
    • dJ = 0 - 2 = -2
    • dist = Math.sqrt((-1)^2 + (-2)^2) = Math.sqrt(5) ≈ 2.24
    • 因为 dist > 2,所以 sourceI = 1sourceJ = 0
    • 拷贝 (1, 0) 位置的像素数据
  • (2, 0)

    • dI = 2 - 2 = 0
    • dJ = 0 - 2 = -2
    • dist = Math.sqrt(0^2 + (-2)^2) = Math.sqrt(4) = 2
    • 因为 dist <= 2,所以 sourceI = Math.round(2 + 0 / 2) = 2
    • sourceJ = Math.round(2 + (-2) / 2) = 1
    • 拷贝 (2, 1) 位置的像素数据
  • (3, 0)

    • dI = 3 - 2 = 1
    • dJ = 0 - 2 = -2
    • dist = Math.sqrt(1^2 + (-2)^2) = Math.sqrt(5) ≈ 2.24
    • 因为 dist > 2,所以 sourceI = 3sourceJ = 0
    • 拷贝 (3, 0) 位置的像素数据
循环第二行 (i = 0, j = 1 到 3)
  • (0, 1)

    • dI = 0 - 2 = -2
    • dJ = 1 - 2 = -1
    • dist = Math.sqrt((-2)^2 + (-1)^2) = Math.sqrt(5) ≈ 2.24
    • 因为 dist > 2,所以 sourceI = 0sourceJ = 1
    • 拷贝 (0, 1) 位置的像素数据
  • (1, 1)

    • dI = 1 - 2 = -1
    • dJ = 1 - 2 = -1
    • dist = Math.sqrt((-1)^2 + (-1)^2) = Math.sqrt(2) ≈ 1.41
    • 因为 dist <= 2,所以 sourceI = Math.round(2 + (-1) / 2) = 1.5 ≈ 2
    • sourceJ = Math.round(2 + (-1) / 2) = 1.5 ≈ 2
    • 拷贝 (2, 2) 位置的像素数据
  • (2, 1)

    • dI = 2 - 2 = 0
    • dJ = 1 - 2 = -1
    • dist = Math.sqrt(0^2 + (-1)^2) = Math.sqrt(1) = 1
    • 因为 dist <= 2,所以 sourceI = Math.round(2 + 0 / 2) = 2
    • sourceJ = Math.round(2 + (-1) / 2) = 1.5 ≈ 2
    • 拷贝 (2, 2) 位置的像素数据
  • (3, 1)

    • dI = 3 - 2 = 1
    • dJ = 1 - 2 = -1
    • dist = Math.sqrt(1^2 + (-1)^2) = Math.sqrt(2) ≈ 1.41
    • 因为 dist <= 2,所以 sourceI = Math.round(2 + 1 / 2) = 2.5 ≈ 3
    • sourceJ = Math.round(2 + (-1) / 2) = 1.5 ≈ 2
    • 拷贝 (3, 2) 位置的像素数据

通过这种方式,我们得到新的像素点坐标

(0,0) (1,0) (2,1) (3,0)
(0,1) (2,2) (2,2) (3,2)
(1,2) (2,2) (2,2) (3,2)
(0,3) (2,3) (2,3) (3,3)

跟原坐标对比下:

(0,0) (1,0) (2,0) (3,0)
(0,1) (1,1) (2,1) (3,1)
(0,2) (1,2) (2,2) (3,2)
(0,3) (1,3) (2,3) (3,3)

对比之下发现(2,2)坐标下的像素由原本的一个点展示变成了四个点展示,周围的像素点也发生了一些变化,由此,中间部分就被放大了。

接下里就是把像素点放进新数组中。

const destOffset = (j * size + i) * 4;
const sourceOffset = (sourceJ * size + sourceI) * 4;
destData[destOffset] = sourceData[sourceOffset];  //r
destData[destOffset + 1] = sourceData[sourceOffset + 1];  //g
destData[destOffset + 2] = sourceData[sourceOffset + 2];  //b
destData[destOffset + 3] = sourceData[sourceOffset + 3];  //a

因为图像数据在数组中的存储规则是:

[r,g,b,a,r,g,b,a,r,g,b,a,r,g,b,a...]

因此通过计算得到像素点在数组中的位置destOffset,而sourceOffset 则是计算的偏移后的数组位置。

最后再将放大镜的圆形绘制到地图上就可以了。

  //绘制圆形 context.beginPath();context.arc(centerX, centerY, half, 0, 2 * Math.PI);context.lineWidth = (3 * half) / radius;context.strokeStyle = "rgba(255,255,255,0.5)";context.putImageData(dest, originX, originY);context.stroke();context.restore();

完整代码:

<template><div class="box"><h1>Magnify</h1><div id="map" class="map"></div></div>
</template><script>
import Map from "ol/Map.js";
import TileLayer from "ol/layer/Tile.js";
import View from "ol/View.js";
import XYZ from "ol/source/XYZ.js";
import { fromLonLat } from "ol/proj.js";
import { getRenderPixel } from "ol/render.js";
import StadiaMaps from "ol/source/StadiaMaps.js";
export default {name: "",components: {},data() {return {map: null,};},computed: {},created() {},mounted() {const layer = new TileLayer({source: new StadiaMaps({layer: "stamen_terrain_background",}),});const container = document.getElementById("map");const map = new Map({layers: [layer],target: container,view: new View({center: fromLonLat([-109, 46.5]),zoom: 6,}),});let radius = 75;document.addEventListener("keydown", function (evt) {if (evt.key === "ArrowUp") {radius = Math.min(radius + 5, 150);map.render();evt.preventDefault();} else if (evt.key === "ArrowDown") {radius = Math.max(radius - 5, 25);map.render();evt.preventDefault();}});// get the pixel position with every movelet mousePosition = null;container.addEventListener("mousemove", function (event) {mousePosition = map.getEventPixel(event);map.render();});container.addEventListener("mouseout", function () {mousePosition = null;map.render();});layer.on("postrender", function (event) {if (mousePosition) {const pixel = getRenderPixel(event, mousePosition);const offset = getRenderPixel(event, [mousePosition[0] + radius,mousePosition[1],]);//计算半径const half = Math.sqrt(Math.pow(offset[0] - pixel[0], 2) + Math.pow(offset[1] - pixel[1], 2));//从画布上下文中提取放大镜区域的图像数据:const context = event.context;const centerX = pixel[0];const centerY = pixel[1];//正方形左边的顶点const originX = centerX - half;const originY = centerY - half;//计算直径const size = Math.round(2 * half + 1);const sourceData = context.getImageData(originX,originY,size,size).data;//获取正方形范围下所有的像素点const dest = context.createImageData(size, size);const destData = dest.data;// 创建放大后的图像数据for (let j = 0; j < size; ++j) {for (let i = 0; i < size; ++i) {//dI 和 dJ 是相对于中心的偏移const dI = i - half;const dJ = j - half;//点到中心的距离const dist = Math.sqrt(dI * dI + dJ * dJ);let sourceI = i;let sourceJ = j;//如果 dist 小于 half,根据偏移和缩放因子计算新的像素位置if (dist < half) {sourceI = Math.round(half + dI / 2);sourceJ = Math.round(half + dJ / 2);}const destOffset = (j * size + i) * 4;const sourceOffset = (sourceJ * size + sourceI) * 4;destData[destOffset] = sourceData[sourceOffset];destData[destOffset + 1] = sourceData[sourceOffset + 1];destData[destOffset + 2] = sourceData[sourceOffset + 2];destData[destOffset + 3] = sourceData[sourceOffset + 3];}}//绘制圆形 context.beginPath();context.arc(centerX, centerY, half, 0, 2 * Math.PI);context.lineWidth = (3 * half) / radius;context.strokeStyle = "rgba(255,255,255,0.5)";context.putImageData(dest, originX, originY);context.stroke();context.restore();}});},methods: {},
};
</script><style lang="scss" scoped>
#map {width: 100%;height: 500px;position: relative;
}
.box {height: 100%;
}</style>

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6+Vue

ES6Vue ES6语法 ​ VUE基于是ES6的&#xff0c;所以在使用Vue之前我们需要先了解一下ES6的语法。 1.什么是ECMAScript6 ECMAScript是浏览器脚本语言的规范&#xff0c;基于javascript来制定的。为什么会出现这个规范呢&#xff1f; 1.1.JS发展史 1995年&#xff0c;网景工…

Linux中部署MySQL环境(本地安装)

进入官网&#xff1a;http://www.mysql.com 选择社区版本得到MySQL 选择对应的版本和系统进行安装 用wget进行软件包下载 wget https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.32-1.el9.x86_64.rpm-bundle.tar解压该软件包 tar -xf mysql-8.0.32-1.el9.x86_64.rpm-bu…

Rcmp: Reconstructing RDMA-Based Memory Disaggregation via CXL——论文阅读

TACO 2024 Paper CXL论文阅读笔记整理 背景 RDMA&#xff1a;RDMA是一系列协议&#xff0c;允许一台机器通过网络直接访问远程机器中的数据。RDMA协议通常固定在RDMA NIC&#xff08;RNIC&#xff09;上&#xff0c;具有高带宽&#xff08;>10 GB/s&#xff09;和微秒级延…

实验13 简单拓扑BGP配置

实验13 简单拓扑BGP配置 一、 原理描述二、 实验目的三、 实验内容四、 实验配置五、 实验步骤 一、 原理描述 BGP&#xff08;Border Gateway Protocol&#xff0c;边界网关协议&#xff09;是一种用于自治系统间的动态路由协议&#xff0c;用于在自治系统&#xff08;AS&…

聚类算法(1)---最大最小距离、C-均值算法

本篇文章是博主在人工智能等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在AI学习笔记&#…

SpringMVC系列九: 数据格式化与验证及国际化

SpringMVC 数据格式化基本介绍基本数据类型和字符串自动转换应用实例-页面演示方式Postman完成测试 特殊数据类型和字符串自动转换应用实例-页面演示方式Postman完成测试 验证及国际化概述应用实例代码实现注意事项和使用细节 注解的结合使用先看一个问题解决问题 数据类型转换…

适耳贴合的气传导耳机,带来智能生活体验,塞那Z50耳夹耳机上手

现在大家几乎每天都会用到各种AI产品&#xff0c;蓝牙耳机也是我们必不可少的装备&#xff0c;最近我发现一款很好用的分体式气传导蓝牙耳机&#xff0c;它还带有一个具备AI功能的APP端&#xff0c;大大方便了我们日常的使用。这款sanag塞那Z50耳夹耳机我用过一段时间以后&…

什么概率密度函数?

首先我们来理解一下什么是连续的随机变量&#xff0c;在此之前&#xff0c;我们要先理解什么是随机变量。所谓随机变量就是在一次随机实验中一组可能的值。比如说抛硬币&#xff0c;我们设正面100&#xff0c;反面200&#xff0c;设随机变量为X&#xff0c;那么X{100,200}。 X是…

Introduction to linear optimization 第 2 章课后题答案 11-15

线性规划导论 Introduction to linear optimization (Dimitris Bertsimas and John N. Tsitsiklis, Athena Scientific, 1997)&#xff0c; 这本书的课后题答案我整理成了一个 Jupyter book&#xff0c;发布在网址&#xff1a; https://robinchen121.github.io/manual-introdu…

python循环结构

1.while 循环 语句&#xff1a; while 循环条件表达式&#xff1a; 代码块 else&#xff1a; 代码块 小练&#xff1a; 设计一百以内的偶数相加 n 0 while n < 100:n 1if n % 2 0 :print(n) 判断是不是闰年&#xff08;四年一润和百年不润&#xff0c;或者四百年一润&am…

高效22KW双向DCDC储能、充电电源模块项目设计开发

22kW 双向CLL谐振变换器的目标是输出电压范围宽、高效率和高功率密度的双向应用&#xff0c;如电动汽车车载充电器和储能系统。研究了一种新的灵活的 CLLC 双向谐振变换器增益控制方案&#xff0c;以便在充放电模式下实现高效率和宽电压增益范围。得益于 Wolfspeed C3MTM 1200V…

简单好用的C++日志库spdlog使用示例

文章目录 前言一、spdlog的日志风格fmt风格printf风格 二、日志格式pattern三、sink&#xff0c;多端写入四、异步写入五、注意事项六、自己封装了的代码usespdlog.h封装代码解释使用示例 前言 C日志库有很多&#xff0c;glog&#xff0c;log4cpp&#xff0c;easylogging, eas…

Unity核心

回顾 Unity核心学习的主要内容 项目展示 基础知识 认识模型制作流程 2D相关 图片导入设置相关 图片导入概述 参数设置——纹理类型 参数设置——纹理形状 参数设置——高级设置 参数设置——平铺拉伸 参数设置——平台设置&#xff08;非常重要&#xff09; Sprite Sprite Edit…

解两道四年级奥数题(等差数列)玩玩

1、1&#xff5e;200这200个连续自然数的全部数字之和是________。 2、2&#xff0c;4&#xff0c;6&#xff0c;……&#xff0c;2008这些偶数的所有各位数字之和是________。 这两道题算易错吧&#xff0c;这里求数字之和&#xff0c;比如124这个数的全部数字之和是1247。 …

ffmpeg音视频开发从入门到精通——ffmpeg 视频数据抽取

文章目录 FFmpeg视频处理工具使用总结环境配置主函数与参数处理打开输入文件获取流信息分配输出文件上下文猜测输出文件格式创建视频流并设置参数打开输出文件并写入头信息读取、转换并写入帧数据写入尾信息并释放资源运行程序注意事项源代码 FFmpeg视频处理工具使用总结 环境…

网络安全:Web 安全 面试题.(SQL注入)

网络安全&#xff1a;Web 安全 面试题.&#xff08;SQL注入&#xff09; 网络安全面试是指在招聘过程中,面试官会针对应聘者的网络安全相关知识和技能进行评估和考察。这种面试通常包括以下几个方面&#xff1a; &#xff08;1&#xff09;基础知识:包括网络基础知识、操作系…

Vue69-路由基本使用

一、需求 二、开发步骤 2-1、路由的安装 vue-router3才能在vue2中使用&#xff01;现在默认是vue-router4版本&#xff0c;要在vue3中使用&#xff01;所以&#xff0c;安装的时候要指定版本。 2-2、在main.js中引入和使用路由 2-3、创建router文件夹 一般在vue中用了vue-ro…

外包IT运维解决方案

随着企业信息化进程的不断深入&#xff0c;IT系统的复杂性和重要性日益增加。高效的IT运维服务对于保证业务连续性、提升企业竞争力至关重要。外包IT运维解决方案通过专业的服务和技术支持&#xff0c;帮助企业降低运维成本、提高运维效率和服务质量。 本文结合《外包IT运维解…

会自动清除的文件——tempfile

原文链接&#xff1a;http://www.juzicode.com/python-tutorial-tempfile/ 在某些不需要持久保存文件的场景下&#xff0c;可以用tempfile模块生成临时文件或者文件夹&#xff0c;这些临时文件或者文件夹在使用完之后就会自动删除。 NamedTemporaryFile用来创建临时文件&…

计算机组成原理 | 数据的表示、运算和校验(3)数据处理与存储

移位 舍入和扩展 存储模式和对齐 不按边界对齐&#xff0c;访存次数会增加一次