import { deepMerge, getLevelMiddleHeight, wgs84ToGcj02Format, convertPosition, } from "../util"; import Mars3dEntity from "../base/mars3d_entity"; import GaodeEntity from "../base/gaode_entity"; import SijiEntity from "../base/siji_entity"; import CesiumEntity from "../base/cesium_entity"; const HorizontalOrigin: any = { center: 0, left: 1, right: -1, }; const VerticalOrigin: any = { center: 0, top: -1, bottom: 1, }; export default (hnMap: any) => { const defaultOption = { id: "", position: [], html: "", horizontalOrigin: "center", verticalOrigin: "center", offset: [0, 0], scaleByDistance: true, distanceDisplayCondition: false, distanceDisplayCondition_far: 1, distanceDisplayCondition_near: 18, data: null, }; class mars3d_class extends Mars3dEntity { type: any = "divPoint"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; graphic: any = null; constructor(option: any) { super(hnMap); this.id = option.id; deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.graphic = new mars3d.graphic.DivGraphic(this.config); } formatConfig(option: any) { const distanceDisplayCondition_far = getLevelMiddleHeight( option.distanceDisplayCondition_far ); const distanceDisplayCondition_near = getLevelMiddleHeight( option.distanceDisplayCondition_near ); return { id: option.id, position: option.position, style: { html: option.html, offsetX: option.offset[0], offsetY: option.offset[1], scaleByDistance: option.scaleByDistance, distanceDisplayCondition: option.distanceDisplayCondition, distanceDisplayCondition_far: distanceDisplayCondition_far, distanceDisplayCondition_near: distanceDisplayCondition_near, clampToGround: !option.position[2], horizontalOrigin: HorizontalOrigin[option.horizontalOrigin], verticalOrigin: VerticalOrigin[option.verticalOrigin], }, attr: option.data, }; } set(option: any) { deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.graphic.setOptions(this.config); } openPopup() { this.graphic.openPopup(); } } class siji_class extends SijiEntity { type: any = "divPoint"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; graphic: any = null; constructor(option: any) { super(hnMap); this.id = option.id; deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.graphic = new SGMap.Marker(this.config); } formatConfig(option: any) { let el = document.createElement("div"); el.className = "marker"; el.id = option.id; el.innerHTML = option.html; // el.style.backgroundImage = // 'url("https://map.sgcc.com.cn/products/js-sdk/v3/assets/images/markers_new2_4ab0bc5_78.png")'; el.style.width = "0"; el.style.height = "0"; return { id: option.id, element: el, anchor: "center", offset: option.offset, }; } set(option: any) { deepMerge(this.option, option); this.config = this.formatConfig(this.option); if (this.graphic) { this.graphic.remove(); } this.graphic = new SGMap.Marker(this.config); this.graphic.setLngLat(option.position).addTo(hnMap.map.map); } openPopup() { this.graphic.openPopup(); } } class cesium_class extends CesiumEntity { type: any = "divPoint"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; graphic: any = null; htmlElement: HTMLElement | null = null; animationFrameId: number | null = null; isDestroyed: boolean = false; isVisible: boolean = false; // 记录当前可见状态 container: HTMLElement | null = null; cameraMoveHandler: any = null; // 改为相机移动事件处理器 lastUpdateTime: number = 0; // 用于控制更新频率 updateInterval: number = 100; // 更新间隔(毫秒) constructor(option: any) { super(hnMap); this.id = option.id; // console.log(`创建 Cesium divPoint: ${this.id}`, option); deepMerge(this.option, option); // 开始监听相机移动事件 this.startUpdateLoop(); } // 创建HTML元素 createHTMLElement() { // console.log(`创建HTML元素: ${this.id}`); const div = document.createElement("div"); div.id = `cesium-divpoint-${this.id}`; div.className = "hnmap-cesium-divpoint"; // 设置固定样式 div.style.position = "absolute"; div.style.pointerEvents = "auto"; div.style.cursor = "pointer"; // div.style.zIndex = "9999"; // 确保在最上层 // div.style.transform = "translate(-50%, -50%)"; // 居中定位 // 初始位置设为屏幕外 div.style.left = "0"; div.style.top = "0"; // div.style.visibility = "hidden"; // 直接设置HTML内容 div.innerHTML = this.option.html; this.htmlElement = div; // 添加到容器 this.addToContainer(); return div; } // 创建所有元素 createElements() { // 立即创建HTML元素 this.createHTMLElement(); // 创建Cesium实体 this.createCesiumEntity(); } // 添加到容器 addToContainer() { if (!this.htmlElement) return; let container = null; // 如果没有找到,尝试获取 viewer if (!container && hnMap.map.map) { container = hnMap.map.map.container || hnMap.map.map._container; } if (container) { // console.log(`找到地图容器,添加divPoint: ${this.id}`, container); // 确保容器有相对定位 const containerStyle = window.getComputedStyle(container); if (containerStyle.position === "static") { container.style.position = "relative"; } // 检查是否已经添加过 if (!container.contains(this.htmlElement)) { container.appendChild(this.htmlElement); } this.container = container; } else { console.warn(`无法找到地图容器: ${this.id},将添加到body`); // 添加到 body 作为备选 if (!document.body.contains(this.htmlElement)) { document.body.appendChild(this.htmlElement); } } } // 创建Cesium实体 createCesiumEntity() { if (!this.option.position || this.option.position.length < 2) { console.error(`无效的位置数据: ${this.id}`, this.option.position); return; } const [lng, lat, alt = 0] = convertPosition(this.option.position); const position = Cesium.Cartesian3.fromDegrees(lng, lat, alt); // console.log(`创建Cesium实体: ${this.id}`, position); const near = getLevelMiddleHeight(this.option.distanceDisplayCondition_near); const far = getLevelMiddleHeight(this.option.distanceDisplayCondition_far); // 创建透明实体(仅用于坐标定位) const entityOptions: any = { id: this.id, position: position, show: true, label: { text: " ", // 空文本 show: true, pixelOffset: new Cesium.Cartesian2( Number(this.option.offset[0]) || 0, -Number(this.option.offset[1]) || 0 // Cesium Y轴向上,需要取反 ), disableDepthTestDistance: Number.POSITIVE_INFINITY, distanceDisplayCondition: this.option.distanceDisplayCondition ? new Cesium.DistanceDisplayCondition(near, far) : undefined, clampToGround:!(this.option.position&&this.option.position[2]), scaleByDistance: this.option.scaleByDistance ? new Cesium.NearFarScalar(1.0e2, 1.0, 1.0e7, 0.1) : undefined, }, }; // 创建实体并添加到地图 const viewer = this.getViewer(); if (viewer && viewer.entities) { this.graphic = viewer.entities.add(entityOptions); } else { this.graphic = new Cesium.Entity(entityOptions); } } // 获取 Cesium viewer getViewer(): any { if (!hnMap.map || !hnMap.map.map) return null; // 尝试不同的 viewer 访问方式 if (hnMap.map.map) return hnMap.map.map; return null; } // 获取场景 getScene(): any { const viewer = this.getViewer(); if (!viewer) return null; return viewer.scene || viewer._scene; } // 获取 canvas getCanvas(): HTMLCanvasElement | null { const scene = this.getScene(); if (!scene) return null; return scene.canvas || scene._canvas; } // 检查所有可见性条件 checkVisibilityConditions(): boolean { // 获取 viewer 和 scene const viewer = this.getViewer(); if (!viewer || !this.option.position || this.option.position.length < 2) { return false; } const [lng, lat, alt = 0] = convertPosition(this.option.position); const position = Cesium.Cartesian3.fromDegrees(lng, lat, alt); // 检查距离显示条件 if (this.option.distanceDisplayCondition) { const cameraPosition = viewer.camera.positionWC; const distance = Cesium.Cartesian3.distance(cameraPosition, position); const nearDistance = getLevelMiddleHeight( this.option.distanceDisplayCondition_near ); const farDistance = getLevelMiddleHeight( this.option.distanceDisplayCondition_far ); const minDistance = Math.min(nearDistance, farDistance); const maxDistance = Math.max(nearDistance, farDistance); const isWithinDistance = distance >= minDistance && distance <= maxDistance; if (!isWithinDistance) { return false; } } return true; } // 检查并创建/销毁元素 checkAndUpdate() { if (this.isDestroyed) return; // 获取 viewer 和 scene const viewer = this.getViewer(); if ( !viewer || !this.option.position || this.option.position.length < 2 ) { return false; } const [lng, lat, alt = 0] = convertPosition(this.option.position); const position = Cesium.Cartesian3.fromDegrees(lng, lat, alt); // 1. 检查是否在距离范围内 // 检查距离显示条件 if (this.option.distanceDisplayCondition) { const cameraPosition = viewer.camera.positionWC; const distance = Cesium.Cartesian3.distance(cameraPosition, position); const nearDistance = getLevelMiddleHeight( this.option.distanceDisplayCondition_near ); const farDistance = getLevelMiddleHeight( this.option.distanceDisplayCondition_far ); const minDistance = Math.min(nearDistance, farDistance); const maxDistance = Math.max(nearDistance, farDistance); if (distance >= minDistance && distance <= maxDistance) { if (!this.isVisible) { // console.log('符合要求,但是需要绘制') this.createElements(); this.isVisible = true; this.updateHTMLElementPosition(); } else { // console.log('符合要求直接update') this.updateHTMLElementPosition(); } } else { // console.log('不符合要求,销毁') this.destroyElements(); this.isVisible = false; } } else { // console.log('不设置distanceDisplayCondition,直接createElements') if(!this.isVisible){ this.isVisible = true this.createElements(); } this.updateHTMLElementPosition(); } } // 启动更新循环 - 使用相机移动事件 startUpdateLoop() { const viewer = this.getViewer(); if (!viewer || !viewer.camera) { console.error(`无法获取viewer或camera: ${this.id}`); return; } // 创建相机移动事件处理器 this.cameraMoveHandler = () => { if (this.isDestroyed) return; this.checkAndUpdate(); }; let lastCameraStr = ''; viewer.scene.postRender.addEventListener(() => { const cam = viewer.camera; const pos = Cesium.Cartographic.fromCartesian(cam.positionWC); const str = `${pos.longitude.toFixed(6)},${pos.latitude.toFixed(6)},${pos.height.toFixed(0)},${cam.heading.toFixed(3)}`; if (str !== lastCameraStr) { lastCameraStr = str; // console.log('Camera changed:', str); // 触发你的逻辑(如更新弹窗位置、同步地图等) if(this.cameraMoveHandler){ this.cameraMoveHandler() } } }); // 注册相机移动事件监听器 // viewer.camera.moveEnd.addEventListener(this.cameraMoveHandler); } // 更新HTML元素位置 updateHTMLElementPosition() { if (!this.htmlElement || this.isDestroyed) return; try { const viewer = this.getViewer(); const scene = this.getScene(); if (!viewer || !scene) { this.htmlElement.style.visibility = "hidden"; return; } const [lng, lat, alt = 0] = convertPosition(this.option.position); const position = Cesium.Cartesian3.fromDegrees(lng, lat, alt); const screenPosition = scene.cartesianToCanvasCoordinates(position); if (screenPosition && Cesium.defined(screenPosition)) { // === 1. 计算相机到目标点的距离(米)=== const cameraDistance = Cesium.Cartesian3.distance( viewer.camera.position, position ); // === 2. 获取配置参数 === const near = getLevelMiddleHeight(this.option.distanceDisplayCondition_near ?? 1); // 默认 0(不限近) const far = getLevelMiddleHeight(this.option.distanceDisplayCondition_far ?? 18); // 默认无限远 const minScale = this.option.minScale ?? 0.3; const maxScale = this.option.maxScale ?? 1.0; // === 3. 判断是否在显示范围内 === if (cameraDistance < near || cameraDistance > far) { this.htmlElement.style.visibility = "hidden"; return; } // === 4. 【关键】线性插值计算 scale === // 距离越小(越近)→ scale 越大 let scale: number; if (far === near) { scale = maxScale; // 防止除零 } else { // 归一化:0.0(在 far 处) → 1.0(在 near 处) const t = 1.0 - (cameraDistance - near) / (far - near); // clamp to [0, 1] const clampedT = Math.max(0, Math.min(1, t)); // 映射到 [minScale, maxScale] scale = minScale + (maxScale - minScale) * clampedT; } // === 5. 计算页面坐标 === const canvasRect = scene.canvas.getBoundingClientRect(); const offsetX = Number(this.option.offset?.[0] || 0); const offsetY = Number(this.option.offset?.[1] || 0); const pageX = screenPosition.x + offsetX; const pageY = screenPosition.y + offsetY; // === 6. 应用样式 === this.htmlElement.style.position = 'absolute'; this.htmlElement.style.left = `${pageX}px`; this.htmlElement.style.top = `${pageY}px`; // this.htmlElement.style.transform = `scale(${scale})`; this.htmlElement.style.transform = `scale(1)`; this.htmlElement.style.transformOrigin = 'left top'; this.htmlElement.style.visibility = 'visible'; } else { this.htmlElement.style.visibility = 'hidden'; } } catch (error) { console.error(`更新位置失败: ${this.id}`, error); this.htmlElement.style.visibility = 'hidden'; } } // 设置属性 set(option: any) { if (this.isDestroyed) return; console.log(`更新divPoint: ${this.id}`, option); deepMerge(this.option, option); // 更新HTML内容 if (option.html !== undefined && this.htmlElement) { this.htmlElement.innerHTML = option.html; } // 更新偏移 if (option.offset !== undefined && this.graphic && this.graphic.label) { const [offsetX, offsetY] = option.offset; this.graphic.label.pixelOffset = new Cesium.Cartesian2( Number(offsetX) || 0, -Number(offsetY) || 0 // Cesium Y轴向上,需要取反 ); } // 更新距离显示条件 if ( option.distanceDisplayCondition !== undefined || option.distanceDisplayCondition_near !== undefined || option.distanceDisplayCondition_far !== undefined ) { if (this.graphic && this.graphic.label) { this.graphic.label.distanceDisplayCondition = option.distanceDisplayCondition ? new Cesium.DistanceDisplayCondition( getLevelMiddleHeight( this.option.distanceDisplayCondition_near ), getLevelMiddleHeight(this.option.distanceDisplayCondition_far) ) : undefined; } } // 如果当前可见,触发一次检查更新 if (this.isVisible) { this.checkAndUpdate(); } } // 销毁所有元素(但不销毁实例) destroyElements() { // console.log(`销毁divPoint元素: ${this.id}`); if(this.option.distanceDisplayCondition){ // 1. 移除HTML元素 if (this.htmlElement) { try { if (this.htmlElement.parentNode) { this.htmlElement.parentNode.removeChild(this.htmlElement); } this.htmlElement = null; } catch (error) { console.error(`移除HTML元素失败: ${this.id}`, error); } } // 2. 移除Cesium实体 if (this.graphic) { try { const viewer = this.getViewer(); if (viewer && viewer.entities) { if (viewer.entities.contains(this.graphic)) { viewer.entities.remove(this.graphic); } } this.graphic = null; } catch (error) { console.error(`移除Cesium实体失败: ${this.id}`, error); } } this.isVisible = false; } } // 销毁整个实例 destroy() { this.isDestroyed = true; // 移除相机移动事件监听器 if (this.cameraMoveHandler) { const viewer = this.getViewer(); if (viewer && viewer.camera) { try { // viewer.camera.moveEnd.removeEventListener(this.cameraMoveHandler); viewer.scene.postRender.removeEventListener(this.cameraMoveHandler) this.cameraMoveHandler = null; } catch (error) { console.error(`移除相机移动事件监听器失败: ${this.id}`, error); } } } // 销毁元素 this.destroyElements(); // 清理引用 this.container = null; this.option = null; this.config = null; console.log(`divPoint ${this.id} 已完全销毁`); } } const fn: any = { mars3d: mars3d_class, gaode: mars3d_class, siji: siji_class, cesium: cesium_class, }; return fn[hnMap.mapType]; };