|
|
<!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> |