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.

1913 lines
59 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>
<script src="./model/mars3d-heatmap/mars3d-heatmap.js"></script>
<link href="./model/mars3d/mars3d.css" rel="stylesheet">
<!-- 在 head 部分添加 GeoTIFF.js 库 -->
<script src="https://cdn.jsdelivr.net/npm/geotiff@2.0.7/dist/geotiff.min.js"></script>
<!-- Mars3D热力图插件 - 使用正确的CDN路径 -->
<!-- <script src="https://cdn.jsdelivr.net/npm/mars3d-plugin-heatmap/dist/mars3d-plugin-heatmap.min.js"></script> -->
<style>
/* 控制面板内容区域添加滚动条 */
.control-panel-content {
flex: 1;
overflow-y: auto;
padding-right: 10px;
margin-right: -10px;
}
/* 美化控制面板内容区域的滚动条 */
.control-panel-content::-webkit-scrollbar {
width: 4px;
}
.control-panel-content::-webkit-scrollbar-track {
background: rgba(12, 42, 73, 0.3);
border-radius: 4px;
}
.control-panel-content::-webkit-scrollbar-thumb {
background: rgba(79, 195, 247, 0.5);
border-radius: 4px;
}
.control-panel-content::-webkit-scrollbar-thumb:hover {
background: rgba(79, 195, 247, 0.8);
}
/* 替换原有的过滤器样式为以下代码 */
/* 修改过滤器相关样式 */
.filter-options {
display: flex;
flex-direction: column;
gap: 12px;
}
.filter-option {
display: flex;
align-items: center;
position: relative;
padding-left: 30px;
margin-bottom: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.filter-option:hover {
transform: translateX(5px);
background: rgba(40, 80, 130, 0.3);
border-radius: 4px;
}
.filter-option input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.filter-option .checkmark {
position: absolute;
left: 0;
height: 20px;
width: 20px;
background-color: rgba(25, 55, 95, 0.6);
border: 2px solid #4fc3f7;
border-radius: 4px;
transition: all 0.3s ease;
pointer-events: all;
/* 确保可以点击 */
}
.filter-option:hover .checkmark {
background-color: rgba(79, 195, 247, 0.2);
}
.filter-option input:checked~.checkmark {
background-color: #4fc3f7;
}
.filter-option .checkmark:after {
content: "";
position: absolute;
display: none;
}
.filter-option input:checked~.checkmark:after {
display: block;
}
.filter-option .checkmark:after {
left: 6px;
top: 2px;
width: 6px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.filter-option label {
font-size: 0.95rem;
color: #e0f0ff;
padding: 5px 0;
cursor: pointer;
width: 100%;
margin-left: 10px;
user-select: none;
/* 防止文本选择 */
}
.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);
}
.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);
}
/* 优化滚动条以确保内容可见 */
/* .info-panel {
overflow-y: auto;
} */
/* 美化滚动条 */
.info-panel::-webkit-scrollbar {
width: 4px;
}
.info-panel::-webkit-scrollbar-track {
background: rgba(12, 42, 73, 0.3);
border-radius: 4px;
}
.info-panel::-webkit-scrollbar-thumb {
background: rgba(79, 195, 247, 0.5);
border-radius: 4px;
}
.info-panel::-webkit-scrollbar-thumb:hover {
background: rgba(79, 195, 247, 0.8);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Arial, sans-serif;
}
body {
background: url('/assets/img/bg.png') no-repeat;
background-size: 100% 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;
/* background-color: rgba(12, 42, 73, 0.8); */
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;
}
.topDiv .right img:nth-child(1) {}
.topDiv .right span {
font-weight: bold;
font-size: 1.2vw;
}
.topDiv .icon-home {
position: absolute;
top: 1vh;
right: 8vw;
width: 4vh;
cursor: pointer;
}
.container {
display: flex;
height: calc(100vh - 8vh - 20px);
position: relative;
margin-bottom: 20px;
}
/* 左侧信息面板 */
.info-panel {
width: 300px;
height: 100%;
padding: 20px;
background: rgba(12, 42, 73, 0.1);
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;
left: 15px;
top: 0;
display: flex;
flex-direction: column;
}
/* 右侧控制面板 */
.control-panel {
width: 280px;
height: 100%;
padding: 20px;
background: rgba(12, 42, 73, 0.9);
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: linear-gradient(to right, #1a3a5f, #2a5a8f);
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px 8px 0 0 !important;
border-bottom: 1px solid #4fc3f7;
padding: 12px 20px;
position: relative;
margin: -20px -20px 20px -20px;
}
.card-header span {
font-size: 1.2rem;
letter-spacing: 1px;
font-family: 'Microsoft YaHei';
font-weight: 400;
color: #ffffff;
} */
.stats-container {
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 25px;
}
.stat-card {
background: rgba(25, 55, 95, 0.6);
/* background: url('/assets/img/common/kva2.png') no-repeat; */
/* background-size: 100% 100%; */
border-radius: 10px;
padding: 15px;
border-left: 4px solid #4fc3f7;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.stat-label {
font-size: 0.9rem;
color: #a0d2ff;
margin-bottom: 5px;
}
.stat-value {
font-size: 1.8rem;
font-weight: bold;
color: #4fc3f7;
}
.data-list {
flex: 1;
overflow-y: auto;
margin-top: 10px;
padding-right: 5px;
}
.data-item {
min-width: 200px;
background: rgba(25, 55, 95, 0.6);
border-radius: 8px;
padding: 10px;
border-left: 3px solid #4fc3f7;
transition: all 0.3s ease;
margin-bottom: 10px;
cursor: pointer;
}
.data-item:hover {
transform: translateX(5px);
background: rgba(40, 80, 130, 0.7);
}
.stream-device-info {
display: flex;
align-items: center;
margin-bottom: 8px;
}
.stream-device-icon {
font-size: 1.5rem;
margin-right: 10px;
}
.stream-device-details {
flex: 1;
}
.stream-device-name {
font-weight: bold;
color: #4fc3f7;
font-size: 0.9rem;
}
.stream-parameter {
font-size: 0.8rem;
color: #a0d2ff;
}
.stream-value-section {
text-align: right;
}
.stream-value {
font-weight: bold;
font-size: 1.1rem;
font-family: 'Courier New', monospace;
}
.stream-timestamp {
font-size: 0.7rem;
color: #81d4fa;
}
.control-group {
margin-bottom: 25px;
}
.control-title {
font-size: 1.2rem;
margin-bottom: 15px;
color: #4fc3f7;
}
.toggle-switch {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.switch-label {
margin-right: 10px;
font-size: 1rem;
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 30px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #2c3e50;
transition: .4s;
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 22px;
width: 22px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: #4fc3f7;
}
input:checked+.slider:before {
transform: translateX(30px);
}
.filter-options {
display: flex;
flex-direction: column;
gap: 10px;
}
.filter-option {
display: flex;
align-items: center;
}
.filter-option input {
margin-right: 10px;
}
/* 地球容器样式 */
.earth-container {
flex: 1;
height: 100%;
position: relative;
/* margin: 0 300px 0 320px; */
background: #0c1a2d;
}
#earthCanvas {
width: 100%;
height: 100%;
display: block;
}
/* 信息卡片样式 */
.info-card {
position: absolute;
background: rgba(16, 36, 62, 0.95);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.4);
border: 1px solid rgba(64, 156, 255, 0.3);
z-index: 1000;
width: 250px;
transform: translate(-50%, -100%);
margin-top: -20px;
}
.info-card::before {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border-width: 10px;
border-style: solid;
border-color: rgba(16, 36, 62, 0.95) transparent transparent transparent;
}
/* 卡片头部样式 */
.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;
}
.info-card-title {
font-size: 1.1rem;
color: #4fc3f7;
margin: 0;
}
.info-card-close {
background: none;
border: none;
color: #a0d2ff;
font-size: 1.2rem;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.info-card-close:hover {
color: #ffffff;
}
.info-card-content {
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;
word-break: break-all;
}
.status-online {
color: #4caf50;
font-weight: bold;
}
.status-offline {
color: #f44336;
font-weight: bold;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.info-panel,
.control-panel {
width: 260px;
}
.earth-container {
margin: 0 275px;
}
}
@media (max-width: 900px) {
.info-panel,
.control-panel {
width: 220px;
padding: 15px;
}
.earth-container {
margin: 0 235px;
}
.stat-value {
font-size: 1.5rem;
}
}
/* 滚动条样式 */
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
background: rgba(12, 42, 73, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(106, 149, 201, 0.5);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(106, 149, 201, 0.7);
}
/* 警报列表样式 */
.alert-list {
/* height: 240px;
overflow-y: auto; */
height: calc(40vh - 8vh - 20px);
overflow: scroll;
}
.alert-item {
display: flex;
align-items: center;
padding: 12px 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
transition: background 0.3s;
}
.alert-item:hover {
background: rgba(255, 255, 255, 0.05);
}
.alert-level {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 10px;
}
.level-high {
background: #ff4560;
box-shadow: 0 0 8px #ff4560;
}
.level-medium {
background: #ffa726;
box-shadow: 0 0 8px #ffa726;
}
.level-low {
background: #00e396;
box-shadow: 0 0 8px #00e396;
}
.alert-content {
flex: 1;
}
.alert-title {
font-weight: bold;
margin-bottom: 5px;
}
.alert-desc {
font-size: 13px;
color: #a0c8ff;
}
.alert-time {
font-size: 12px;
color: #7a9ccc;
}
/* 图表容器样式 */
.chart-container {
width: 100%;
/* height: 200px; */
margin-top: 10px;
height: calc(40vh - 8vh - 20px);
}
/* 新增样式:实时数据监控网格布局 */
.stats-grid {
height: calc(40vh - 8vh - 20px);
overflow: scroll;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 25px;
}
.stat-grid-item {
background: rgba(25, 55, 95, 0.6);
border-radius: 8px;
padding: 12px;
/* border-left: 3px solid #4fc3f7; */
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.stat-grid-value {
font-size: 1rem;
font-weight: bold;
margin-bottom: 5px;
}
.stat-grid-label {
font-size: 0.8rem;
color: #a0d2ff;
margin-bottom: 3px;
}
.stat-grid-status {
font-size: 0.7rem;
color: #81d4fa;
}
/* 视频会议系统样式 */
.video-conference {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 25px;
}
.video-item {
background: rgba(25, 55, 95, 0.6);
border-radius: 8px;
padding: 12px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.video-item:hover {
transform: translateY(-3px);
background: rgba(40, 80, 130, 0.7);
}
.video-icon {
font-size: 2rem;
margin-bottom: 8px;
}
.video-name {
font-size: 0.9rem;
color: #e0f0ff;
}
/* 网络系统状态样式 */
.network-status {
margin-top: 20px;
}
.network-item {
margin-bottom: 12px;
}
.network-label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 0.9rem;
}
.network-name {
color: #a0d2ff;
}
.network-value {
color: #4fc3f7;
}
.network-progress {
height: 8px;
background: rgba(25, 55, 95, 0.6);
border-radius: 4px;
overflow: hidden;
}
.network-progress-bar {
height: 100%;
border-radius: 4px;
transition: width 0.5s ease;
}
/* 项目监控样式 */
.project-monitor {
margin-top: 10px;
}
.project-item {
margin-bottom: 12px;
}
.project-label {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-size: 0.9rem;
}
.project-name {
color: #a0d2ff;
}
.project-value {
color: #4fc3f7;
}
.project-progress {
height: 8px;
background: rgba(25, 55, 95, 0.6);
border-radius: 4px;
overflow: hidden;
}
.project-progress-bar {
height: 100%;
border-radius: 4px;
transition: width 0.5s ease;
}
.mars3d-popup-background {
background: var(--mars-base-bg, rgba(16, 36, 62, 0.85)) !important;
}
/* 区域选择器样式 */
.region-selector {
margin-bottom: 15px;
}
.region-selector select {
width: 100%;
padding: 8px 12px;
background: rgba(25, 55, 95, 0.6);
border: 1px solid rgba(79, 195, 247, 0.5);
border-radius: 4px;
color: #e0f0ff;
font-size: 0.9rem;
}
.region-selector select:focus {
outline: none;
border-color: #4fc3f7;
}
.region-selector label {
display: block;
margin-bottom: 5px;
font-size: 0.9rem;
color: #a0d2ff;
}
</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" style="width:100%; height:100%;"></div>
</div>
<!-- 右侧控制面板 -->
<div class="control-panel">
<div class="card-header">
<span>生态水质分析</span>
</div>
<div class="region-selector">
<label for="region-select">选择区域:</label>
<select id="region-select">
<option value="all">全海域</option>
<option value="lianyungang">连云区</option>
<option value="lanshan">岚山区</option>
<option value="ganyu">赣榆区</option>
<option value="guannan">灌南县</option>
<option value="guanyun">灌云县</option>
</select>
</div>
<div class="chart-container" id="waterQualityChart"></div>
<div class="card-header">
<span>含氧量趋势分析</span>
</div>
<div class="chart-container" id="oxygenChart"></div>
<div class="card-header">
<span>叶绿素趋势分析</span>
</div>
<div class="chart-container" id="chlorophyllChart"></div>
</div>
</div>
<script>
// 全局变量
let map;
let isSimulationRunning = true;
let waterQualityChart;
let oxygenChart;
let chlorophyllChart;
let heatLayer = null
// 连云港生态区域数据
const lianyungangEcoRegions = [
{
name: "非湿地",
type: "non-wetland",
color: "#cccccc",
coordinates: [
[[119.2, 34.8], [119.4, 34.8], [119.4, 35.0], [119.2, 35.0], [119.2, 34.8]]
]
},
{
name: "永久水域",
type: "permanent-water",
color: "#2ca25f",
coordinates: [
[[119.3, 34.6], [119.5, 34.6], [119.5, 34.8], [119.3, 34.8], [119.3, 34.6]]
]
},
{
name: "沼泽(乔木/灌木)",
type: "swamp-woody",
color: "#756bb1",
coordinates: [
[[119.1, 34.7], [119.3, 34.7], [119.3, 34.9], [119.1, 34.9], [119.1, 34.7]]
]
},
{
name: "沼泽(草本植被)",
type: "swamp-herbaceous",
color: "#dd3497",
coordinates: [
[[119.0, 34.6], [119.2, 34.6], [119.2, 34.8], [119.0, 34.8], [119.0, 34.6]]
]
},
{
name: "淹滩",
type: "flooded-beach",
color: "#fdbb84",
coordinates: [
[[119.4, 34.5], [119.6, 34.5], [119.6, 34.7], [119.4, 34.7], [119.4, 34.5]]
]
},
{
name: "盐碱地",
type: "saline-land",
color: "#b30000",
coordinates: [
[[119.5, 34.7], [119.7, 34.7], [119.7, 34.9], [119.5, 34.9], [119.5, 34.7]]
]
},
{
name: "红树林",
type: "mangrove",
color: "#41b6c4",
coordinates: [
[[119.6, 34.6], [119.8, 34.6], [119.8, 34.8], [119.6, 34.8], [119.6, 34.6]]
]
},
{
name: "盐沼",
type: "salt-marsh",
color: "#1d91c0",
coordinates: [
[[119.7, 34.5], [119.9, 34.5], [119.9, 34.7], [119.7, 34.7], [119.7, 34.5]]
]
},
{
name: "潮滩",
type: "tidal-flat",
color: "#ffff00",
coordinates: [
[[119.8, 34.4], [120.0, 34.4], [120.0, 34.6], [119.8, 34.6], [119.8, 34.4]]
]
},
{
name: "海洋",
type: "ocean",
color: "#081d58",
coordinates: [
[[119.9, 34.3], [120.1, 34.3], [120.1, 34.5], [119.9, 34.5], [119.9, 34.3]]
]
}
];
// 初始化页面
document.addEventListener('DOMContentLoaded', function () {
initDateTime();
initWaterQualityChart();
initOxygenChart();
initChlorophyllChart();
initMap();
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;
}
updateDateTime();
setInterval(updateDateTime, 1000);
}
// 初始化生态水质分析图表
function initWaterQualityChart() {
waterQualityChart = echarts.init(document.getElementById('waterQualityChart'));
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis'
},
legend: {
data: ['PH值', '浊度', '盐度'],
textStyle: {
color: '#e0f0ff'
},
top: '0%'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2019', '2020', '2021', '2022', '2023'],
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
},
splitLine: {
lineStyle: {
color: 'rgba(79, 195, 247, 0.2)'
}
}
},
series: [
{
name: 'PH值',
type: 'line',
smooth: true,
data: [7.9, 8.0, 8.1, 8.2, 8.1],
lineStyle: {
color: '#00e396'
},
itemStyle: {
color: '#00e396'
}
},
{
name: '浊度',
type: 'line',
smooth: true,
data: [12.5, 11.8, 10.2, 9.5, 8.3],
lineStyle: {
color: '#00c6ff'
},
itemStyle: {
color: '#00c6ff'
}
},
{
name: '盐度',
type: 'line',
smooth: true,
data: [30.5, 30.8, 31.2, 31.5, 31.3],
lineStyle: {
color: '#4fc3f7'
},
itemStyle: {
color: '#4fc3f7'
}
}
]
};
waterQualityChart.setOption(option);
}
// 初始化含氧量趋势分析图表
function initOxygenChart() {
oxygenChart = echarts.init(document.getElementById('oxygenChart'));
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis'
},
legend: {
data: ['溶解氧'],
textStyle: {
color: '#e0f0ff'
},
top: '0%'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2019', '2020', '2021', '2022', '2023'],
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
}
},
yAxis: {
type: 'value',
name: 'mg/L',
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
},
splitLine: {
lineStyle: {
color: 'rgba(79, 195, 247, 0.2)'
}
}
},
series: [
{
name: '溶解氧',
type: 'line',
smooth: true,
data: [6.8, 6.9, 7.1, 7.2, 7.0],
lineStyle: {
color: '#ff6b6b'
},
itemStyle: {
color: '#ff6b6b'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(255, 107, 107, 0.5)'
}, {
offset: 1,
color: 'rgba(255, 107, 107, 0.1)'
}]
}
}
}
]
};
oxygenChart.setOption(option);
}
// 初始化叶绿素趋势分析图表
function initChlorophyllChart() {
chlorophyllChart = echarts.init(document.getElementById('chlorophyllChart'));
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis'
},
legend: {
data: ['叶绿素a'],
textStyle: {
color: '#e0f0ff'
},
top: '0%'
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2019', '2020', '2021', '2022', '2023'],
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
}
},
yAxis: {
type: 'value',
name: 'μg/L',
axisLine: {
lineStyle: {
color: '#4fc3f7'
}
},
axisLabel: {
color: '#a0d2ff'
},
splitLine: {
lineStyle: {
color: 'rgba(79, 195, 247, 0.2)'
}
}
},
series: [
{
name: '叶绿素a',
type: 'line',
smooth: true,
data: [3.2, 3.5, 4.1, 4.3, 4.0],
lineStyle: {
color: '#a8e6cf'
},
itemStyle: {
color: '#a8e6cf'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: 'rgba(168, 230, 207, 0.5)'
}, {
offset: 1,
color: 'rgba(168, 230, 207, 0.1)'
}]
}
}
}
]
};
chlorophyllChart.setOption(option);
}
// 初始化地图
async function initMap() {
try {
// 通过 AJAX 加载 config.json 配置文件
fetch('./config/config2.json')
.then(response => response.json())
.then(config => {
// 使用配置文件初始化地图
map = new mars3d.Map("mars3dMap", config.map3d);
// 等待地图完全加载后再添加图层
map.on(mars3d.EventType.load, function () {
console.log('地图加载完成');
setTimeout(() => {
// 加载生态区域热力图
addEcoRegionsHeatLayer();
// 添加图例
addLegend();
// 添加 GeoTIFF 图层
addGeoTIFFLayer();
}, 1000);
});
})
.catch(error => {
console.error('加载配置文件失败:', error);
// 使用默认配置
initDefaultMap();
});
} catch (error) {
console.error('初始化 Mars3D 地图时出错:', error);
initDefaultMap();
}
}
// 默认地图配置
function initDefaultMap() {
map = new mars3d.Map("mars3dMap", {
scene: {
center: { lat: 34.7, lng: 119.5, alt: 50000, heading: 0, pitch: -45 },
showSun: true,
showMoon: true,
showSkyBox: true,
showSkyAtmosphere: true,
fog: true,
fxaa: true,
cameraController: {
zoomFactor: 2.0,
minimumZoomDistance: 1,
maximumZoomDistance: 50000000,
}
},
control: {
homeButton: true,
sceneModePicker: true,
baseLayerPicker: true,
navigationHelpButton: true,
animation: true,
timeline: true,
fullscreenButton: true,
vrButton: true,
},
basemaps: [
{
id: 10,
name: "天地图影像",
icon: "img/basemaps/tdt_img.png",
type: "group",
layers: [
{
name: "底图",
type: "tdt",
layer: "img_d",
},
{
name: "注记",
type: "tdt",
layer: "cia_w",
}
]
}
]
});
// 地图加载完成后添加图层
map.on(mars3d.EventType.load, function () {
console.log('默认地图加载完成');
setTimeout(() => {
// 加载生态区域热力图
addEcoRegionsHeatLayer();
// 添加图例
addLegend();
// 添加 GeoTIFF 图层
addGeoTIFFLayer();
}, 1000);
});
}
// 添加区域选择事件监听
document.getElementById('region-select').addEventListener('change', function () {
updateCharts(this.value);
});
// 生成连云港沿海岸线生态监测点数据
function generateCoastalEcoPoints() {
const points = [];
// 连云港沿海岸线坐标范围(更贴近实际海岸线)
const coastalAreas = [
// 赣榆区沿海
{ startLng: 119.1, startLat: 35.0, endLng: 119.4, endLat: 34.9, density: 80 },
// 连云区沿海
{ startLng: 119.4, startLat: 34.9, endLng: 119.7, endLat: 34.7, density: 120 },
// 灌云县沿海
{ startLng: 119.7, startLat: 34.7, endLng: 120.0, endLat: 34.5, density: 100 },
// 灌南县沿海
{ startLng: 119.5, startLat: 34.4, endLng: 119.8, endLat: 34.3, density: 60 }
];
coastalAreas.forEach(area => {
for (let i = 0; i < area.density; i++) {
// 在沿海区域生成随机点
const lng = area.startLng + Math.random() * (area.endLng - area.startLng);
const lat = area.startLat + Math.random() * (area.endLat - area.startLat);
// 根据位置确定生态类型和颜色
const ecoData = getEcoTypeByLocation(lng, lat);
points.push({
lng: lng,
lat: lat,
value: ecoData.value,
type: ecoData.type,
name: ecoData.name,
color: ecoData.color
});
}
});
return points;
}
// 根据位置确定生态类型
function getEcoTypeByLocation(lng, lat) {
// 根据经纬度确定生态类型
if (lng > 119.8 && lat < 34.4) {
return { type: "ocean", name: "海洋", color: "#081d58", value: 0.9 };
} else if (lng > 119.6 && lat < 34.6) {
return { type: "tidal-flat", name: "潮滩", color: "#ffff00", value: 0.8 };
} else if (lng > 119.4 && lat < 34.7) {
return { type: "salt-marsh", name: "盐沼", color: "#1d91c0", value: 0.7 };
} else if (lng > 119.3 && lat < 34.8) {
return { type: "mangrove", name: "红树林", color: "#41b6c4", value: 0.6 };
} else if (lng > 119.2 && lat < 34.9) {
return { type: "permanent-water", name: "永久水域", color: "#2ca25f", value: 0.5 };
} else if (lng > 119.1 && lat < 35.0) {
return { type: "swamp-woody", name: "沼泽(乔木/灌木)", color: "#756bb1", value: 0.4 };
} else {
return { type: "non-wetland", name: "非湿地", color: "#cccccc", value: 0.3 };
}
}
// 添加生态区域热力图
function addEcoRegionsHeatLayer() {
if (!map) {
console.warn('地图未初始化完成,延迟添加生态区域热力图');
setTimeout(addEcoRegionsHeatLayer, 1000);
return;
}
try {
console.log('开始加载生态区域热力图数据...');
// 生成沿海岸线生态监测点数据
const coastalPoints = generateCoastalEcoPoints();
// 转换为热力图需要的格式
const heatPoints = coastalPoints.map(point => ({
lng: point.lng,
lat: point.lat,
value: point.value
}));
console.log('生成的生态区域热力图数据点:', heatPoints.length);
// 检查热力图插件是否可用
if (typeof mars3d.layer.HeatLayer !== 'undefined') {
console.log('使用Mars3D热力图插件创建生态区域热力图');
if (heatLayer) {
map.removeLayer(heatLayer);
heatLayer = null;
}
// 创建生态区域热力图
heatLayer = new mars3d.layer.HeatLayer({
name: '连云港生态区域热力图',
positions: heatPoints,
heatStyle: {
radius: 50,
blur: 0.7,
gradient: {
0.0: '#cccccc', // 非湿地
0.3: '#2ca25f', // 永久水域
0.4: '#756bb1', // 沼泽(乔木/灌木)
0.5: '#dd3497', // 沼泽(草本植被)
0.6: '#fdbb84', // 淹滩
0.7: '#b30000', // 盐碱地
0.8: '#41b6c4', // 红树林
0.9: '#1d91c0', // 盐沼
1.0: '#081d58' // 海洋
},
},
style: {
arc: false,
height: 500,
},
});
map.addLayer(heatLayer);
console.log('生态区域热力图图层添加成功');
// 添加点击事件显示生态信息
heatLayer.on(mars3d.EventType.click, function (event) {
const position = event.cartographic;
const lng = position.lng;
const lat = position.lat;
// 查找最近的生态监测点
const nearestPoint = findNearestEcoPoint(lng, lat, coastalPoints);
if (nearestPoint) {
showEcoPointInfo(nearestPoint, event);
}
});
} else {
console.log('热力图插件不可用,使用点图层模拟生态区域');
createEcoPointLayerSimulation(coastalPoints);
}
} catch (error) {
console.error('加载生态区域热力图时出错:', error);
// 出错时使用点图层模拟
const coastalPoints = generateCoastalEcoPoints();
createEcoPointLayerSimulation(coastalPoints);
}
}
// 查找最近的生态监测点
function findNearestEcoPoint(lng, lat, points) {
let nearestPoint = null;
let minDistance = Infinity;
points.forEach(point => {
const distance = Math.sqrt(
Math.pow(point.lng - lng, 2) +
Math.pow(point.lat - lat, 2)
);
if (distance < minDistance) {
minDistance = distance;
nearestPoint = point;
}
});
return nearestPoint;
}
// 显示生态监测点信息
function showEcoPointInfo(point, event) {
// 移除现有的信息卡片
const existingCard = document.querySelector('.info-card');
if (existingCard) {
existingCard.remove();
}
// 创建信息卡片
const infoCard = document.createElement('div');
infoCard.className = 'info-card';
infoCard.style.cssText = `
position: absolute;
left: ${event.containerPosition.x}px;
top: ${event.containerPosition.y}px;
transform: translate(-50%, -100%);
margin-top: -20px;
`;
infoCard.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 style="margin: 0; color: #4fc3f7; font-size: 14px;">${point.name}</h3>
<button class="info-card-close" style="background: none; border: none; color: #a0d2ff; cursor: pointer;">×</button>
</div>
<div class="info-card-content">
<div class="info-item">
<span class="info-label">生态类型:</span>
<span class="info-value">${point.type}</span>
</div>
<div class="info-item">
<span class="info-label">生态指数:</span>
<span class="info-value">${point.value.toFixed(2)}</span>
</div>
<div class="info-item">
<span class="info-label">坐标:</span>
<span class="info-value">${point.lng.toFixed(4)}, ${point.lat.toFixed(4)}</span>
</div>
<div class="info-item">
<span class="info-label">颜色标识:</span>
<span class="info-value">
<div style="width: 15px; height: 15px; background: ${point.color}; display: inline-block; margin-left: 5px; border: 1px solid white;"></div>
</span>
</div>
</div>
`;
document.querySelector('.earth-container').appendChild(infoCard);
// 添加关闭按钮事件
const closeButton = infoCard.querySelector('.info-card-close');
closeButton.addEventListener('click', function () {
infoCard.remove();
});
// 点击地图其他地方关闭卡片
map.once(mars3d.EventType.click, function () {
infoCard.remove();
});
}
// 创建生态区域点图层模拟
function createEcoPointLayerSimulation(points) {
if (heatLayer) {
map.removeLayer(heatLayer);
heatLayer = null;
}
// 创建图形图层来模拟生态区域热力图
heatLayer = new mars3d.layer.GraphicLayer({
name: '生态区域热力图模拟'
});
// 先将图层添加到地图
map.addLayer(heatLayer);
// 根据生态类型设置不同的颜色和大小
points.forEach(point => {
const graphic = new mars3d.graphic.PointEntity({
position: [point.lng, point.lat],
style: {
pixelSize: 10,
color: point.color,
outlineColor: '#FFFFFF',
outlineWidth: 1,
opacity: 0.7
},
attr: {
name: point.name,
type: point.type,
value: point.value
},
popup: `
<div style="min-width: 200px;">
<h3 style="margin: 0 0 10px 0; color: #4fc3f7;">${point.name}</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
<div><strong>生态类型:</strong></div><div>${point.type}</div>
<div><strong>生态指数:</strong></div><div>${point.value.toFixed(2)}</div>
<div><strong>坐标:</strong></div><div>${point.lng.toFixed(4)}, ${point.lat.toFixed(4)}</div>
<div><strong>颜色标识:</strong></div>
<div>
<div style="width: 15px; height: 15px; background: ${point.color}; border: 1px solid white;"></div>
</div>
</div>
</div>
`
});
// 添加到已存在的图层
heatLayer.addGraphic(graphic);
});
console.log('生态区域点图层模拟热力图创建完成');
}
// 添加图例
function addLegend() {
const legend = document.createElement('div');
legend.style.cssText = `
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(16, 36, 62, 0.85);
border: 1px solid rgba(64, 156, 255, 0.3);
border-radius: 8px;
padding: 15px;
color: #e0f0ff;
z-index: 1000;
max-width: 250px;
backdrop-filter: blur(5px);
`;
legend.innerHTML = `
<h3 style="margin: 0 0 10px 0; color: #4fc3f7; font-size: 14px;">生态区域图例</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
${lianyungangEcoRegions.map(region => `
<div style="display: flex; align-items: center; margin-bottom: 5px;">
<div style="width: 15px; height: 15px; background: ${region.color}; margin-right: 8px; border: 1px solid white;"></div>
<span style="font-size: 12px;">${region.name}</span>
</div>
`).join('')}
</div>
`;
document.querySelector('.earth-container').appendChild(legend);
}
// 初始化事件监听器
function initEventListeners() {
// 窗口大小变化时重新调整图表大小
window.addEventListener('resize', function () {
waterQualityChart.resize();
oxygenChart.resize();
chlorophyllChart.resize();
});
}
// 更新图表数据
function updateCharts(region) {
// 根据区域更新图表数据
const regionData = {
'all': {
ph: [7.9, 8.0, 8.1, 8.2, 8.1],
turbidity: [12.5, 11.8, 10.2, 9.5, 8.3],
salinity: [30.5, 30.8, 31.2, 31.5, 31.3],
oxygen: [6.8, 6.9, 7.1, 7.2, 7.0],
chlorophyll: [3.2, 3.5, 4.1, 4.3, 4.0]
},
'lianyungang': {
ph: [7.8, 7.9, 8.0, 8.1, 8.0],
turbidity: [11.5, 10.8, 9.2, 8.5, 7.3],
salinity: [29.5, 29.8, 30.2, 30.5, 30.3],
oxygen: [6.7, 6.8, 7.0, 7.1, 6.9],
chlorophyll: [3.0, 3.3, 3.9, 4.1, 3.8]
},
'lanshan': {
ph: [8.0, 8.1, 8.2, 8.3, 8.2],
turbidity: [13.5, 12.8, 11.2, 10.5, 9.3],
salinity: [31.5, 31.8, 32.2, 32.5, 32.3],
oxygen: [6.9, 7.0, 7.2, 7.3, 7.1],
chlorophyll: [3.4, 3.7, 4.3, 4.5, 4.2]
},
'ganyu': {
ph: [7.7, 7.8, 7.9, 8.0, 7.9],
turbidity: [10.5, 9.8, 8.2, 7.5, 6.3],
salinity: [28.5, 28.8, 29.2, 29.5, 29.3],
oxygen: [6.6, 6.7, 6.9, 7.0, 6.8],
chlorophyll: [2.8, 3.1, 3.7, 3.9, 3.6]
},
'guannan': {
ph: [7.9, 8.0, 8.1, 8.2, 8.1],
turbidity: [12.0, 11.3, 9.7, 9.0, 7.8],
salinity: [30.0, 30.3, 30.7, 31.0, 30.8],
oxygen: [6.8, 6.9, 7.1, 7.2, 7.0],
chlorophyll: [3.1, 3.4, 4.0, 4.2, 3.9]
},
'guanyun': {
ph: [8.0, 8.1, 8.2, 8.3, 8.2],
turbidity: [13.0, 12.3, 10.7, 10.0, 8.8],
salinity: [31.0, 31.3, 31.7, 32.0, 31.8],
oxygen: [6.9, 7.0, 7.2, 7.3, 7.1],
chlorophyll: [3.3, 3.6, 4.2, 4.4, 4.1]
}
};
const data = regionData[region] || regionData['all'];
// 更新水质分析图表
waterQualityChart.setOption({
series: [
{ data: data.ph },
{ data: data.turbidity },
{ data: data.salinity }
]
});
// 更新含氧量趋势图表
oxygenChart.setOption({
series: [
{ data: data.oxygen }
]
});
// 更新叶绿素趋势图表
chlorophyllChart.setOption({
series: [
{ data: data.chlorophyll }
]
});
}
// 替换原有的 addGeoTIFFLayer 函数
async function addGeoTIFFLayer() {
try {
// 使用 GeoTIFF.js 读取文件
const tiff = await GeoTIFF.fromUrl('/data/GWL_FCS30_2020_E95N0.tif');
const image = await tiff.getImage();
const bbox = image.getBoundingBox(); // 获取地理范围
const raster = await image.readRasters();
// 创建 Canvas 绘制图像
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = image.getWidth();
const height = image.getHeight();
canvas.width = width;
canvas.height = height;
// 这里需要根据你的 GeoTIFF 数据类型进行处理
// 简单示例:假设是单波段灰度图像
const data = raster[0];
const imageData = ctx.createImageData(width, height);
for (let i = 0; i < data.length; i++) {
const value = data[i];
const idx = i * 4;
// 简单的灰度映射
imageData.data[idx] = value; // R
imageData.data[idx + 1] = value; // G
imageData.data[idx + 2] = value; // B
imageData.data[idx + 3] = 255; // A
}
ctx.putImageData(imageData, 0, 0);
// 创建图像图层
const imageLayer = new mars3d.layer.ImageLayer({
name: "GWL_FCS30_2020_E95N0",
url: canvas.toDataURL(),
rectangle: {
xmin: bbox[0], // west
xmax: bbox[2], // east
ymin: bbox[1], // south
ymax: bbox[3] // north
},
opacity: 0.8,
style: {
clampToGround: true
}
});
map.addLayer(imageLayer);
console.log("GeoTIFF 图层加载成功");
} catch (error) {
console.error("加载 GeoTIFF 文件时出错:", error);
}
}
</script>
</body>
</html>