40 openlayers setCenter 之后 绘制了Overlay 地图定位异常

news2024/11/18 18:40:37

前言

这是之前在 生产环境碰到的一个问题

这个其实就是 业务上一个地图点击点位展示详情, 然后再点击另外一个点位 展示详情, 切换中心店的这个过程 

其主要的问题是 使用 openlayers 的 Map.View.setCenter() 了之后, 整个地图的中心点切换到了一个莫名其妙的地方 

然后 经过大量的事件排查, 我们发现 在我们复现问题的操作的过程中总共有两个地方在进行中心点的切换 

第一个是我们业务上的 map.getView().setCenter(), 然后另外一个是 openlayers 的 Overlay 元素的重新绘制 的过程中的一个中心点的切换

然后 正常的业务情况是 先执行 Overlay 的相关业务绘制, 然后再执行 map.getView().setCenter()

然后 在我们这里测试出了某一个情况下 业务代码这边先执行的 map.getView().setCenter() 然后再执行的 Overlay 的相关业务绘制, 然后导致 地图上面看不到数据, 产生了一个 bug, 就是 我点击了目标点位, 期望地图上面能够展示这个点, 以及这个点位的详情信息, 但是实际情况是 地图上面展示的是空的

我们这里 便是来看一下 这个问题 

测试用例

<template>
  <div style="width: 1920px; height:1080px;" >
    <div class="olMapUsageClass"></div>

    <div class="overdelay1" ref="overdelay1" >
      this is over delay1
    </div>
  </div>

</template>

<script>

  import Map from 'ol/Map'
  import View from 'ol/View'
  import DragPan from 'ol/interaction/DragPan'
  import MouseWheelZoom from 'ol/interaction/MouseWheelZoom'
  import PointerInteraction from 'ol/interaction/Pointer'
  import GeoJSON from 'ol/format/GeoJSON'
  import {Tile as TileLayer} from 'ol/layer'
  import {Vector as VectorLayer} from 'ol/layer'
  import {Image as ImageLayer} from 'ol/layer'
  import {Vector as VectorSource} from 'ol/source'
  import {Feature as Feature} from 'ol'
  import Point from 'ol/geom/Point'
  import LineString from 'ol/geom/LineString'
  import Polygon from 'ol/geom/Polygon'
  import CircleGeo from 'ol/geom/Circle'
  import XYZ from "ol/source/XYZ"
  import ImageStatic from "ol/source/ImageStatic"
  import {Circle, Fill, Icon, RegularShape, Stroke, Style, Text} from 'ol/style'
  import Overlay from 'ol/Overlay'
  import {transformExtent, transform} from "ol/proj"

  export default {
    name: "olMapUsage",
    components: {},
    props: {},
    data() {
      return {
        map: null,
        tdtImgLayer: null,
        labelLayer: null,
        overlay: null,
      };
    },
    computed: {},
    watch: {},
    created() {

    },
    mounted() {

      this.initMap()

      this.test12SetCenterThenAddOverlay()

    },
    methods: {
      initMap() {
        let center = [105.065735, 30.659462]
        let projection = "EPSG:4326"
        let zoom = 10
        let minZoom = 5
        let maxZoom = 20
        const layer = []
        const view = new View({
          ...(this.viewOptions || {}),
          projection,
          center,
          zoom,
          minZoom,
          maxZoom
        })

        this.map = new Map({
          ...(this.mapOptions || {}),
          layers: [].concat(layer),
          view: view,
          target: this.$el,
          controls: [],
          interactions: [
            new DragPan(),
            new MouseWheelZoom(),
            new PointerInteraction({
              handleEvent: this.handleEvent
            })
          ]
        })
      },
      test06AddCircleLayer(coord, color) {
        color = color || 'green'
        let style = new Style({
          image: new Circle({
            radius:20,
            fill: new Fill({
              color: color
            })
          })
        })
        let feature = new Feature({
          geometry: new Point(coord)
        })
        feature.setStyle(style)
        let source = new VectorSource()
        source.addFeature(feature)

        let layer = new VectorLayer({
          source: source
        })
        this.map.addLayer(layer);
      },
      test10SetCenter(coord, color) {
        this.map.getView().setCenter(coord)
        this.test06AddCircleLayer(coord, color)
      },
      test11AddOverlay(coord) {
        this.overlay && this.map.removeOverlay(this.overlay)
        this.overlay = new Overlay({
          element: this.$refs.overdelay1,
          position: coord,
          positioning: "bottom-center",
          offset: [0, 0],
          autoPan: true,
          autoPanMargin: 200,
          autoPanAnimation: {
            duration: 1000
          },
          map: this.map
        })
        this.map.addOverlay(this.overlay)
      },
      test12SetCenterThenAddOverlay() {

        // refer cycle
        this.test06AddCircleLayer([10.265735, 10.659462], "#007f5a")
        this.test06AddCircleLayer([105.565735, 30.759462], "#0039ff")

        let _this = this
        // use this for map.addOverlay's animation update
        setTimeout(function() {
          _this.test11AddOverlay([10.065735, 10.459462])
          _this.test10SetCenter([10.065735, 10.459462], "yellow")
        }, 2000)

        // the core case, normal or exception or compensated
        setTimeout(function() {

          // case1. function of addOverlay
          _this.test11AddOverlay([105.065735, 30.259462])

          // case2. normal case
          // _this.test11AddOverlay([105.065735, 30.259462])
          // _this.test10SetCenter([105.065735, 30.259462], "red")

          // case3. exception case
          // _this.test10SetCenter([105.065735, 30.259462], "red")
          // _this.test11AddOverlay([105.065735, 30.259462])

          // case4. compensated case
          // _this.test10SetCenter([105.065735, 30.259462], "red")
          // setTimeout(function() {
          //   _this.test11AddOverlay([105.065735, 30.259462])
          // }, 1000)
        }, 5000)
      },

    }
  };
</script>

<style lang="scss">
  .olMapUsageClass {

  }
  .overdelay1 {
    position: absolute;
    border: 1px greenyellow solid;
    width: 200px;
    height: 50px;
  }
</style>

map.addOverlay 的绘制和计算 

这里使用 上面的 case1 的相关代码 来查看

整个测试用例分为了三次状态, 第一个是初始状态, 第二个是 切换到 [10.065735, 10.459462] 附近, 第三个是切换到 [105.065735, 30.259462] 附近 

我们这里 着重关注 第三次切换

我们上面配置的 Overlay 的 autopan 相关配置为 margin 为 200, duration 为 1s, 我们这里是从做下切换到右上, 因此 overlay1 是距离右上 200px, 然后整个切换的耗时为 1s

注意这里 Overlay 的 autopan 切换的中心点不是将 Overlay 放在正中间, 只需要满足 autopanMargin 的配置即可 

我们可以将 autopanMargin 配置调整下, 再看下一个大概的情况, autoPan 只需要确保给定的元素在页面上能够找到即可 

然后 autopan 目标中心点计算方式如下 

这里 newCenterPx 的计算涉及到两个变量, 其一是 center, 即 map.getView().getCenter 为当前的 [10.065735, 10.459462]

另外一个 delta 计算结果为 [68617, -14213]

delta 的计算依赖于 overlayRect, 值为 [70137, -13923, 70337, -13873]

然后最终 计算出来新的中心点为 [104.29, 29.85]

保证的是 center 切换到新的中心点的时候 可以看到 [105.065735, 30.259462] 的 Overlay

正常绘制 以及 切换中心点的情况

这里切换到 case2 来执行 

会先执行 Overlay 然后再执行 map.getView().setCenter

Overlay 的 autopan 这个阶段, 可以看到和上面是一样的, 然后设置中心点为 [104.29, 29.85], 以及动画切换 

然后接着是 setCenter 的处理, 设置中心点为 [105.065735, 30.259462] 然后渲染 

这样最终的效果如下, center 为 点位的位置, 以及 overlay所在的位置 

异常绘制 以及 切换中心点的情况

这里切换到 case3 来执行 

会先执行 map.getView().setCenter 然后再 执行 Overlay

map.getView().setCenter

map.getView().setCenter 之后, 设置了 map 的中心点的字段为 [105.065735, 30.259462]

然后接下来的 applyTargetState 才会开始真实的数据的渲染, 切换 

Overlay 的 autopan 这个阶段

然后 autopan 目标中心点计算方式如下 

这里 newCenterPx 的计算涉及到两个变量, 其一是 center, 即 map.getView().getCenter 为当前的 [105.065735, 30.259462]

另外一个 delta 计算结果为 [68617, -14213]

delta 的计算依赖于 overlayRect, 值为 [70137, -13923, 70337, -13873]

可以看到 和上面正常的 Overlay 加载的时候会存在一些问题 

第一 center 变化了, 拿到的 overlayRect 还是 [10.065735, 10.459462] 状态下的 overlayRect, 然后计算出的偏移也是 [10.065735, 10.459462] 相比于 [105.065735, 30.259462] 的偏移 

然后 导致了计算中心点存在问题, 最终 autopan 切换到目标中心点之后, 切换到了一个错误的中心点, 导致页面看不到数据 

然后 我们大致推导一下 这个新的中心点

从上面 map.addOverlay 中我们可以看到 delta 对应的经纬度偏移大致为 [104.29-10.06, 29.85-10.45], 即为 [94.23, 19.4]

然后 大致的新的中心点位为 [105.065735+94.23, 30.259462+19.4] 为 [199.29, 49.65]

可以看到 和我们的推导一样

综上 核心问题就是, map.getView().setCenter 之后先切换了 map.view 的中心点的数据 

但是 View.applyTargetState 的过程是异步的 

Overlay 的Overlay.panIntoView 中 this.getRect(element, [outerWidth(element),outerHeight(element),]); 获取到的元素状态为 中心点为 [10.065735, 10.459462] 的状态下面的元素状态

然后一个是 最新的中心点位, 一个是基于 旧的中心点位元素状态计算的偏移, 然后 最终得到了一个 错误的结果

如何先切换中心点 然后再绘制 Overlay

然后 这里的一个 曲线救国的调整方式就是, 在 setCenter 渲染结束了之后, 在添加 Overlay, 调整的方式如下 

// case4. compensated case
_this.test10SetCenter([105.065735, 30.259462], "red")
setTimeout(function() {
  _this.test11AddOverlay([105.065735, 30.259462])
}, 1000)

然后 我们这里看一下 我们这里的 Overlay 的添加情况, 这会是 另外的一个状态

切换了中心点之后, 然后再执行 test11AddOverlay 的时候, 会发现 Overlay 的坐标在当前上下文的区域内, 不用进行 autopan, 直接跳过了 autopan 的流程 

完 

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

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

相关文章

Occupancy 训练策略

损失函数 Dice Loss Dice Loss 是一种用于图像分割的损失函数&#xff0c;其灵感来自于Dice 系数&#xff0c;是一种衡量两个样本相似度的方法。Dice 系数定义为&#xff1a; Dice 系数 2 * TP / (2 * TP FP FN) 其中&#xff1a; TP&#xff1a;预测为正且真实值为正的像…

深入理解MySQL中的JOIN算法

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、引言二、嵌套循环连接&#xff08;Nested-Loop Join&#xff09;2.1 工作原理2.2 性能考虑2.3 优化策略 三、块嵌套循环…

云原生(五)、Docker-Swarm集群

基础环境说明 1、环境准备 1、启动4台服务器&#xff08;在同一个网段内&#xff09;。 2、重命名4台服务器&#xff0c;方便区分。 hostnamectl set-hostname swarm1 reboot安装docker。参考文章&#xff1a;云原生&#xff08;二&#xff09;、Docker基础 2、DockerSwarm…

STM32---DHT11温湿度传感器与BH1750FVI光照传感器(HAL库、含源码)

写在前面&#xff1a;本节我们学习使用两个常见的传感器模块&#xff0c;分别为DHT11温湿度传感器以及BH1750FVI光照传感器,这两种传感器在对于环境监测中具有十分重要的作用&#xff0c;因为其使用简单方便&#xff0c;所以经常被用于STM32的项目之中。今天将使用分享给大家&a…

C# WPF编程-事件

C# WPF编程-路由事件 路由事件概要路由事件的三种方式 WPF事件WPF最重要的5类事件&#xff1a;生命周期事件 鼠标事件键盘事件多点触控输入原始触控 路由事件概要 路由事件是具有更强传播能力的事件&#xff0c;它们可在元素树中向上冒泡和向下隧道传播&#xff0c;并沿着传播…

【测试开发学习历程】认识Python + 安装Python

1 认识 Python 人生苦短&#xff0c;我用 Python —— Life is short, I use Python 1.1 Python 的起源 Python 的创始人为吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;&#xff0c;江湖人称“龟叔” 1989 年的圣诞节期间&#xff0c;吉多范罗苏姆为了在荷兰首都阿姆…

查看文件内容的指令:cat,tac,nl,more,less,head,tail,写入文件:echo

目录 cat 介绍 输入重定向 选项 -b -n -s tac 介绍 输入重定向 nl 介绍 示例 more 介绍 选项 less 介绍 搜索文本 选项 head 介绍 示例 选项 -n tail 介绍 示例 选项 echo 介绍 输出重定向 追加重定向 cat 介绍 将标准输入(键盘输入)的内容打…

鸿蒙Harmony应用开发—ArkTS-LazyForEach:数据懒加载

LazyForEach从提供的数据源中按需迭代数据&#xff0c;并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach&#xff0c;框架会根据滚动容器可视区域按需创建组件&#xff0c;当组件滑出可视区域外时&#xff0c;框架会进行组件销毁回收以降低内存占用。 接口…

Day45:WEB攻防-PHP应用SQL二次注入堆叠执行DNS带外功能点黑白盒条件

目录 PHP-MYSQL-二次注入-DEMO&74CMS DEMO-用户注册登录修改密码 CMS-74CMS个人中心简历功能 PHP-MYSQL-堆叠注入-DEMO&CTF强网 Demo 2019强网杯-随便注&#xff08;CTF题型&#xff09; PHP-MYSQL-带外注入-DEMO&DNSLOG(让服务器主动把数据交出去) 知识点&…

Docker入门到实践之环境配置

Docker入门到实践之环境配置 docker 环境安装 Ubuntu/Debian: sudo apt update sudo apt install docker.ioCentOS/RHEL: sudo yum install dockerArch Linux: sudo pacman -S docker如果未安装成功&#xff0c;或者env的path未设置成功&#xff0c;运行时会报错 Bash: Do…

国自然提交状态,NSFC已审核 ≠ 申请书被受理!!!

本 期 推 荐 【SciencePub学术】2024年度国家自然科学基金集中受理期项目申请受理工作已基本结束。到底什么状态才算申请书被NSFC接收成功呢&#xff1f; 01 申请书状态 申请人登录ISIS系统&#xff0c;至此&#xff0c;绝大部分申请人的系统状态为下面三种&#xff1a; …

Stable Diffusion实现光影字效果

昨天下午有人在群里发光影图片&#xff0c;大家都觉得很酷&#xff0c;我没怎么在意。直到早上我在小红书看到有人发同款图片&#xff0c;只是一晚上的时间点赞就超过了8000&#xff0c;而且评论数也很高&#xff0c;也可以做文字定制变现。研究了一下发现这个效果不难实现&…

训练不了AI,怎么办?

即使直接训练人工智能&#xff08;AI&#xff09;模型对许多人来说可能是一个技术上和资源上的挑战&#xff0c;仍然有多种方式可以参与、利用和推动AI技术的发展&#xff0c;而不必直接参与到模型的训练过程中。以下是一些可以考虑的途径&#xff1a; 1. 使用现有的AI服务和工…

【计算机网络篇】数据链路层(3)差错检测

文章目录 &#x1f95a;误码&#x1f354;两种常见的检错技术⭐奇偶校验⭐循环冗余校验&#x1f388;例子 &#x1f95a;误码 误码首先介绍误码的相关概念 &#x1f354;两种常见的检错技术 ⭐奇偶校验 奇校验是在待发送的数据后面添加1个校验位&#xff0c;使得添加该校验…

springcloud+nacos服务注册与发现

快速开始 | Spring Cloud Alibaba 参考官方快速开始教程写的&#xff0c;主要注意引用的包是否正确。 这里是用的2022.0.0.0-RC2版本的springCloud&#xff0c;所以需要安装jdk21&#xff0c;参考上一个文章自行安装。 nacos-config实现配置中心功能-CSDN博客 将nacos-conf…

[C语言]结构体、位段、枚举常量、联合体

目录 结构体 结构体的使用方法 结构体所占用的大小 位段 位段的使用方法 位段所占用的大小 枚举常量 枚举常量的使用方法 枚举常量的优势 联合体 联合体的使用方法 结构体 结构体的使用方法 结构体是一些值的集合&#xff0c;我们可以定义一个结构体&#xff0c;里…

Pink老师Echarts教学笔记

可视化面板介绍 ​ 应对现在数据可视化的趋势&#xff0c;越来越多企业需要在很多场景(营销数据&#xff0c;生产数据&#xff0c;用户数据)下使用&#xff0c;可视化图表来展示体现数据&#xff0c;让数据更加直观&#xff0c;数据特点更加突出。 01-使用技术 完成该项目需…

TCP(socket 套接字)编程 1

一、TCP套接字编程架构如下 二、相关代码实现 1、服务器端代码 package com.company;import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket;public class Main {public static void main(String[] args) {…

GeoAI

GeoAI 是人工智能 (AI) 与空间数据、科学和地理空间技术的集成&#xff0c;以增进对空间问题的理解和解决。GeoAI 包括应用传统人工智能技术&#xff0c;通过从结构化和非结构化数据中提取、分类和检测信息来生成空间数据。GeoAI 也是空间显式人工智能技术的使用&#xff0c;旨…

超越工具的限制!菜鸟工具箱帮你节省时间

在这个飞速发展的时代&#xff0c;我们越来越依赖各种工具来提高工作效率和解决问题。然而&#xff0c;你是否意识到&#xff0c;有些工具不仅仅是工具&#xff0c;它们还能为你带来更多的可能性和机遇&#xff1f;菜鸟工具箱就是这样一个超越工具本身的存在&#xff01; 菜鸟工…