You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
695 lines
23 KiB
HTML
695 lines
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>海洋生态实时监测平台 - 连云港热力图</title>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.0/echarts.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts-gl/2.0.0/echarts-gl.min.js"></script>
|
|
|
|
<!-- 确保这些文件路径正确 -->
|
|
<script src="./model/mars3d-cesium/Build/Cesium/Cesium.js"></script>
|
|
<link href="./model/mars3d-cesium/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
|
|
<script src="./model/mars3d/mars3d.js"></script>
|
|
<link href="./model/mars3d/mars3d.css" rel="stylesheet">
|
|
|
|
<!-- Mars3D热力图插件 - 使用正确的CDN路径 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/mars3d-plugin-heatmap/dist/mars3d-plugin-heatmap.min.js"></script>
|
|
|
|
<style>
|
|
/* 保持原有样式不变 */
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Segoe UI', Arial, sans-serif;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(135deg, #0c1a2d 0%, #1a3a5f 100%);
|
|
color: #e0f0ff;
|
|
overflow: hidden;
|
|
height: 100vh;
|
|
}
|
|
|
|
/* 顶部标题栏样式 */
|
|
.topDiv {
|
|
width: 100%;
|
|
height: 8vh;
|
|
background: url('/assets/img/topBg.png') top no-repeat;
|
|
background-size: 100% 115%;
|
|
display: flex;
|
|
justify-content: center;
|
|
z-index: 990;
|
|
position: relative;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.topDiv .center {
|
|
font-family: PangMenZhengDao, 'Source Han Sans CN';
|
|
font-weight: 400;
|
|
font-size: 4.1vh;
|
|
color: #ffffff;
|
|
align-self: center;
|
|
margin-top: -40px;
|
|
}
|
|
|
|
.topDiv .left {
|
|
position: absolute;
|
|
top: 4vh;
|
|
left: 3vw;
|
|
color: #ffffff;
|
|
}
|
|
|
|
.topDiv .left span:nth-child(1) {
|
|
font-weight: bold;
|
|
font-size: 0.8vw;
|
|
margin-right: 0.5vh;
|
|
}
|
|
|
|
.topDiv .left span:nth-child(2) {
|
|
font-weight: bold;
|
|
font-size: 1.2vw;
|
|
}
|
|
|
|
.topDiv .right {
|
|
position: absolute;
|
|
top: 3vh;
|
|
right: 0;
|
|
color: #ffffff;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.container {
|
|
display: flex;
|
|
height: calc(100vh - 8vh - 20px);
|
|
position: relative;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* 地球容器样式 */
|
|
.earth-container {
|
|
flex: 1;
|
|
height: 100%;
|
|
position: relative;
|
|
background: #0c1a2d;
|
|
}
|
|
|
|
#mars3dMap {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
|
|
/* 右侧控制面板 */
|
|
.control-panel {
|
|
width: 300px;
|
|
height: 100%;
|
|
padding: 20px;
|
|
background: rgba(12, 42, 73, 0.7);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(64, 156, 255, 0.3);
|
|
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
|
|
z-index: 10;
|
|
border-radius: 8px;
|
|
position: absolute;
|
|
right: 15px;
|
|
top: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* 卡片头部样式 */
|
|
.card-header {
|
|
height: 40px;
|
|
background: url('/assets/img/common/homeTitle2.png') no-repeat;
|
|
background-position: center;
|
|
background-size: 108% 120%;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border-radius: 8px 8px 0 0 !important;
|
|
border-bottom: 1px solid var(--light-blue);
|
|
padding: 32px 20px;
|
|
position: relative;
|
|
margin: -20px -20px 0px -20px;
|
|
}
|
|
|
|
.card-header span {
|
|
font-size: 1.2rem;
|
|
letter-spacing: 1px;
|
|
font-family: 'YouSheBiaoTiHei', 'Microsoft YaHei';
|
|
font-weight: 400;
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* 控制组样式 */
|
|
.control-group {
|
|
margin-bottom: 20px;
|
|
padding: 10px;
|
|
background: rgba(16, 36, 62, 0.3);
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(64, 156, 255, 0.2);
|
|
}
|
|
|
|
.control-title {
|
|
font-size: 1.1rem;
|
|
margin: 15px 0 10px 0;
|
|
color: #4fc3f7;
|
|
padding-bottom: 5px;
|
|
border-bottom: 1px solid rgba(79, 195, 247, 0.3);
|
|
}
|
|
|
|
/* 按钮样式 */
|
|
.btn-group {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 8px 15px;
|
|
background: rgba(25, 55, 95, 0.6);
|
|
border: 1px solid rgba(79, 195, 247, 0.5);
|
|
border-radius: 4px;
|
|
color: #e0f0ff;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
flex: 1;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: rgba(79, 195, 247, 0.2);
|
|
}
|
|
|
|
.btn.active {
|
|
background: rgba(79, 195, 247, 0.5);
|
|
border-color: #4fc3f7;
|
|
}
|
|
|
|
/* 滑块样式 */
|
|
.slider-container {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.slider-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 5px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.slider-name {
|
|
color: #a0d2ff;
|
|
}
|
|
|
|
.slider-value {
|
|
color: #4fc3f7;
|
|
}
|
|
|
|
.slider {
|
|
width: 100%;
|
|
height: 8px;
|
|
background: rgba(25, 55, 95, 0.6);
|
|
border-radius: 4px;
|
|
outline: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
.slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: #4fc3f7;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* 图例样式 */
|
|
.legend {
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
background: rgba(25, 55, 95, 0.6);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.legend-title {
|
|
font-size: 1rem;
|
|
color: #4fc3f7;
|
|
margin-bottom: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.legend-gradient {
|
|
height: 20px;
|
|
background: linear-gradient(to right, blue, cyan, green, yellow, red);
|
|
border-radius: 4px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.legend-labels {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.8rem;
|
|
color: #a0d2ff;
|
|
}
|
|
|
|
/* 热力图信息面板 */
|
|
.heatmap-info {
|
|
margin-top: 15px;
|
|
padding: 10px;
|
|
background: rgba(25, 55, 95, 0.6);
|
|
border-radius: 8px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.info-item {
|
|
margin: 8px 0;
|
|
display: flex;
|
|
}
|
|
|
|
.info-label {
|
|
width: 70px;
|
|
color: #a0d2ff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.info-value {
|
|
flex: 1;
|
|
color: #e0f0ff;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- 顶部标题栏 -->
|
|
<div class="topDiv">
|
|
<div class="left">
|
|
<span id="current-date">2025年9月17日</span>
|
|
<span id="current-time">15:18:00</span>
|
|
</div>
|
|
<div class="center">海洋生态实时监测平台 - 连云港热力图</div>
|
|
<div class="right">
|
|
<img src="/common/images/logo_gt.png" alt="" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- 地球容器 -->
|
|
<div class="earth-container">
|
|
<div id="mars3dMap"></div>
|
|
</div>
|
|
|
|
<!-- 右侧控制面板 -->
|
|
<div class="control-panel">
|
|
<div class="card-header">
|
|
<span>热力图控制面板</span>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<div class="control-title">热力图类型</div>
|
|
<div class="btn-group">
|
|
<div class="btn active" data-type="temperature">海水温度</div>
|
|
<div class="btn" data-type="oxygen">溶解氧</div>
|
|
<div class="btn" data-type="chlorophyll">叶绿素</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<div class="control-title">热力图参数</div>
|
|
<div class="slider-container">
|
|
<div class="slider-label">
|
|
<span class="slider-name">透明度</span>
|
|
<span class="slider-value" id="opacity-value">0.7</span>
|
|
</div>
|
|
<input type="range" min="0" max="1" step="0.1" value="0.7" class="slider" id="opacity-slider">
|
|
</div>
|
|
|
|
<div class="slider-container">
|
|
<div class="slider-label">
|
|
<span class="slider-name">半径</span>
|
|
<span class="slider-value" id="radius-value">25</span>
|
|
</div>
|
|
<input type="range" min="10" max="50" step="1" value="25" class="slider" id="radius-slider">
|
|
</div>
|
|
|
|
<div class="slider-container">
|
|
<div class="slider-label">
|
|
<span class="slider-name">模糊度</span>
|
|
<span class="slider-value" id="blur-value">0.8</span>
|
|
</div>
|
|
<input type="range" min="0" max="1" step="0.1" value="0.8" class="slider" id="blur-slider">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<div class="control-title">图例</div>
|
|
<div class="legend">
|
|
<div class="legend-title">数值强度</div>
|
|
<div class="legend-gradient"></div>
|
|
<div class="legend-labels">
|
|
<span>低</span>
|
|
<span>中</span>
|
|
<span>高</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<div class="control-title">热力图信息</div>
|
|
<div class="heatmap-info">
|
|
<div class="info-item">
|
|
<span class="info-label">当前类型:</span>
|
|
<span class="info-value" id="current-type">海水温度</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="info-label">数据点数:</span>
|
|
<span class="info-value" id="data-count">256</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="info-label">覆盖区域:</span>
|
|
<span class="info-value">连云港近海</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<span class="info-label">更新时间:</span>
|
|
<span class="info-value" id="update-time">15:18:00</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 全局变量
|
|
let map;
|
|
let heatmapLayer;
|
|
let currentHeatmapType = 'temperature';
|
|
|
|
// 连云港近海坐标范围
|
|
const lianyungangBounds = {
|
|
minLng: 118.5,
|
|
maxLng: 120.0,
|
|
minLat: 34.0,
|
|
maxLat: 35.2
|
|
};
|
|
|
|
// 热力图数据
|
|
const heatmapData = {
|
|
temperature: generateHeatmapData(18, 28), // 温度范围18-28°C
|
|
oxygen: generateHeatmapData(5, 9), // 溶解氧范围5-9mg/L
|
|
chlorophyll: generateHeatmapData(2, 8) // 叶绿素范围2-8μg/L
|
|
};
|
|
|
|
// 初始化页面
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initDateTime();
|
|
initMap();
|
|
initHeatmap();
|
|
initEventListeners();
|
|
});
|
|
|
|
// 初始化日期时间
|
|
function initDateTime() {
|
|
function updateDateTime() {
|
|
const now = new Date();
|
|
const dateStr = now.getFullYear() + '年' +
|
|
(now.getMonth() + 1) + '月' +
|
|
now.getDate() + '日';
|
|
const timeStr = now.toTimeString().substr(0, 8);
|
|
|
|
document.getElementById('current-date').textContent = dateStr;
|
|
document.getElementById('current-time').textContent = timeStr;
|
|
document.getElementById('update-time').textContent = timeStr;
|
|
}
|
|
|
|
updateDateTime();
|
|
setInterval(updateDateTime, 1000);
|
|
}
|
|
|
|
// 初始化地图
|
|
function initMap() {
|
|
// 创建地图
|
|
const mapOptions = {
|
|
basemaps: [
|
|
{
|
|
name: '电子地图',
|
|
type: 'tdt',
|
|
layer: 'vec',
|
|
show: true
|
|
},
|
|
{
|
|
name: '影像地图',
|
|
type: 'tdt',
|
|
layer: 'img',
|
|
show: false
|
|
}
|
|
],
|
|
scene: {
|
|
center: { lat: 34.6, lng: 119.2, alt: 80000, heading: 0, pitch: -45 },
|
|
showSun: true,
|
|
showMoon: true,
|
|
showSkyBox: true,
|
|
showSkyAtmosphere: true,
|
|
fog: true,
|
|
fxaa: true,
|
|
globe: {
|
|
showGroundAtmosphere: true,
|
|
depthTestAgainstTerrain: false
|
|
},
|
|
cameraController: {
|
|
enableZoom: true,
|
|
enableRotate: true,
|
|
enableTilt: true,
|
|
enableTranslate: true
|
|
}
|
|
},
|
|
control: {
|
|
baseLayerPicker: true,
|
|
homeButton: true,
|
|
sceneModePicker: true,
|
|
navigationHelpButton: true,
|
|
fullscreenButton: true
|
|
}
|
|
};
|
|
|
|
// 初始化地图
|
|
map = new mars3d.Map('mars3dMap', mapOptions);
|
|
}
|
|
|
|
// 初始化热力图
|
|
function initHeatmap() {
|
|
// 检查热力图插件是否可用
|
|
if (typeof mars3d.layer.HeatLayer === 'undefined') {
|
|
console.error('热力图插件未正确加载,使用备用方案');
|
|
createCustomHeatmap();
|
|
return;
|
|
}
|
|
|
|
// 创建热力图图层
|
|
heatmapLayer = new mars3d.layer.HeatLayer({
|
|
name: '生态热力图',
|
|
// 热力图样式配置
|
|
style: {
|
|
radius: 25,
|
|
opacity: 0.7,
|
|
blur: 0.8,
|
|
gradient: {
|
|
0.0: 'blue',
|
|
0.2: 'cyan',
|
|
0.4: 'green',
|
|
0.6: 'yellow',
|
|
0.8: 'orange',
|
|
1.0: 'red'
|
|
}
|
|
}
|
|
});
|
|
|
|
// 添加热力图图层到地图
|
|
map.addLayer(heatmapLayer);
|
|
|
|
// 加载初始数据
|
|
updateHeatmapData(currentHeatmapType);
|
|
}
|
|
|
|
// 备用方案:创建自定义热力图效果
|
|
function createCustomHeatmap() {
|
|
console.log('使用自定义热力图实现');
|
|
|
|
// 使用点图层模拟热力图效果
|
|
heatmapLayer = new mars3d.layer.GraphicLayer({
|
|
name: '生态热力图'
|
|
});
|
|
|
|
map.addLayer(heatmapLayer);
|
|
|
|
// 加载初始数据
|
|
updateHeatmapData(currentHeatmapType);
|
|
}
|
|
|
|
// 生成热力图数据
|
|
function generateHeatmapData(minValue, maxValue) {
|
|
const data = [];
|
|
const pointCount = 256; // 数据点数量
|
|
|
|
for (let i = 0; i < pointCount; i++) {
|
|
// 在连云港范围内随机生成坐标
|
|
const lng = lianyungangBounds.minLng +
|
|
Math.random() * (lianyungangBounds.maxLng - lianyungangBounds.minLng);
|
|
const lat = lianyungangBounds.minLat +
|
|
Math.random() * (lianyungangBounds.maxLat - lianyungangBounds.minLat);
|
|
|
|
// 生成随机值,但模拟一些热点区域
|
|
let value;
|
|
const hotspotChance = Math.random();
|
|
|
|
if (hotspotChance < 0.1) {
|
|
// 10%的概率生成热点(高值)
|
|
value = minValue + 0.7 * (maxValue - minValue) +
|
|
Math.random() * 0.3 * (maxValue - minValue);
|
|
} else if (hotspotChance < 0.2) {
|
|
// 10%的概率生成冷点(低值)
|
|
value = minValue + Math.random() * 0.3 * (maxValue - minValue);
|
|
} else {
|
|
// 80%的概率生成正常值
|
|
value = minValue + Math.random() * (maxValue - minValue);
|
|
}
|
|
|
|
data.push({
|
|
lng: lng,
|
|
lat: lat,
|
|
value: value
|
|
});
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// 更新热力图数据
|
|
function updateHeatmapData(type) {
|
|
if (!heatmapLayer || !heatmapData[type]) return;
|
|
|
|
// 清除现有数据
|
|
heatmapLayer.clear();
|
|
|
|
// 如果是自定义热力图实现
|
|
if (heatmapLayer.constructor.name === 'GraphicLayer') {
|
|
// 使用点图层模拟热力图
|
|
const data = heatmapData[type];
|
|
const minValue = Math.min(...data.map(d => d.value));
|
|
const maxValue = Math.max(...data.map(d => d.value));
|
|
|
|
data.forEach(point => {
|
|
// 根据数值大小计算颜色和大小
|
|
const normalizedValue = (point.value - minValue) / (maxValue - minValue);
|
|
const color = getColorByValue(normalizedValue);
|
|
const size = 8 + normalizedValue * 12;
|
|
|
|
const graphic = new mars3d.graphic.PointEntity({
|
|
position: [point.lng, point.lat],
|
|
style: {
|
|
pixelSize: size,
|
|
color: color,
|
|
outlineColor: '#ffffff',
|
|
outlineWidth: 1,
|
|
opacity: 0.7
|
|
},
|
|
attr: point
|
|
});
|
|
|
|
heatmapLayer.addGraphic(graphic);
|
|
});
|
|
} else {
|
|
// 使用真正的热力图图层
|
|
heatmapLayer.setData(heatmapData[type]);
|
|
}
|
|
|
|
// 更新信息面板
|
|
document.getElementById('current-type').textContent = getHeatmapTypeName(type);
|
|
document.getElementById('data-count').textContent = heatmapData[type].length;
|
|
|
|
// 更新按钮状态
|
|
document.querySelectorAll('.btn-group .btn').forEach(btn => {
|
|
if (btn.dataset.type === type) {
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
console.log(`已切换到${getHeatmapTypeName(type)}热力图`);
|
|
}
|
|
|
|
// 根据数值获取颜色
|
|
function getColorByValue(value) {
|
|
if (value < 0.2) return '#0000ff'; // 蓝色
|
|
if (value < 0.4) return '#00ffff'; // 青色
|
|
if (value < 0.6) return '#00ff00'; // 绿色
|
|
if (value < 0.8) return '#ffff00'; // 黄色
|
|
return '#ff0000'; // 红色
|
|
}
|
|
|
|
// 获取热力图类型名称
|
|
function getHeatmapTypeName(type) {
|
|
const names = {
|
|
temperature: '海水温度',
|
|
oxygen: '溶解氧',
|
|
chlorophyll: '叶绿素'
|
|
};
|
|
return names[type] || '未知类型';
|
|
}
|
|
|
|
// 初始化事件监听器
|
|
function initEventListeners() {
|
|
// 热力图类型切换
|
|
document.querySelectorAll('.btn-group .btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
const type = this.dataset.type;
|
|
currentHeatmapType = type;
|
|
updateHeatmapData(type);
|
|
});
|
|
});
|
|
|
|
// 透明度滑块
|
|
const opacitySlider = document.getElementById('opacity-slider');
|
|
const opacityValue = document.getElementById('opacity-value');
|
|
|
|
opacitySlider.addEventListener('input', function() {
|
|
const value = this.value;
|
|
opacityValue.textContent = value;
|
|
if (heatmapLayer && heatmapLayer.style) {
|
|
heatmapLayer.style.opacity = parseFloat(value);
|
|
}
|
|
});
|
|
|
|
// 半径滑块
|
|
const radiusSlider = document.getElementById('radius-slider');
|
|
const radiusValue = document.getElementById('radius-value');
|
|
|
|
radiusSlider.addEventListener('input', function() {
|
|
const value = this.value;
|
|
radiusValue.textContent = value;
|
|
if (heatmapLayer && heatmapLayer.style) {
|
|
heatmapLayer.style.radius = parseInt(value);
|
|
}
|
|
});
|
|
|
|
// 模糊度滑块
|
|
const blurSlider = document.getElementById('blur-slider');
|
|
const blurValue = document.getElementById('blur-value');
|
|
|
|
blurSlider.addEventListener('input', function() {
|
|
const value = this.value;
|
|
blurValue.textContent = value;
|
|
if (heatmapLayer && heatmapLayer.style) {
|
|
heatmapLayer.style.blur = parseFloat(value);
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html> |