# Ciyon Web3D 应用开发指南 ## 概述 本文档为AI开发3D地图应用提供指导,重点讲解如何基于 `cemap.html` 进行二次开发,实现资产点击交互和统计图表展示等功能。 ## 技术栈 ### 前端 - **核心引擎**: Cesium.js (基于WebGL的3D地球可视化引擎) - **UI框架**: 自定义框架 (ciy.js, ciycmp.js) - **图表库**: ECharts (可选,用于统计图表) ### 数据库 - MySQL - **主要数据表**: - `zc_cemap_data` - 地图配置数据 - `zc_cemap_bill` - 指示牌资源 - `zc_cemap_glb` - GLB模型资源 --- ## 资产管理 ### 资产说明 系统提供以下资产类型,可通过后台管理界面进行管理: 1. **GLB模型资源** - 3D建筑、设施等模型 - 管理界面: `glb.html` - 数据表: `zc_cemap_glb` - 支持路径分类管理 2. **指示牌资源** - 平面图片、标识牌等 - 管理界面: `bill.html` - 数据表: `zc_cemap_bill` - 支持路径分类管理 3. **3D Tile资源** - 实景航拍、大规模3D场景 - 管理界面: `ceeditor.html`(地编器) - 可直接在编辑器中添加和配置 ### 地图配置 地图配置存储在 `zc_cemap_data` 表的 `mapjson` 字段中,包含: - 地图源设置(天地图、高德、腾讯、百度等) - 3D Tile列表 - Entity列表(GLB模型、指示牌等) - 飞行点列表 - 地形、遮罩等设置 --- ## 核心API - ciyearth4.js ### 1. 初始化地图 ```javascript var ce = new ciyearth(); ce.init({ domid: 'ceContainer', // DOM容器ID或DOM对象 personheight: 1.8, // 眼睛到地面高度 timeline: 0, // 1显示时间轴 ion: 'your-ion-token', // Cesium Ion授权token sunhourmin: '12:12', // 太阳时间 setin: 3, // 1地球飞入, 2中国飞入, 3直接进入 wmts_source: 1, // 地图源: 1默认, 2纯色, 3天地图, 4高德, 5腾讯, 6百度 wmts_style: 1, // 样式: 1卫星, 2卫星(标注), 3矢量 tiles: [], // 3DTile数组 entitys: [], // Entity数组 flys: [] // 快捷定位点数组 }); ``` ### 3. Entity管理 #### Entity类型 - `model` - 3D模型(GLB/GLTF) - `billboard` - 永正面(始终朝向相机) - `plane` - 墙面(平面贴图) #### 添加Entity ```javascript ce.addentity({ type: 'model', // 类型: model/billboard/plane name: '建筑名称', bind: 'building_123', // 资产编号,用于关联业务数据 lng: 116.39, // 经度 lat: 39.9, // 纬度 height: 10, // 高度 url: '/path/to/model.glb', // 模型URL w: 10, // 宽度(米) - plane/billboard h: 10, // 高度(米) - plane/billboard sc: 1 // 缩放比例 }); ``` #### 添加标记 ```javascript ce.addmarker({ lng: 116.39, lat: 39.9, img: '/path/to/icon.png', width: 20, height: 27, autoheight: true }); ``` ### 4. 相机控制 #### 飞行到指定位置 ```javascript ce.flyTo({ fd: {x, y, z}, // 目标位置 fo: {h, p, r} // h:heading, p:pitch, r:roll }, 3); // 时间(秒) ``` #### 设置相机位置 ```javascript ce.setView({ fd: {x, y, z}, fo: {h, p, r} }); ``` #### 获取相机位置 ```javascript var camera = ce.getView(); // 返回: {fd: {x, y, z}, fo: {h, p, r}} ``` ### 5. 事件系统 ```javascript ce.ciyevent = function(type, act, data, data2) { if (type == 'entity') { if (act == 'leftclick') { // 左键点击Entity var entity = data.entity; var bindId = entity.ciydata.bind; // 获取资产编号 console.log('点击了资产:', bindId); // 在这里处理资产点击事件 showAssetInfo(bindId); } } else if (type == 'tile') { if (act == 'leftclick') { // 左键点击3D Tile var primitive = data.primitive; console.log('点击了Tile'); } } else if (type == 'control') { if (act == 'op') { // 控制操作中(飞行/行走) // data: Cartographic相机位置 updateCameraInfo(data); } } }; ``` ### 6. 飞行/行走模式 ```javascript // 开启飞行模式 ce.control_fly_open(false); // 飞行 ce.control_fly_open(true); // 行走 // 关闭飞行模式 ce.control_fly_close(); ``` **键盘控制**: - `W` - 前进 - `S` - 后退 - `A` - 左移 - `D` - 右移 - `Q` - 上升 - `E` - 下降 - `Z` - 左转 - `X` - 右转 - `空格` - 跳跃(行走模式) - `F` - 减速 - `R` - 加速 - `ESC` - 退出飞行模式 ### 7. 地形管理 ```javascript // 添加地形 ce.add_terrain('url', { url: 'https://example.com/terrain', requestVertexNormals: true, requestWaterMask: true }); // 获取高度 var height = await ce.getposheight(cartesian3); var floorHeight = ce.getfloorheight(); ``` --- ## 地图应用开发 ### 基础页面结构 ```html 3D地图应用
飞行
步行

统计数据

``` --- ## 事件交互 ### Entity点击事件 ```javascript ce.ciyevent = function(type, act, data, data2) { if (type == 'entity' && act == 'leftclick') { var entity = data.entity; // 获取Entity信息 var entityInfo = { id: entity.id, name: entity.name, type: entity.ciytype, bind: entity.ciydata.bind, // 资产编号 position: entity.position._value, customData: entity.ciydata // 自定义数据 }; console.log('点击Entity:', entityInfo); // 处理点击事件 handleEntityClick(entityInfo); } }; ``` ### Tile点击事件 ```javascript if (type == 'tile' && act == 'leftclick') { var primitive = data.primitive; console.log('点击3D Tile:', primitive); // 可以通过拾取获取Tile中的具体建筑信息 // 这需要3D Tile数据中包含属性信息 } ``` ### 自定义点击事件 ```javascript // 添加自定义点击处理器 var handler = new Cesium.ScreenSpaceEventHandler(ce.viewer.canvas); handler.setInputAction(function(click) { var pickedObject = ce.viewer.scene.pick(click.position); if (Cesium.defined(pickedObject)) { if (pickedObject.id) { // 点击了Entity var entity = pickedObject.id; console.log('Entity:', entity.name, entity.ciydata); } else if (pickedObject.primitive) { // 点击了Primitive(如3D Tile) console.log('Primitive:', pickedObject.primitive); } } else { // 点击了空白处 console.log('点击空白处'); // 可以关闭浮动面板 $5('#assetPanel').hide(); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); ``` --- ## 浮动面板 ### 创建浮动面板 ```javascript // 左侧面板 - 资产信息 var leftPanel = document.createElement('div'); leftPanel.className = 'float-panel left'; leftPanel.style.cssText = 'top: 80px; left: 20px; width: 320px;'; leftPanel.innerHTML = '

资产信息

'; document.body.appendChild(leftPanel); // 右侧面板 - 统计图表 var rightPanel = document.createElement('div'); rightPanel.className = 'float-panel right'; rightPanel.style.cssText = 'top: 80px; right: 20px; width: 380px;'; rightPanel.innerHTML = '

统计数据

'; document.body.appendChild(rightPanel); // 顶部面板 - 搜索/筛选 var topPanel = document.createElement('div'); topPanel.className = 'float-panel top'; topPanel.innerHTML = ''; document.body.appendChild(topPanel); // 底部面板 - 快捷操作 var bottomPanel = document.createElement('div'); bottomPanel.className = 'float-panel bottom'; bottomPanel.innerHTML = ''; document.body.appendChild(bottomPanel); ``` ### 面板样式 ```css .float-panel { position: absolute; background: rgba(255, 255, 255, 0.95); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 15px; z-index: 1000; transition: all 0.3s ease; } .float-panel.top { top: 20px; left: 50%; transform: translateX(-50%); } .float-panel.left { top: 80px; left: 20px; width: 320px; max-height: calc(100vh - 100px); overflow-y: auto; } .float-panel.right { top: 80px; right: 20px; width: 380px; max-height: calc(100vh - 100px); overflow-y: auto; } .float-panel.bottom { bottom: 20px; left: 50%; transform: translateX(-50%); } /* 面板折叠状态 */ .float-panel.collapsed { transform: scale(0); opacity: 0; pointer-events: none; } ``` ### 面板显示/隐藏 ```javascript // 显示面板 function showPanel(selector) { $5(selector).removeClass('collapsed'); } // 隐藏面板 function hidePanel(selector) { $5(selector).addClass('collapsed'); } // 切换面板 function togglePanel(selector) { $5(selector).toggleClass('collapsed'); } ``` --- ## 统计图表 ### 使用ECharts渲染图表 ```javascript // 渲染柱状图 function renderBarChart(containerId, data) { var chart = echarts.init(document.getElementById(containerId)); var option = { title: { text: data.title || '柱状图', left: 'center', textStyle: {fontSize: 12} }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.categories || [] }, yAxis: { type: 'value' }, series: [{ name: data.seriesName || '数值', type: 'bar', data: data.values || [], itemStyle: { color: '#5470c6' } }] }; chart.setOption(option); return chart; } // 渲染折线图 function renderLineChart(containerId, data) { var chart = echarts.init(document.getElementById(containerId)); var option = { title: { text: data.title || '折线图', left: 'center', textStyle: {fontSize: 12} }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.categories || [] }, yAxis: { type: 'value' }, series: [{ name: data.seriesName || '数值', type: 'line', data: data.values || [], smooth: true, areaStyle: { opacity: 0.3 } }] }; chart.setOption(option); return chart; } // 渲染饼图 function renderPieChart(containerId, data) { var chart = echarts.init(document.getElementById(containerId)); var option = { title: { text: data.title || '饼图', left: 'center', textStyle: {fontSize: 12} }, tooltip: { trigger: 'item' }, legend: { bottom: 0 }, series: [{ type: 'pie', radius: '50%', data: data.values || [], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } } }] }; chart.setOption(option); return chart; } ``` ### 响应式图表 ```javascript // 监听窗口大小变化 var charts = []; function addChart(chart) { charts.push(chart); } window.addEventListener('resize', function() { charts.forEach(function(chart) { chart.resize(); }); }); ``` --- ## 完整示例 ### 示例:智慧园区管理 ```javascript // 资产数据映射 var assetDataMap = { 'building_001': { name: 'A栋办公楼', type: '办公楼', floors: 18, area: 12000, occupancy: 85, status: '正常', energy: { today: 4500, month: 120000 }, visits: [120, 200, 150, 80, 70, 110, 130], timeDist: [20, 150, 280, 90] }, 'building_002': { name: 'B栋研发中心', type: '研发中心', floors: 12, area: 8000, occupancy: 92, status: '正常', energy: { today: 3800, month: 98000 }, visits: [90, 150, 180, 100, 60, 130, 140], timeDist: [15, 120, 250, 80] } }; // 处理Entity点击 function handleEntityClick(entityInfo) { var assetId = entityInfo.bind; if (!assetId || !assetDataMap[assetId]) { console.log('未知资产:', assetId); return; } var asset = assetDataMap[assetId]; // 显示资产信息面板 showAssetPanel(asset); // 更新统计图表 updateStatCharts(asset); // 飞行到资产位置 if (entityInfo.position) { ce.viewer.flyTo(ce.viewer.entities.getById(entityInfo.id), { offset: new Cesium.HeadingPitchRange(1.57, -0.6, 50) }); } } // 显示资产信息面板 function showAssetPanel(asset) { var html = '
'; html += '

' + asset.name + '

'; html += '
'; html += '
类型:' + asset.type + '
'; html += '
楼层:' + asset.floors + '层
'; html += '
面积:' + asset.area + '㎡
'; html += '
入住率:' + asset.occupancy + '%
'; html += '
状态:' + asset.status + '
'; html += '
'; html += '
'; html += '
今日能耗:' + asset.energy.today + ' kWh
'; html += '
本月能耗:' + asset.energy.month + ' kWh
'; html += '
'; html += '
'; $5('#assetContent').html(html); } // 更新统计图表 function updateStatCharts(asset) { // 更新访问量趋势图 var visitsChart = echarts.init(document.getElementById('visitsChart')); visitsChart.setOption({ series: [{ data: asset.visits }] }); // 更新时段分布图 var timeDistChart = echarts.init(document.getElementById('timeDistChart')); timeDistChart.setOption({ series: [{ data: asset.timeDist }] }); } // 初始化页面 function initPage() { // 初始化地图 ce.init({ domid: 'ceContainer', wmts_source: 4, wmts_style: 3, setin: 3, entitys: [ { type: 'model', name: 'A栋办公楼', bind: 'building_001', lng: 116.39, lat: 39.9, height: 0, url: '/models/building_a.glb' }, { type: 'model', name: 'B栋研发中心', bind: 'building_002', lng: 116.40, lat: 39.9, height: 0, url: '/models/building_b.glb' } ] }); // 设置事件回调 ce.ciyevent = function(type, act, data) { if (type == 'entity' && act == 'leftclick') { var entity = data.entity; handleEntityClick({ id: entity.id, bind: entity.ciydata.bind, position: entity.position._value }); } }; } // 页面加载完成后执行 ciyfn.pageload(initPage); ``` --- ## 最佳实践 ### 1. 性能优化 ```javascript // 使用requestRenderMode按需渲染 ce.init({ domid: 'ceContainer', requestRenderMode: true, maximumRenderTimeChange: Infinity }); // 距离优化:只加载视野内的Entity ce.reloadentity(); // 自动优化加载 // 减少Entity数量 // 对于大量相似Entity,考虑使用3D Tile或PrimitiveCollection ``` ### 2. 事件处理 ```javascript // 使用节流防止频繁触发 function throttle(func, delay) { var lastTime = 0; return function() { var now = Date.now(); if (now - lastTime >= delay) { lastTime = now; func.apply(this, arguments); } }; } // 应用节流 ce.ciyevent = throttle(function(type, act, data) { // 处理事件 }, 100); ``` ### 3. 内存管理 ```javascript // 销毁图表实例 function disposeCharts() { charts.forEach(function(chart) { chart.dispose(); }); charts = []; } // 页面卸载时清理 window.addEventListener('beforeunload', function() { disposeCharts(); ce.destroy(); }); ``` ### 4. 响应式设计 ```javascript // 监听窗口大小变化 window.addEventListener('resize', function() { // 调整图表大小 charts.forEach(function(chart) { chart.resize(); }); // 调整面板位置 updatePanelPositions(); }); // 移动端适配 if (ciyfn.inmobile()) { // 隐藏部分面板 $5('.control-panel').hide(); // 调整面板样式 $5('.float-panel.left').css('width', '80%'); } ``` --- ## 常见问题 ### 1. 如何获取点击的Entity详细信息? ```javascript ce.ciyevent = function(type, act, data) { if (type == 'entity' && act == 'leftclick') { var entity = data.entity; console.log('Entity ID:', entity.id); console.log('Entity Name:', entity.name); console.log('Entity Type:', entity.ciytype); console.log('Custom Data:', entity.ciydata); console.log('Position:', entity.position._value); } }; ``` ### 2. 如何在点击时高亮显示Entity? ```javascript // 使用轮廓高亮 ce.silhouette.selected = [entity]; // 使用颜色高亮 entity.model.color = Cesium.Color.fromCssColorString('#ffff00'); // 重置高亮 ce.silhouette.selected = []; entity.model.color = Cesium.Color.WHITE; ``` ### 3. 如何加载大量Entity而不影响性能? ```javascript // 分批加载 var batchSize = 100; var currentIndex = 0; function loadBatch() { var batch = entities.slice(currentIndex, currentIndex + batchSize); batch.forEach(function(entityData) { ce.addentity(entityData); }); currentIndex += batchSize; if (currentIndex < entities.length) { setTimeout(loadBatch, 100); } } loadBatch(); ``` ### 4. 如何实现图层的显示/隐藏? ```javascript // 给Entity添加分组属性 ce.addentity({ type: 'model', name: '建筑1', category: 'building', // 分类 // ... }); // 控制图层显示 function toggleLayer(category, visible) { for (var id in ce.entitys) { var entity = ce.entitys[id]; if (entity.ciydata.category === category) { entity.show = visible; } } } // 使用 toggleLayer('building', false); // 隐藏建筑层 toggleLayer('building', true); // 显示建筑层 ``` ### 5. 如何实现搜索定位功能? ```javascript // 搜索Entity function searchEntity(keyword) { for (var id in ce.entitys) { var entity = ce.entitys[id]; if (entity.name && entity.name.indexOf(keyword) > -1) { // 找到匹配的Entity flyToEntity(id); return; } } } // 飞行到Entity function flyToEntity(entityId) { var entity = ce.entitys[entityId]; ce.viewer.flyTo(entity, { offset: new Cesium.HeadingPitchRange(1.57, -0.6, 50) }); // 高亮显示 ce.silhouette.selected = [entity]; } ``` --- ## 参考资料 - Cesium官方文档: https://cesium.com/docs/ - Cesium Ion: https://cesium.com/ion/ - ECharts文档: https://echarts.apache.org/zh/index.html - 开源作者: 众产® - 官网: http://ciy.cn/code