cesium 实现批量divpoint气泡,及气泡碰撞测试与自动避让

  • 需求背景
  • 解决效果
  • index.vue

需求背景

需要实现一个上百点批量同时存在的 popup 弹框,为了提高用户体验
1.重叠的弹框,需要隐藏下一层级的 popup
2.为了让用户尽可能看到较全的弹框,需要做弹框的自动避让

解决效果

index.vue

<!--/**
* @author: liuk
* @date: 2024-08-20
* @describe:数值
*/-->
<template><div class="numericalValue-wrap"><teleport to="body"><ul v-show="showTip && item.visible"v-for="(item,index) in listData" :key="index":class="['surveyStation-popup','sectionEntityDom'+index,'section-popup',item.offsetPopupBoxType,item?.levelOverflow >= 0.01 ? 'waterlevel-overflow' : '']":style="{transform: `translate(${item.AABB?.offsetX || 0}px, ${item.AABB?.offsetY ||0}px)`}"><li>名称:<span class="label">{{ index }}</span></li><li>编号:<span class="label">{{ index }}</span></li><li>水位:<span class="num">{{ item.waterLevel }}</span>m<span style="color:red" v-if="item.levelOverflow>= 0.01">{{ item.levelOverflow.toFixed(2) }}</span></li><li>流量:<span class="num">{{ item.flow }}</span> mm</li></ul></teleport></div>
</template><script lang="ts" setup>
import {onMounted, onUnmounted, reactive, toRefs} from "vue";const model = reactive({showTip: true,listData: [],popupPoss: [],curId: "",dialogVisible: false
})
const {showTip, showGrid, popupPoss, listData, curId, dialogVisible} = toRefs(model)onMounted(() => {getlist()viewer.dataSources.add(sectionDatasource);handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction(onMouseMove, Cesium.ScreenSpaceEventType.MOUSE_MOVE);handler.setInputAction(onMouseClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);viewer.camera.percentageChanged = 0;viewer.scene.camera.changed.addEventListener(showPopupBox);
})onUnmounted(() => {sectionDatasource.entities.removeAll()handler.destroy()viewer.dataSources.remove(sectionDatasource);viewer.scene.camera.changed.removeEventListener(showPopupBox);
})const getlist = () => {const data = [{"ctr_points_lonlat": [[113.04510386306632,25.748247970488464],[113.04619931039747,25.746722270257674]],},/* ... */]setTimeout(() => {model.listData = data || []model.popupPoss = new Array(data.length).fill("").map(() => ({}))addTip(data)}, 500)
}// 地图逻辑
import {usemapStore} from "@/store/modules/cesiumMap";
import mittBus from "@/utils/mittBus";const sectionDatasource = new Cesium.CustomDataSource("section");const mapStore = usemapStore()
let handler, PreSelEntity
const viewer = mapStore.getCesiumViewer();
const addTip = (data) => {data.forEach(item => {sectionDatasource.entities.add({customType: "sectionEntity",id: item.label,data: item,polyline: {positions: Cesium.Cartesian3.fromDegreesArray(item.ctr_points_lonlat.flat()),material: Cesium.Color.fromCssColorString("yellow").withAlpha(1),width: 5,}})})
}const onMouseMove = (movement) => {if (PreSelEntity) {PreSelEntity.polyline.material = Cesium.Color.fromCssColorString("yellow").withAlpha(1)PreSelEntity = null}const pickedObject = viewer.scene.pick(movement.endPosition);if (!Cesium.defined(pickedObject) || !Cesium.defined(pickedObject.id)) returnconst entity = pickedObject.id;if (!(entity instanceof Cesium.Entity) || entity.customType !== "sectionEntity") returnentity.polyline.material = Cesium.Color.fromCssColorString("red").withAlpha(1)if (entity !== PreSelEntity) PreSelEntity = entity;
}const onMouseClick = (movement) => {const pickedObject = viewer.scene.pick(movement.position);if (!Cesium.defined(pickedObject) || !Cesium.defined(pickedObject.id)) returnconst entity = pickedObject.id;if (!(entity instanceof Cesium.Entity) || entity.customType !== "sectionEntity") returnmodel.curId = entity.id
}const offsetPopupBoxOptions = {top: [-0.5, -1],bottom: [-0.5, 0],right: [0, -0.5],left: [-1, -0.5],
}
const showPopupBox = () => {if (!model.showTip) return// 碰撞检测const {left, top, bottom, right} = viewer.container.getBoundingClientRect()model.listData.forEach(async (item, index) => {const curIndex = model.listData.findIndex(x => x.name === item.name)let width, height, areaif (!item.AABB) {const dom = document.querySelector(`.sectionEntityDom${curIndex}`)width = parseInt(getComputedStyle(dom).width) + 2 * parseInt(getComputedStyle(dom).padding.split(" ")[1])height = parseInt(getComputedStyle(dom).height) + 2 * parseInt(getComputedStyle(dom).padding.split(" ")[0])area = width * heightitem.AABB = {width, height, area: width * height}} else {width = item.AABB.widthheight = item.AABB.heightarea = item.AABB.area}const longitude = (item.ctr_points_lonlat[0][0] + item.ctr_points_lonlat[1][0]) / 2const latitude = (item.ctr_points_lonlat[0][1] + item.ctr_points_lonlat[1][1]) / 2const curPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, item.heightZ);const {x, y} = viewer.scene.cartesianToCanvasCoordinates(curPosition)if (index === 0) {item.offsetPopupBoxType = "top";item.AABB.offsetX = x + offsetPopupBoxOptions["top"] * widthitem.AABB.offsetY = y + offsetPopupBoxOptions["top"] * height}const offsetPopupBoxKeys = Object.keys(offsetPopupBoxOptions)const toChecks = model.listData.slice(0, index) // 需要测试碰撞的单位offsetPopupBoxKeys.some((type) => {item.offsetPopupBoxType = ""item.AABB.offsetX = x + offsetPopupBoxOptions[type][0] * widthitem.AABB.offsetY = y + offsetPopupBoxOptions[type][1] * heightconst check = toChecks.every(checkItem => {const box1 = checkItem.AABBconst box2 = item.AABBlet intersectionArea = 0 // 相交面积// 计算在每个轴上的重叠部分const overlapX = Math.min(box1.offsetX + box1.width, box2.offsetX + box2.width) - Math.max(box1.offsetX, box2.offsetX);const overlapY = Math.min(box1.offsetY + box1.height, box2.offsetY + box2.height) - Math.max(box1.offsetY, box2.offsetY);// 如果在两个轴上都有重叠,则计算相交区域的面积if (overlapX > 0 && overlapY > 0) intersectionArea = overlapX * overlapY;return intersectionArea <= area * 0.05;});if (check) {item.offsetPopupBoxType = type}return check})switch (true) { // 屏幕边界限制case item.AABB.offsetX + width <= right && item.AABB.offsetX >= left && item.AABB.offsetY >= top && item.AABB.offsetY + height <= bottom:item.visible = !!item.offsetPopupBoxType;breakdefault:item.visible = false;break}model.listData[curIndex] = item})
}
</script><style lang="scss">
.surveyStation-popup {position: fixed;top: 0;left: 0;z-index: 3;margin: 0;padding: 7px 15px;list-style: none;background: rgba(5, 9, 9, 0.6);border-radius: 4px;font-size: 14px;color: #fff;cursor: default;--w: 24px;--h: 10px;&::before {content: "";background-color: rgba(0, 0, 0, 0.7);position: absolute;bottom: 0;left: 50%;width: var(--w);height: var(--h);transform: translate(-50%, 100%) translateY(-0.5px);clip-path: polygon(50% 100%, 0 0, 100% 0);}&.ponint-list::before{display: none;}&.map2d {margin-left: -15px; // 二维图片底座尺寸大小margin-top: -50px;}&.map3d {margin-left: -15px; // 三维图片底座尺寸大小margin-top: -100px;}.ponint-list-li {cursor: pointer;&:hover {background: rgba(204, 204, 204, .6);}}
}.section-popup {--w: 24px;--h: 10px;width: 150px;height: 80px;margin-top: -10px;&::before {content: "";background-color: rgba(0, 0, 0, 0.7);position: absolute;bottom: 0;left: 50%;width: 24px;height: 10px;transform: translate(-50%, 100%) translateY(-0.5px);clip-path: polygon(50% 100%, 0 0, 100% 0);}&.top {margin-top: calc(var(--h) * -1);&::before {top: auto;bottom: 0;right: auto;left: 50%;width: var(--w);height: var(--h);transform: translate(-50%, 100%) translateY(-0.5px);clip-path: polygon(50% 100%, 0 0, 100% 0);}}&.bottom {margin-top: var(--h);&::before {top: 0;bottom: auto;right: auto;left: 50%;width: var(--w);height: var(--h);transform: translate(-50%, -100%) translateY(0.5px);clip-path: polygon(50% 0, 0 100%, 100% 100%);}}&.right {margin-left: var(--h);&::before {top: 50%;bottom: auto;right: auto;left: 0;width: var(--h);height: var(--w);transform: translate(-100%, -50%) translateX(0.5px);clip-path: polygon(100% 0, 0 50%, 100% 100%);}}&.left {margin-left: calc(var(--h) * -1);&::before {top: 50%;bottom: auto;right: 0;left: auto;width: var(--h);height: var(--w);transform: translate(100%, -50%) translateX(-0.5px);clip-path: polygon(0 100%, 0 0, 100% 50%);}}&.waterlevel-overflow {animation: dm-yj-breathe 800ms ease-in-out infinite;animation-direction: alternate;}.label {color: #00ff00;}.num {color: orange;}
}
</style>

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

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

相关文章

加速网络体验,Squid缓存代理:让浏览如飞,畅享无限网络速度!

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言: squ…

响应式Web设计:纯HTML和CSS的实现技巧

1. 简介 1.1. 概述 响应式Web设计(Responsive Web Design,简称RWD)是一种网络页面设计布局,它能够根据访问设备的屏幕尺寸、分辨率和其他特性动态地调整布局、图片和内容,以提供更好的用户体验。这种设计理念的核心在于“移动优先”,即首先考虑移动用户的体验,然后再扩…

Git使用——常见报错及其解决方法

一、报错关键词&#xff1a;OpenSSL、10054 fatal 1、在pull或push项目时&#xff0c;报错&#xff1a; fatal: unable to access https://github.com/../: OpenSSL SSL_read: Connection was reset, errno 10054 2、解决方法&#xff1a;进行解除/禁用Git SSL验证 项目里右…

【时时三省】C语言例题----华为机试题<字符串反转>

目录 1,题目 描述 输入描述: 输出描述: 示例1 2,代码

订单到期关闭如何实现?

目录 一、被动关闭 二、定时任务 三、JDK自带的DelayQueue 四、Netty的时间轮 五、Kafka的时间轮 六、RocketMQ延迟消息 七、RabbitMQ死信队列 八、RabbitMQ插件 九、Redis过期监听 十、Redis的Zset 十一、Redisson 在电商、支付等系统中&#xff0c;一般都是先创建…

详解华为项目管理,附华为高级项目管理内训材料

&#xff08;一&#xff09;华为在项目管理中通过有效的沟通、灵活的组织结构、坚持不懈的努力、细致的管理和科学的考核体系&#xff0c;实现了持续的创新和发展。通过引进先进的管理模式&#xff0c;强调以客户需求为导向&#xff0c;华为不仅优化了技术管理和项目研发流程&a…

【通用】C++ #pragma(特殊指令的预处理指令)

#pragma是一种用于向编译器发出特殊指令的预处理指令。它的作用是提供编译器特定的功能或控制编译行为。虽然 #pragma 不是标准 C 的一部分&#xff0c;但它被许多编译器实现并提供了不同的扩展。以下是一些常见的 #pragma 指令&#xff1a; 常见的 #pragma 指令 #pragma onc…

多重示例详细说明Eureka原理实践

Eureka原理&#xff08;Eureka Principle&#xff09;是指在长时间的思考和积累之后&#xff0c;通过偶然的瞬间获得灵感或发现解决问题的方法的一种认知现象。这个过程通常包括三个主要阶段&#xff1a;准备阶段、潜伏期以及突然的灵感爆发。下面详细说明Eureka原理的实践步骤…

Pyramid学习笔记

Pyramid学习笔记 Static Assets&#xff1a; static assets 指那些非Python原文件&#xff0c;如&#xff1a;图片、css、js、还有目录&#xff08;没有__init__.py文件的目录&#xff09;以及Mako或Chamelon模板文件。 ####理解asset规范&#xff1a; render_to_response(m…

vue3 组合式API

<!-- 深度监听 deep 点击按钮控制台&#xff0c;才输出count变化了: 1, 老值: 0;否则控制台不输出 --> <script setup>import { ref,watch } from vueconst state ref({count:0})const setCount () > {state.count.value}watch(state, () > {console.log(…

基于PHP的文件包含介绍

引言&#xff1a;在实际开发过程中&#xff0c;经常会遇到部分模块功能需要重复使用的情况&#xff0c;比如数据库的增删改查&#xff0c;文件包含通过将需要重复使用的功能模块代码引入其他文件的内容&#xff0c;实现重用代码、分离配置等。然而&#xff0c;如果文件包含操作…

QT中通过TCP协议多线程的文件传输(客户端)

首先&#xff0c;新建一个项目&#xff0c;我命名为了SendFileClient 首先我们要在pro文件中 代码第一行加入network的后缀 一、窗口搭建 如图所示&#xff0c;在第一个QWidget中让客户端输入IP&#xff0c;端口号 连接服务器 第二个Qwidget 设置一个LineEdit,供客户端选择要…

报表系统之Redash

Redash 是一个开源的数据可视化和仪表板工具&#xff0c;旨在帮助用户轻松地从多个数据源中提取、查询、可视化数据&#xff0c;并分享结果。它的设计目标是让数据分析变得更加便捷&#xff0c;即使是非技术用户也能通过简单的操作生成复杂的数据报告和仪表板。 核心概念和功能…

sql基础语句题练

此篇文章所有题都来源于牛客网 牛客网在线编程_SQL篇_非技术快速入门 记录一下自己的做题的历程&#xff0c;废话不多说直接开整 1.查询所有列 select * from user_profile user_profile代表表table的意思 2.查询多列 select device_id,gender,age,university from user…

[godot] 采用状态机时,如何处理攻击时移动?如“冲撞”

这里以‘史莱姆撞击’为例子&#xff0c;将‘空中跃进’定义为伤害帧。&#xff08;见下图&#xff09; 先梳理流程&#xff1a;a.史莱姆原地蓄力(起跳准备)--->b.跳起并移动一段距离(空中跃进)--->c.落地调整 一 当状态机进入‘攻击状态’时&#xff0c;在enter()中…

计算机毕业设计PySpark+Django农产品推荐系统 农产品爬虫 农产品商城 农产品大数据 农产品数据分析可视化 PySpark Hadoop

基于Spark的农产品个性推荐系统 相关技术介绍: 1. Python Python是一种高级编程语言&#xff0c;具有简洁、易读、易学的特点&#xff0c;被广泛应用于Web开发、数据分析、人工智能等领域。 在此系统中&#xff0c;我们使用Python进行后端开发&#xff0c;利用其强大的语法…

基本数据类型 --- 浮点型

float的机器码表示&#xff1a; 一个float数据 (pow(-1, sign) fraction) * pow(2, exponent - 127) 由上图&#xff0c;可得&#xff1a; (pow(-1, sign) fraction) * pow(2, exponent - 127) ( 1 2^(-2) ) * pow(2, 124-127) 0.15625 其他文章&#xff1a; https://b…

Docker Swarm部署SpringCloud Alibaba微服务踩坑记录

为了方便部署和维护微服务项目&#xff0c;还是得上集群部署方案&#xff0c;决定采用Docker的swarm&#xff0c;为什么不是k8s&#xff0c;因为部署骑来又是个新的工具&#xff0c;之前就一直用的docker&#xff0c;自带了类k8s的工具&#xff0c;索性就直接使用swarm了&#…

力扣(K件物品的最大和)

数据量小不需要考虑时间复杂度 数学思维理清楚步骤---然后代码翻译实现 Ⅰ、 K件物品的最大和 袋子中装有一些物品&#xff0c;每个物品上都标记着数字 1 、0 或 -1 。 给你四个非负整数 numOnes 、numZeros 、numNegOnes 和 k 。 袋子最初包含&#xff1a; numOnes 件标…