1138 lines
30 KiB
Markdown
1138 lines
30 KiB
Markdown
# 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
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>3D地图应用</title>
|
||
<link href="/jscss/style.css" rel="stylesheet" type="text/css" />
|
||
<link href="/zces108/widgets.css" rel="stylesheet" type="text/css" />
|
||
<style type="text/css">
|
||
#ceContainer {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
}
|
||
|
||
/* 浮动面板样式 */
|
||
.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;
|
||
}
|
||
|
||
.float-panel.top {
|
||
top: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
.float-panel.left {
|
||
top: 80px;
|
||
left: 20px;
|
||
width: 300px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.float-panel.right {
|
||
top: 80px;
|
||
right: 20px;
|
||
width: 350px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.float-panel.bottom {
|
||
bottom: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
}
|
||
|
||
/* 控制面板 */
|
||
.control-panel {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
border-radius: 0 0 0 8px;
|
||
padding: 10px;
|
||
}
|
||
|
||
/* 飞行点列表 */
|
||
.fly-points {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
padding-bottom: 30px;
|
||
}
|
||
|
||
.fly-points > div {
|
||
padding: 8px 20px;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid #ddd;
|
||
border-radius: 20px;
|
||
color: #333;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.fly-points > div:hover {
|
||
background: #fff;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.fly-points > div.select {
|
||
background: #007aff;
|
||
color: #fff;
|
||
border-color: #007aff;
|
||
}
|
||
|
||
/* 图表容器 */
|
||
.chart-container {
|
||
width: 100%;
|
||
height: 200px;
|
||
margin-top: 10px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="ceContainer"></div>
|
||
|
||
<!-- 控制面板 -->
|
||
<div class="control-panel">
|
||
<div class="flex">
|
||
<div class="tip flex1" id="cameraTip"></div>
|
||
<div class="btn xs def" onclick="ce.control_fly_open(false)">飞行</div>
|
||
<div class="btn xs def" onclick="ce.control_fly_open(true)">步行</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 飞行点列表 -->
|
||
<div class="fly-points" id="flyPoints"></div>
|
||
|
||
<!-- 左侧资产列表 -->
|
||
<div class="float-panel left" id="assetPanel" style="display:none;">
|
||
<h3>资产信息</h3>
|
||
<div id="assetInfo"></div>
|
||
<div id="assetCharts"></div>
|
||
</div>
|
||
|
||
<!-- 右侧统计面板 -->
|
||
<div class="float-panel right" id="statPanel">
|
||
<h3>统计数据</h3>
|
||
<div id="statContent"></div>
|
||
</div>
|
||
|
||
<!-- 加载脚本 -->
|
||
<script type="text/javascript" src="/jscss/ciy.js"></script>
|
||
<script type="text/javascript" src="/zces108/Cesium.js"></script>
|
||
<script type="text/javascript" src="/zces108/ciyearth.min.js"></script>
|
||
<script type="text/javascript" src="/zces108/map.min.js"></script>
|
||
<script type="text/javascript" src="/jscss/echarts.min.js"></script>
|
||
|
||
<script type="text/javascript">
|
||
'use strict';
|
||
var ce = new ciyearth();
|
||
var currentAssetId = null;
|
||
var statChart = null;
|
||
|
||
// 初始化地图
|
||
function initMap() {
|
||
ciyfn.callfunc('init', {}, function (json) {
|
||
var mapset = ciyfn.tojson(json.cemap.mapjson);
|
||
mapset.domid = 'ceContainer';
|
||
ce.init(mapset);
|
||
|
||
// 渲染飞行点
|
||
renderFlyPoints(mapset.flys);
|
||
|
||
// 初始化统计图表
|
||
initStatChart();
|
||
});
|
||
}
|
||
|
||
// 渲染飞行点
|
||
function renderFlyPoints(flys) {
|
||
if (!flys || flys.length === 0) return;
|
||
|
||
var container = $5('#flyPoints');
|
||
for (var i = 0; i < flys.length; i++) {
|
||
var html = $5('<div onclick="flyTo(this)">' + flys[i].name + '</div>');
|
||
html.attr('data-dat', flys[i].dat);
|
||
container.append(html);
|
||
}
|
||
}
|
||
|
||
// 飞行到指定点
|
||
function flyTo(dom) {
|
||
event.stopPropagation();
|
||
$5('.fly-points>div').removeClass('select');
|
||
$5(dom).addClass('select');
|
||
var dat = $5(dom).attr('data-dat');
|
||
ce.flyTo(ciyfn.tojson(dat), 3);
|
||
}
|
||
|
||
// 显示资产信息
|
||
function showAssetInfo(assetId) {
|
||
currentAssetId = assetId;
|
||
|
||
// 显示左侧面板
|
||
$5('#assetPanel').show();
|
||
|
||
// 获取资产详情
|
||
ciyfn.callfunc('getAssetDetail', {id: assetId}, function (json) {
|
||
var html = '<div class="asset-detail">';
|
||
html += '<p><strong>名称:</strong> ' + json.data.name + '</p>';
|
||
html += '<p><strong>类型:</strong> ' + json.data.type + '</p>';
|
||
html += '<p><strong>位置:</strong> ' + json.data.location + '</p>';
|
||
html += '<p><strong>状态:</strong> ' + json.data.status + '</p>';
|
||
html += '</div>';
|
||
|
||
$5('#assetInfo').html(html);
|
||
|
||
// 渲染资产相关图表
|
||
renderAssetCharts(json.data);
|
||
});
|
||
}
|
||
|
||
// 渲染资产图表
|
||
function renderAssetCharts(assetData) {
|
||
var chartsContainer = $5('#assetCharts');
|
||
chartsContainer.html('');
|
||
|
||
// 示例:渲染趋势图
|
||
var chart1 = $5('<div class="chart-container" id="chart1"></div>');
|
||
chartsContainer.append(chart1);
|
||
|
||
var option1 = {
|
||
title: { text: '近7天访问量', left: 'center', textStyle: {fontSize: 12} },
|
||
xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
||
yAxis: { type: 'value' },
|
||
series: [{
|
||
data: assetData.visits || [120, 200, 150, 80, 70, 110, 130],
|
||
type: 'line',
|
||
smooth: true,
|
||
areaStyle: { opacity: 0.3 }
|
||
}]
|
||
};
|
||
|
||
var chart1Instance = echarts.init(document.getElementById('chart1'));
|
||
chart1Instance.setOption(option1);
|
||
|
||
// 示例:渲染分布图
|
||
var chart2 = $5('<div class="chart-container" id="chart2"></div>');
|
||
chartsContainer.append(chart2);
|
||
|
||
var option2 = {
|
||
title: { text: '时段分布', left: 'center', textStyle: {fontSize: 12} },
|
||
xAxis: { type: 'category', data: ['00-06', '06-12', '12-18', '18-24'] },
|
||
yAxis: { type: 'value' },
|
||
series: [{
|
||
data: assetData.timeDist || [20, 150, 280, 90],
|
||
type: 'bar',
|
||
itemStyle: { color: '#5470c6' }
|
||
}]
|
||
};
|
||
|
||
var chart2Instance = echarts.init(document.getElementById('chart2'));
|
||
chart2Instance.setOption(option2);
|
||
|
||
// 监听窗口大小变化
|
||
window.addEventListener('resize', function() {
|
||
chart1Instance.resize();
|
||
chart2Instance.resize();
|
||
});
|
||
}
|
||
|
||
// 初始化统计图表
|
||
function initStatChart() {
|
||
var container = $5('#statContent');
|
||
container.html('<div class="chart-container" id="statChart"></div>');
|
||
|
||
statChart = echarts.init(document.getElementById('statChart'));
|
||
|
||
var option = {
|
||
title: { text: '区域统计', left: 'center', textStyle: {fontSize: 14} },
|
||
tooltip: { trigger: 'axis' },
|
||
legend: { bottom: 0 },
|
||
xAxis: { type: 'category', data: ['区域A', '区域B', '区域C', '区域D'] },
|
||
yAxis: { type: 'value' },
|
||
series: [
|
||
{ name: '建筑数', type: 'bar', data: [120, 200, 150, 80] },
|
||
{ name: '设备数', type: 'bar', data: [220, 182, 191, 234] }
|
||
]
|
||
};
|
||
|
||
statChart.setOption(option);
|
||
|
||
window.addEventListener('resize', function() {
|
||
statChart.resize();
|
||
});
|
||
}
|
||
|
||
// 更新相机信息
|
||
function updateCameraInfo(cartographic) {
|
||
var lng = Cesium.Math.toDegrees(cartographic.longitude);
|
||
var lat = Cesium.Math.toDegrees(cartographic.latitude);
|
||
var html = '经度:' + lng.toFixed(4) + ' 纬度:' + lat.toFixed(4) + ' 海拔:' + cartographic.height.toFixed(1) + '米';
|
||
$5('#cameraTip').html(html);
|
||
}
|
||
|
||
// 设置事件回调
|
||
ce.ciyevent = function (type, act, data, data2) {
|
||
if (type == 'entity') {
|
||
if (act == 'leftclick') {
|
||
// 点击Entity
|
||
var entity = data.entity;
|
||
var bindId = entity.ciydata.bind;
|
||
if (bindId) {
|
||
showAssetInfo(bindId);
|
||
}
|
||
}
|
||
} else if (type == 'control') {
|
||
if (act == 'op') {
|
||
// 控制操作中
|
||
updateCameraInfo(data);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 页面加载完成后初始化
|
||
ciyfn.pageload(function () {
|
||
try {
|
||
initMap();
|
||
} catch (e) {
|
||
console.log('init:', e);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
---
|
||
|
||
## 事件交互
|
||
|
||
### 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 = '<h3>资产信息</h3><div id="assetContent"></div>';
|
||
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 = '<h3>统计数据</h3><div id="statContent"></div>';
|
||
document.body.appendChild(rightPanel);
|
||
|
||
// 顶部面板 - 搜索/筛选
|
||
var topPanel = document.createElement('div');
|
||
topPanel.className = 'float-panel top';
|
||
topPanel.innerHTML = '<input type="text" placeholder="搜索资产..." />';
|
||
document.body.appendChild(topPanel);
|
||
|
||
// 底部面板 - 快捷操作
|
||
var bottomPanel = document.createElement('div');
|
||
bottomPanel.className = 'float-panel bottom';
|
||
bottomPanel.innerHTML = '<button onclick="resetView()">重置视角</button>';
|
||
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 = '<div class="asset-info">';
|
||
html += '<h4>' + asset.name + '</h4>';
|
||
html += '<div class="info-grid">';
|
||
html += '<div><span>类型:</span>' + asset.type + '</div>';
|
||
html += '<div><span>楼层:</span>' + asset.floors + '层</div>';
|
||
html += '<div><span>面积:</span>' + asset.area + '㎡</div>';
|
||
html += '<div><span>入住率:</span>' + asset.occupancy + '%</div>';
|
||
html += '<div><span>状态:</span><span class="status ' + asset.status + '">' + asset.status + '</span></div>';
|
||
html += '</div>';
|
||
html += '<div class="energy-info">';
|
||
html += '<div><span>今日能耗:</span>' + asset.energy.today + ' kWh</div>';
|
||
html += '<div><span>本月能耗:</span>' + asset.energy.month + ' kWh</div>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
|
||
$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 |