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

news/2024/7/19 14:05:39 标签: js, map, layer

前言

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

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

其主要的问题是 使用 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.niftyadmin.cn/n/5448352.html

相关文章

Pillow 一文读懂

Pillow 简介、特点和安装 Pillow 简介 Pillow作为python的第三方图像处理库&#xff0c;提供了广泛的文件格式支持&#xff0c;强大的图像处理能力&#xff0c;主要包括图像储存、图像显示、格式转换以及基本的图像处理操作等。 PIL(Python Image Library)是python的第三方图…

(三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练

这里写目录标题 一、colmap解算数据放入高斯1. 将稀疏重建的文件放入高斯2. 将稠密重建的文件放入高斯 二、vkitti数据放入高斯 一、colmap解算数据放入高斯 运行Colmap.bat文件之后&#xff0c;进行稀疏重建和稠密重建之后可以得到如下文件结构。 1. 将稀疏重建的文件放入高…

Mysql事务及存储引擎

一、Mysql事务 1.1 Mysql事务的概念 所谓事务&#xff0c;它是一个操作序列&#xff0c;这些操作要么都执行&#xff0c;要么都不执行&#xff0c;它是一个不可分割的工作单位。 1.2 事务的ACID特点 事务应该具有的四个特性&#xff1a;原子性&#xff08;Atomicity&#xff09…

PHP+MySQL开发组合:智慧同城便民信息小程序源码系统 带完整的安装代码包以及安装部署教程

当前&#xff0c;城市生活的节奏日益加快&#xff0c;人们对各类便民信息的需求也愈发迫切。无论是寻找家政服务、二手交易&#xff0c;还是发布租房、求职信息&#xff0c;一个高效、便捷的信息平台显得尤为重要。传统的信息发布方式往往存在信息更新不及时、查找困难等问题&a…

生物信息学文章中常见的图应该怎么看?

目录 火山图 热图 箱线图 森林图 LASSO回归可视化图&#xff08;套索图&#xff09; 交叉验证图 PCA图 ROC曲线图 这篇文章只介绍这些图应该怎么解读&#xff0c;具体怎么绘制&#xff0c;需要什么参数&#xff0c;怎么处理数据&#xff0c;会在下一篇文章里面给出 火山…

无极低码SQL模板引擎使用教程示例,自己手撸一个sql模板引擎进行动态sql生成。

无极低码 &#xff1a;https://wheart.cn 无极低码SQL模板使用教程 一、模板结构与规则 无极低码SQL模板通过简洁的Markdown格式&#xff0c;使SQL语句具有更强的灵活性和适应性&#xff0c;简化了根据业务需求定制SQL的过程。 无极低码SQL模板是一种基于Markdown格式的特殊…

全屏解决方案 (screenfull or vueuse)

ScreenFull 使用 (方案一) 参考文章朝阳 39 参考文章半夏_2021 安装 npm install screenfull --save (默认是 6.0,vue2 环境下会报错,所以需要安装 5.1.0) vue2 安装 npm i screenfull5.1.0 引入 import screenfull from “screenfull”; 调用 // 属性 screenfull.isFullscree…

docker desktop 登录不上账号

配置走代理&#xff08;系统全局&#xff09;也没用 解决方法 参考博文&#xff1a; https://blog.csdn.net/weixin_37477009/article/details/135797296 https://adoyle.me/Today-I-Learned/docker/docker-desktop.html 下载 Proxifiler 配置 Proxifiler