Cesium地图弹窗组件:打造专业级地理信息展示系统

一、前言:为什么需要专业的弹窗组件?

        在现代WebGIS应用中,地图弹窗是用户与地理信息交互的核心界面。一个优秀的弹窗组件不仅能清晰展示数据,更能提升用户体验和应用专业性。Cesium作为强大的3D地球可视化库,虽然功能丰富,但并未提供开箱即用的高级弹窗功能。本文将介绍如何实现一个功能完备、样式优雅的Cesium弹窗组件。

二、功能亮点

  • 互斥显示:同时只显示一个弹窗,避免界面混乱

  • 高度自定义:支持HTML字符串和函数生成动态内容

  • 智能定位:自动跟随实体位置,相机移动时实时更新

  • 优雅动画:平滑的显示/隐藏过渡效果

  • 响应式设计:自适应不同屏幕尺寸和内容长度

核心实现代码

class CesiumPopup {
    constructor(viewer, options = {}) {
        this.viewer = viewer;
        this.options = Object.assign({
            offset: [0, -45],
            closeButton: true,
            maxWidth: 320,
            className: 'cesium-popup'
        }, options);
        
        this.currentPopup = null;
        this.handler = null;
        
        this.init();
    }
    
    // 初始化事件处理
    init() {
        const self = this;
        
        // 使用屏幕空间事件处理器
        this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
        
        // 监听左键点击事件
        this.handler.setInputAction(function(event) {
            const picked = self.viewer.scene.pick(event.position);
            if (Cesium.defined(picked) && picked.id) {
                // 如果点击的是实体且有popupContent属性
                if (picked.id.popupContent) {
                    self.showPopup(picked.id, event.position);
                }
            } else {
                // 点击空白处关闭弹窗
                self.closePopup();
            }
        }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    }
    
    // 显示弹窗
    showPopup(entity, position) {
        // 关闭现有弹窗
        this.closePopup();
        
        // 创建弹窗容器
        const popup = document.createElement('div');
        popup.className = this.options.className;
        popup.style.maxWidth = this.options.maxWidth + 'px';
        
        // 添加关闭按钮
        if (this.options.closeButton) {
            const closeBtn = document.createElement('button');
            closeBtn.className = 'close-btn';
            closeBtn.innerHTML = '×';
            closeBtn.onclick = () => this.closePopup();
            popup.appendChild(closeBtn);
        }
        
        // 设置弹窗内容
        if (typeof entity.popupContent === 'string') {
            popup.innerHTML += entity.popupContent;
        } else if (typeof entity.popupContent === 'function') {
            popup.innerHTML += entity.popupContent();
        } else {
            console.warn('Entity has no valid popupContent');
            return;
        }
        
        // 添加到DOM
        this.viewer.container.appendChild(popup);
        this.currentPopup = {
            element: popup,
            entity: entity
        };
        
        // 定位弹窗
        this.positionPopup(position);
        
        // 监听相机变化事件,更新弹窗位置
        this.cameraMoveHandler = this.viewer.camera.moveEnd.addEventListener(() => {
            if (this.currentPopup) {
                const newPosition = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                    this.viewer.scene, 
                    entity.position.getValue(Cesium.JulianDate.now())
                );
                if (newPosition) {
                    this.positionPopup(newPosition);
                }
            }
        });
    }
    
    // 其他方法...
}

三、使用指南

基本集成

// 初始化Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
    terrainProvider: Cesium.createWorldTerrain()
});

// 创建弹窗组件实例
const popupManager = new CesiumPopup(viewer);

添加带弹窗的实体

// 添加简单实体
const entity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
    billboard: {
        image: 'pin.png',
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        width: 40,
        height: 40
    },
    popupContent: `
        <h3>位置标题</h3>
        <p>详细信息描述</p>
        <p>更多内容...</p>
    `
});

// 添加动态内容实体
const dynamicEntity = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-74.5, 40.5),
    billboard: {
        image: 'pin.png',
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
        width: 40,
        height: 40
    },
    popupContent: function() {
        const date = new Date();
        return `
            <h3>动态内容</h3>
            <p>当前时间: ${date.toLocaleTimeString()}</p>
            <p>随机值: ${Math.random()}</p>
        `;
    }
});

四、高级应用场景

1. 实时数据监控

// 创建实时监控点
function createMonitoringPoint(position, title) {
    return viewer.entities.add({
        position: position,
        billboard: {
            image: 'sensor.png',
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM
        },
        popupContent: function() {
            // 模拟实时数据
            const temperature = (20 + Math.random() * 10).toFixed(1);
            const humidity = (50 + Math.random() * 30).toFixed(1);
            
            return `
                <h3>${title}监测站</h3>
                <p><strong>温度:</strong> ${temperature}°C</p>
                <p><strong>湿度:</strong> ${humidity}%</p>
                <p><strong>更新时间:</strong> ${new Date().toLocaleTimeString()}</p>
                <div class="alert ${temperature > 25 ? 'alert-warning' : 'alert-info'}">
                    ${temperature > 25 ? '高温警告' : '状态正常'}
                </div>
            `;
        }
    });
}

2. 交互式操作面板

// 创建带交互操作的弹窗
function createInteractivePoint(position, title) {
    return viewer.entities.add({
        position: position,
        billboard: {
            image: 'interactive.png',
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM
        },
        popupContent: function() {
            return `
                <h3>${title}</h3>
                <p>选择操作类型:</p>
                <div class="button-group">
                    <button onclick="handleAction('detail')">查看详情</button>
                    <button onclick="handleAction('edit')">编辑信息</button>
                    <button onclick="handleAction('navigate')">导航到此</button>
                </div>
                <div id="action-result"></div>
            `;
        }
    });
}

五、以下是全部的实现逻辑(需要本机搭建服务)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cesium弹窗组件 - 修复关闭按钮点击问题</title>
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.87/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.87/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
    <style>
        html, body, #cesiumContainer {
            width: 100%; 
            height: 100%; 
            margin: 0; 
            padding: 0; 
            overflow: hidden;
            font-family: 'Microsoft YaHei', Arial, sans-serif;
        }
        
        /* 弹窗样式 */
        .cesium-popup {
            position: absolute;
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
            padding: 20px;
            min-width: 240px;
            max-width: 320px;
            z-index: 1000;
            transform: translate(-50%, -100%);
            margin-top: -20px;
            transition: opacity 0.3s ease;
            display: none;
        }
        
        .cesium-popup.active {
            display: block;
        }
        
        .cesium-popup:after {
            content: '';
            position: absolute;
            top: 100%;
            left: 50%;
            margin-left: -12px;
            border-width: 12px;
            border-style: solid;
            border-color: white transparent transparent transparent;
        }
        
        .cesium-popup h3 {
            margin: 0 0 12px 0;
            font-size: 18px;
            color: #2c3e50;
            border-bottom: 2px solid #3498db;
            padding-bottom: 8px;
        }
        
        .cesium-popup p {
            margin: 8px 0;
            font-size: 14px;
            color: #34495e;
            line-height: 1.5;
        }
        
        .cesium-popup .close-btn {
            position: absolute;
            top: 12px;
            right: 15px;
            cursor: pointer;
            font-size: 20px;
            color: #95a5a6;
            background: none;
            border: none;
            font-weight: bold;
            z-index: 1001;
            width: 30px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            pointer-events: auto !important;
        }
        
        .cesium-popup .close-btn:hover {
            color: #e74c3c;
            background-color: #f5f5f5;
        }
        
        .cesium-popup .popup-content {
            pointer-events: none;
        }
        
        .cesium-popup .popup-content * {
            pointer-events: auto;
        }
        
        .header {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            background: rgba(42, 42, 42, 0.8);
            color: white;
            padding: 15px 20px;
            z-index: 1000;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        }
        
        .header h1 {
            margin: 0;
            font-size: 22px;
            font-weight: 500;
        }
        
        .header p {
            margin: 5px 0 0;
            font-size: 14px;
            color: #ecf0f1;
        }
        
        .control-panel {
            position: absolute;
            top: 100px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 999;
            width: 250px;
        }
        
        .control-panel h3 {
            margin-top: 0;
            color: #2c3e50;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 8px;
        }
        
        .control-panel p {
            font-size: 14px;
            color: #7f8c8d;
            line-height: 1.4;
        }
        
        .control-panel button {
            background: #3498db;
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            margin-top: 10px;
            width: 100%;
        }
        
        .control-panel button:hover {
            background: #2980b9;
        }
        
        .debug-info {
            position: absolute;
            top: 150px;
            left: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 999;
            width: 250px;
            max-height: 300px;
            overflow-y: auto;
        }
        
        .debug-info h3 {
            margin-top: 0;
            color: #2c3e50;
            border-bottom: 1px solid #bdc3c7;
            padding-bottom: 8px;
        }
        
        #eventLog {
            font-size: 12px;
            line-height: 1.4;
            color: #34495e;
        }
        
        .status-bar {
            position: absolute;
            bottom: 20px;
            right: 20px;
            background: rgba(255, 255, 255, 0.9);
            padding: 10px 15px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 999;
            font-size: 14px;
            color: #2c3e50;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>Cesium弹窗组件 - 修复关闭按钮点击问题</h1>
        <p>点击地图上的扎点查看弹窗效果,点击关闭按钮可以关闭弹窗</p>
    </div>
    
    <div id="cesiumContainer"></div>
    
    <div class="control-panel">
        <h3>控制面板</h3>
        <p>点击地图上的任意标记点查看详细信息。</p>
        <p>点击弹窗右上角的关闭按钮可以关闭弹窗。</p>
        <button id="addPoint">添加随机扎点</button>
        <button id="clearAll">清除所有扎点</button>
    </div>
    
    <div class="debug-info">
        <h3>事件日志</h3>
        <div id="eventLog">事件日志将显示在这里</div>
    </div>
    
    <div class="status-bar" id="statusBar">
        就绪 - 点击地图上的扎点查看信息
    </div>

    <script>
        // 设置Cesium Ion访问令牌
        Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1ZDVlMTQ5ZC1kZTYxLTQzM2MtODU3ZS1kMjA4YzZiOTBjZGEiLCJpZCI6Mjc5NTY3LCJpYXQiOjE3NDA2NDMyODJ9.ScSAm-1y-8DT0uq4DXGt8NQrEPVo3b_f-P_5bghF7rU'

        // 初始化Cesium Viewer
        const viewer = new Cesium.Viewer('cesiumContainer', {
            terrainProvider: Cesium.createWorldTerrain(),
            baseLayerPicker: false,
            homeButton: false,
            sceneModePicker: false,
            navigationHelpButton: false,
            animation: false,
            timeline: false,
            fullscreenButton: false,
            geocoder: false
        });
        
        // 设置初始视角
        viewer.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(-75.0, 40.0, 5000000),
            orientation: {
                heading: 0,
                pitch: -Math.PI/2,
                roll: 0
            }
        });

        // 更新状态栏
        function updateStatus(message) {
            const statusBar = document.getElementById('statusBar');
            statusBar.textContent = message;
        }
        
        // 更新事件日志
        function logEvent(message) {
            const eventLog = document.getElementById('eventLog');
            const timestamp = new Date().toLocaleTimeString();
            eventLog.innerHTML = `<span style="color:#777;">[${timestamp}]</span> ${message}<br>` + eventLog.innerHTML;
            
            // 保持日志长度
            if (eventLog.children.length > 20) {
                eventLog.removeChild(eventLog.lastChild);
            }
        }

        // 定义弹窗组件类
        class CesiumPopup {
            constructor(viewer, options = {}) {
                this.viewer = viewer;
                this.options = Object.assign({
                    offset: [0, -45],
                    closeButton: true,
                    maxWidth: 320,
                    className: 'cesium-popup'
                }, options);
                
                this.popups = new Map(); // 存储所有弹窗
                this.activePopup = null; // 当前活动弹窗
                this.handler = null;
                this.preUpdateHandler = null;
                
                this.init();
            }
            
            // 初始化事件处理
            init() {
                const self = this;
                
                // 使用屏幕空间事件处理器
                this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
                
                // 监听左键点击事件
                this.handler.setInputAction(function(event) {
                    const picked = self.viewer.scene.pick(event.position);
                    if (Cesium.defined(picked) && picked.id) {
                        // 如果点击的是实体且有popupContent属性
                        if (picked.id.popupContent) {
                            self.showPopup(picked.id, event.position);
                            updateStatus(`已显示弹窗: ${picked.id.name || "未命名实体"}`);
                            logEvent(`地图点击: 显示弹窗 ${picked.id.name || "未命名实体"}`);
                        }
                    } else {
                        // 点击空白处关闭弹窗
                        self.hideAllPopups();
                        updateStatus("已关闭所有弹窗");
                        logEvent("地图空白处点击: 关闭所有弹窗");
                    }
                }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
                
                // 监听场景更新事件,实时更新所有弹窗位置
                this.preUpdateHandler = this.viewer.scene.preUpdate.addEventListener(function() {
                    self.updateAllPopupsPosition();
                });
            }
            
            // 为实体创建弹窗
            createPopupForEntity(entity) {
                if (this.popups.has(entity.id)) return;
                
                // 创建弹窗容器
                const popup = document.createElement('div');
                popup.className = this.options.className;
                popup.style.maxWidth = this.options.maxWidth + 'px';
                popup.id = `popup-${entity.id}`;
                
                // 添加关闭按钮 - 使用单独的容器
                if (this.options.closeButton) {
                    const closeBtnContainer = document.createElement('div');
                    closeBtnContainer.style.position = 'absolute';
                    closeBtnContainer.style.top = '10px';
                    closeBtnContainer.style.right = '10px';
                    closeBtnContainer.style.zIndex = '1002';
                    
                    const closeBtn = document.createElement('button');
                    closeBtn.className = 'close-btn';
                    closeBtn.innerHTML = '×';
                    closeBtn.title = '关闭弹窗';
                    
                    // 直接绑定点击事件
                    closeBtn.onclick = (e) => {
                        e.stopPropagation();
                        e.preventDefault();
                        this.hidePopup(entity.id);
                        updateStatus(`已关闭弹窗: ${entity.name || "未命名实体"}`);
                        logEvent(`关闭按钮点击: 关闭弹窗 ${entity.name || "未命名实体"}`);
                        return false;
                    };
                    
                    closeBtnContainer.appendChild(closeBtn);
                    popup.appendChild(closeBtnContainer);
                }
                
                // 创建内容容器
                const contentContainer = document.createElement('div');
                contentContainer.className = 'popup-content';
                
                // 设置弹窗内容
                if (typeof entity.popupContent === 'string') {
                    contentContainer.innerHTML = entity.popupContent;
                } else if (typeof entity.popupContent === 'function') {
                    contentContainer.innerHTML = entity.popupContent();
                } else {
                    console.warn('Entity has no valid popupContent');
                    return;
                }
                
                popup.appendChild(contentContainer);
                
                // 阻止弹窗点击事件冒泡到地图
                popup.addEventListener('click', function(e) {
                    e.stopPropagation();
                    logEvent("弹窗点击: 事件已阻止冒泡");
                });
                
                // 添加到DOM
                this.viewer.container.appendChild(popup);
                
                // 存储弹窗引用
                this.popups.set(entity.id, {
                    element: popup,
                    entity: entity
                });
                
                logEvent(`创建弹窗: ${entity.name || "未命名实体"}`);
            }
            
            // 显示指定实体的弹窗
            showPopup(entity, position) {
                // 隐藏所有弹窗
                this.hideAllPopups();
                
                // 如果该实体没有弹窗,先创建一个
                if (!this.popups.has(entity.id)) {
                    this.createPopupForEntity(entity);
                }
                
                // 显示弹窗
                const popup = this.popups.get(entity.id);
                popup.element.classList.add('active');
                this.activePopup = popup;
                
                // 定位弹窗
                this.positionPopup(popup, position);
                
                logEvent(`显示弹窗: ${entity.name || "未命名实体"}`);
            }
            
            // 更新所有弹窗位置
            updateAllPopupsPosition() {
                for (const [entityId, popup] of this.popups) {
                    if (popup.element.classList.contains('active')) {
                        const position = Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                            this.viewer.scene, 
                            popup.entity.position.getValue(Cesium.JulianDate.now())
                        );
                        if (position) {
                            this.positionPopup(popup, position);
                        }
                    }
                }
            }
            
            // 定位弹窗
            positionPopup(popup, position) {
                const popupElement = popup.element;
                const offset = this.options.offset;
                
                // 计算弹窗位置
                const x = position.x + offset[0];
                const y = position.y + offset[1];
                
                // 检查弹窗是否在视图内
                const containerRect = this.viewer.container.getBoundingClientRect();
                const popupRect = popupElement.getBoundingClientRect();
                
                // 如果弹窗超出右边界
                if (x + popupRect.width / 2 > containerRect.width) {
                    popupElement.style.left = (containerRect.width - popupRect.width / 2) + 'px';
                } 
                // 如果弹窗超出左边界
                else if (x - popupRect.width / 2 < 0) {
                    popupElement.style.left = (popupRect.width / 2) + 'px';
                } 
                // 正常情况
                else {
                    popupElement.style.left = x + 'px';
                }
                
                // 如果弹窗超出上边界
                if (y - popupRect.height < 0) {
                    popupElement.style.top = (popupRect.height + 10) + 'px';
                    popupElement.style.transform = 'translate(-50%, 0)';
                    popupElement.style.marginTop = '0';
                } 
                // 正常情况
                else {
                    popupElement.style.top = y + 'px';
                    popupElement.style.transform = 'translate(-50%, -100%)';
                    popupElement.style.marginTop = '-20px';
                }
            }
            
            // 隐藏指定弹窗
            hidePopup(entityId) {
                if (this.popups.has(entityId)) {
                    const popup = this.popups.get(entityId);
                    popup.element.classList.remove('active');
                    
                    if (this.activePopup && this.activePopup.entity.id === entityId) {
                        this.activePopup = null;
                    }
                    
                    logEvent(`隐藏弹窗: ${popup.entity.name || "未命名实体"}`);
                }
            }
            
            // 隐藏所有弹窗
            hideAllPopups() {
                for (const [entityId, popup] of this.popups) {
                    popup.element.classList.remove('active');
                }
                this.activePopup = null;
                logEvent("隐藏所有弹窗");
            }
            
            // 移除指定实体的弹窗
            removePopup(entityId) {
                if (this.popups.has(entityId)) {
                    const popup = this.popups.get(entityId);
                    if (popup.element.parentNode) {
                        popup.element.parentNode.removeChild(popup.element);
                    }
                    this.popups.delete(entityId);
                    
                    if (this.activePopup && this.activePopup.entity.id === entityId) {
                        this.activePopup = null;
                    }
                    
                    logEvent(`移除弹窗: ${popup.entity.name || "未命名实体"}`);
                }
            }
            
            // 清除所有弹窗
            clearAllPopups() {
                for (const [entityId, popup] of this.popups) {
                    if (popup.element.parentNode) {
                        popup.element.parentNode.removeChild(popup.element);
                    }
                }
                this.popups.clear();
                this.activePopup = null;
                logEvent("清除所有弹窗");
            }
            
            // 销毁组件
            destroy() {
                this.clearAllPopups();
                if (this.handler) {
                    this.handler.destroy();
                }
                if (this.preUpdateHandler) {
                    this.viewer.scene.preUpdate.removeEventListener(this.preUpdateHandler);
                }
                logEvent("销毁弹窗组件");
            }
        }

        // 创建弹窗组件实例
        const popupManager = new CesiumPopup(viewer);
        
        // 添加示例扎点
        function addDemoEntities() {
            // 示例1 - 纽约
            const entity1 = viewer.entities.add({
                name: "纽约市",
                position: Cesium.Cartesian3.fromDegrees(-74.00597, 40.71427),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.ROYALBLUE
                },
                popupContent: `
                    <h3>纽约市</h3>
                    <p><strong>人口:</strong> 8,400,000</p>
                    <p><strong>特色:</strong> 自由女神像、中央公园、时代广场</p>
                    <p>美国最大城市和金融中心,被称为"不夜城"。</p>
                `
            });
            
            // 示例2 - 华盛顿
            const entity2 = viewer.entities.add({
                name: "华盛顿特区",
                position: Cesium.Cartesian3.fromDegrees(-77.03687, 38.90719),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.CRIMSON
                },
                popupContent: `
                    <h3>华盛顿特区</h3>
                    <p><strong>人口:</strong> 705,749</p>
                    <p><strong>特色:</strong> 白宫、国会大厦、林肯纪念堂</p>
                    <p>美国首都,政治中心和许多国家纪念馆的所在地。</p>
                `
            });
            
            // 示例3 - 波士顿
            const entity3 = viewer.entities.add({
                name: "波士顿",
                position: Cesium.Cartesian3.fromDegrees(-71.05888, 42.36025),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.FORESTGREEN
                },
                popupContent: `
                    <h3>波士顿</h3>
                    <p><strong>人口:</strong> 675,647</p>
                    <p><strong>特色:</strong> 自由之路、哈佛大学、芬威公园</p>
                    <p>美国最古老的城市之一,以其丰富的历史和高等教育机构闻名。</p>
                `
            });
            
            // 示例4 - 迈阿密
            const entity4 = viewer.entities.add({
                name: "迈阿密",
                position: Cesium.Cartesian3.fromDegrees(-80.19179, 25.76168),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.GOLD
                },
                popupContent: `
                    <h3>迈阿密</h3>
                    <p><strong>人口:</strong> 463,347</p>
                    <p><strong>特色:</strong> 南海滩、艺术装饰街区、温暖气候</p>
                    <p>国际金融和文化中心,以其拉丁文化影响和海滩闻名。</p>
                `
            });
            
            // 示例5 - 芝加哥
            const entity5 = viewer.entities.add({
                name: "芝加哥",
                position: Cesium.Cartesian3.fromDegrees(-87.62980, 41.87811),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.PURPLE
                },
                popupContent: function() {
                    const date = new Date();
                    return `
                        <h3>芝加哥</h3>
                        <p><strong>访问时间:</strong> ${date.toLocaleTimeString()}</p>
                        <p><strong>特色:</strong> 威利斯大厦、千禧公园、深盘披萨</p>
                        <p>美国第三大城市,以其建筑、文化和美食闻名。</p>
                    `;
                }
            });
            
            // 将视角移动到实体范围
            viewer.zoomTo(viewer.entities);
            
            return [entity1, entity2, entity3, entity4, entity5];
        }
        
        // 添加随机扎点
        function addRandomPoint() {
            const longitude = -180 + Math.random() * 360;
            const latitude = -90 + Math.random() * 180;
            
            const entity = viewer.entities.add({
                name: "随机位置",
                position: Cesium.Cartesian3.fromDegrees(longitude, latitude),
                billboard: {
                    image: 'https://ecmb.bdimg.com/tam-ogel/-440932281_-336120762_88_88.png',
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    width: 48,
                    height: 48,
                    color: Cesium.Color.PURPLE
                },
                popupContent: `
                    <h3>随机位置</h3>
                    <p><strong>经度:</strong> ${longitude.toFixed(4)}°</p>
                    <p><strong>纬度:</strong> ${latitude.toFixed(4)}°</p>
                    <p>这是一个随机生成的位置点。</p>
                `
            });
            
            updateStatus(`已添加随机扎点: ${longitude.toFixed(2)}°, ${latitude.toFixed(2)}°`);
            logEvent(`添加随机扎点: ${longitude.toFixed(2)}°, ${latitude.toFixed(2)}°`);
            return entity;
        }
        
        // 清除所有实体
        function clearAllEntities() {
            viewer.entities.removeAll();
            popupManager.clearAllPopups();
            updateStatus("已清除所有扎点和弹窗");
            logEvent("清除所有实体和弹窗");
        }
        
        // 添加示例实体
        const demoEntities = addDemoEntities();
        
        // 添加控制按钮事件
        document.getElementById('addPoint').addEventListener('click', function() {
            addRandomPoint();
        });
        
        document.getElementById('clearAll').addEventListener('click', function() {
            clearAllEntities();
        });
        
        // 添加说明
        console.log("Cesium弹窗组件已初始化");
        console.log("- 点击地图上的标记点查看弹窗");
        console.log("- 弹窗互斥显示,一次只显示一个");
        console.log("- 移动或缩放地图时,弹窗会跟随扎点实时移动");
        console.log("- 点击弹窗右上角的关闭按钮可以关闭弹窗");
        
        updateStatus("就绪 - 点击地图上的扎点查看信息");
        logEvent("Cesium弹窗组件初始化完成");
    </script>
</body>
</html>

结语

        本文介绍的Cesium弹窗组件提供了完整的地图信息展示解决方案,既保留了Cesium强大的地理可视化能力,又弥补了其在用户交互方面的不足。通过灵活的配置选项和丰富的扩展接口,开发者可以快速构建出专业级的地图应用界面。

        该组件已在实际项目中得到验证,能够有效提升用户体验和操作效率。无论是简单的信息展示还是复杂的交互操作,都能提供稳定可靠的支持。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值