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

news2024/11/24 14:08:11

官网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 move
    let 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.coloradmin.cn/o/1849826.html

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

相关文章

对于初学者,该如何选择大模型框架 LlamaIndex 与 LangChain ?

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

内容安全复习 6 - 白帽子安全漏洞挖掘披露的法律风险

文章目录 安全漏洞的法律概念界定安全漏洞特征白帽子安全漏洞挖掘面临的法律风险“白帽子”安全漏洞挖掘的风险根源“白帽子”的主体边界授权行为边界关键结论 安全漏洞的法律概念界定 可以被利用来破坏所在系统的网络或信息安全的缺陷或错误&#xff1b;被利用的网络缺陷、错…

python例子:翻译器(简单)

作品介绍 作品名称&#xff1a;翻译器 开发环境&#xff1a;PyCharm 2023.3.4 python3.7 用到的库&#xff1a;PyQt5、translate、sys 作品简介&#xff1a;“输入内容”输入要翻译的中文内容&#xff0c;“选择语言”选择要翻译的语种&#xff0c;最后点击“开始翻译”&a…

Python | Leetcode Python题解之第165题比较版本号

题目&#xff1a; 题解&#xff1a; class Solution:def compareVersion(self, version1: str, version2: str) -> int:n, m len(version1), len(version2)i, j 0, 0while i < n or j < m:x 0while i < n and version1[i] ! .:x x * 10 ord(version1[i]) - o…

SHA256 安全散列算法加速器实验

1、SHA256 介绍 SHA256 加速器是用来计算 SHA-256 的计算单元&#xff0c; SHA256 是 SHA-2 下细分出的一种算法。 SHA-2 名称来自于安全散列算法 2 &#xff08;英语&#xff1a; Secure Hash Algorithm 2 &#xff09;的缩写&#xff0c;一种密码散列函 数算法标准…

你只是重新发现了一些东西

指北君关于另外一条思维路径的发现。 "自以为是"的顿悟时刻 有很多时候&#xff0c;我会"自以为是"的发现/发明一些东西。这种"自以为是"的时刻通常还带有一些骄傲自豪的情绪。这种感觉特别像古希腊博学家阿基米德 在苦思冥想如何测量不规则物体…

第100+12步 ChatGPT学习:R实现KNN分类

基于R 4.2.2版本演示 一、写在前面 有不少大佬问做机器学习分类能不能用R语言&#xff0c;不想学Python咯。 答曰&#xff1a;可&#xff01;用GPT或者Kimi转一下就得了呗。 加上最近也没啥内容写了&#xff0c;就帮各位搬运一下吧。 二、R代码实现KNN分类 &#xff08;1&a…

基于Java实训中心管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

基于Java学生干部管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

【机器学习 复习】 第1章 概述

一、概念 1.机器学习是一种通过先验信息来提升模型能力的方式。 即从数据中产生“模型”( model )的算法&#xff0c;然后对新的数据集进行预测。 2.数据集&#xff08;Dataset&#xff09;&#xff1a;所有数据的集合称为数据集。 训练集&#xff1a;用来训练出一个适合模…

聊聊Vue中的Router(路由)

Vue构造的是一个单页面应用 在 Vue 中&#xff0c;router&#xff08;路由&#xff09;用于定义应用的不同页面路径和组件之间的映射关系&#xff0c;通过路由从而实现页面的切换和导航功能 vue中所有的xxx.vue文件&#xff0c;都是路由组件&#xff0c;这些组件都会被vue读取…

MySQL 死锁查询和解决死锁

来了来了来了&#xff01;客户现场又要骂街了&#xff0c;你们这是什么破系统怎么这么慢啊&#xff1f;&#xff01;&#xff1f;&#xff01; 今天遇到了mysql死锁&#xff0c;直接导致服务器CPU被PUA直接GUA了&#xff01; 别的先别管&#xff0c;先看哪里死锁&#xff0c;或…

【Springcloud微服务】Docker下篇

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Springcloud微服务 &#x1f320; 首发时间&#xff1a;2024年6月22日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f4…

【MySQL数据库】:MySQL视图特性

视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义&#xff0c;同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图中的数据并不会单独存储在数据库中&#xff0c;其数据来自定义视图时查询所引用的表&#xff08;基表&#xff09;&#xff0c;在每…

1931java Web披萨店订餐系统idea开发mysql数据库web结构java编程计算机网页源码servlet项目

一、源码特点 java Web 披萨店订餐系统是一套完善的信息管理系统&#xff0c;结合java 开发技术和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用 B/S模式开发。 视频地址&#xff1a;…

从零开始的Ollama指南:部署私域大模型

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

c语言回顾-结构体(2)

前言 前面讲了结构体的概念&#xff0c;定义&#xff0c;赋值&#xff0c;访问等知识&#xff0c;本节内容小编将讲解结构体的内存大小的计算以及通过结构体实现位段&#xff0c;话不多说&#xff0c;直接上干货&#xff01;&#xff01;&#xff01; 1.结构体内存对齐 说到计…

Ubuntu系统使用快速入门实践(八)—— git 命令使用

Ubuntu系统使用快速入门实践系列文章 下面是Ubuntu系统使用系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 Ubuntu系统使用快速入门实践系列文章总链接 下面是专栏地址&#xff1a; Ubuntu系统使用快速入门实践系列文章专栏 文章目录 Ubuntu系统使用快速…

UDS服务——RequestDownload(0x34)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍RequestDownload(0x34)—— 请求下载,用于给ECU下载数据的,最常见的应用就是在bootloader中,程序下载工具会发起请求下载,以完成ECU程序的升级。通过阅读本文,希望能对你有所帮助。 文章目录 诊断协议那些事儿…

【C++】一个极简但完整的C++程序

一、一个极简但完整的C程序 我们编写程序是为了解决问题和任务的。 1、任务&#xff1a; 某个书店将每本售出的图书的书名和出版社&#xff0c;输入到一个文件中&#xff0c;这些信息以书售出的时间顺序输入&#xff0c;每两周店主会手工计算每本书的销售量、以及每个出版社的…