Compare commits
19 Commits
727c204ae7
...
6f674873cf
| Author | SHA1 | Date |
|---|---|---|
|
|
6f674873cf | |
|
|
e565cce907 | |
|
|
b9e273fbcc | |
|
|
610a47b4bd | |
|
|
43a2bfaf57 | |
|
|
ca9973fd9b | |
|
|
f2015af2b7 | |
|
|
7978bd3a61 | |
|
|
7970910f6b | |
|
|
9302be89c5 | |
|
|
20444e9243 | |
|
|
51478c40c9 | |
|
|
220da59e62 | |
|
|
96585b087b | |
|
|
2a193a9dff | |
|
|
6658118fba | |
|
|
ac105fd0c5 | |
|
|
e2e3dd6952 | |
|
|
cd3774770e |
|
|
@ -10,6 +10,8 @@ module.exports = {
|
||||||
es6: true
|
es6: true
|
||||||
},
|
},
|
||||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||||
|
// 完全禁用 ESLint
|
||||||
|
lintOnSave: false,
|
||||||
|
|
||||||
// add your custom rules here
|
// add your custom rules here
|
||||||
// it is base on https://github.com/vuejs/eslint-config-vue
|
// it is base on https://github.com/vuejs/eslint-config-vue
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ module.exports = {
|
||||||
|
|
||||||
// Various Dev Server settings
|
// Various Dev Server settings
|
||||||
host: 'localhost', // can be overwritten by process.env.HOST
|
host: 'localhost', // can be overwritten by process.env.HOST
|
||||||
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
port: 8181, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||||
autoOpenBrowser: false,
|
autoOpenBrowser: false,
|
||||||
errorOverlay: true,
|
errorOverlay: true,
|
||||||
notifyOnErrors: true,
|
notifyOnErrors: true,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,13 @@
|
||||||
<link rel="stylesheet" href="./static/css/reset.css">
|
<link rel="stylesheet" href="./static/css/reset.css">
|
||||||
<link rel="stylesheet" href="./static/js/Cesium/Widgets/widgets.css">
|
<link rel="stylesheet" href="./static/js/Cesium/Widgets/widgets.css">
|
||||||
<script type="text/javascript" src="./static/js/Cesium/Cesium.js"></script>
|
<script type="text/javascript" src="./static/js/Cesium/Cesium.js"></script>
|
||||||
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&type=webgl&ak=AwIb6rcMDySo2AKQ2hVGIjmFCL4J1oJK"></script>-->
|
|
||||||
|
<!-- 引入 Mapbox GL JS -->
|
||||||
|
<link href="./static/css/mapbox-gl-v3.16.0.css" rel="stylesheet">
|
||||||
|
<script src="./static/js/mapbox-gl-v3.16.0.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&type=webgl&ak=AwIb6rcMDySo2AKQ2hVGIjmFCL4J1oJK"></script>-->
|
||||||
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> -->
|
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts-gl/dist/echarts-gl.min.js"></script> -->
|
||||||
<!-- <script type="text/javascript" src="http://maps.google.com/maps/api/js?key=AIzaSyD9kf3RTO2HwggOQ1_fbZawfiKzyBNPXeY"></script> -->
|
<!-- <script type="text/javascript" src="http://maps.google.com/maps/api/js?key=AIzaSyD9kf3RTO2HwggOQ1_fbZawfiKzyBNPXeY"></script> -->
|
||||||
<!-- <script type="text/javascript" src="http://124.16.219.154:8080/geoserver/gwc/service/wmts?service=WMTS&version=1.1.1&request=GetCapabilities"></script> -->
|
<!-- <script type="text/javascript" src="http://124.16.219.154:8080/geoserver/gwc/service/wmts?service=WMTS&version=1.1.1&request=GetCapabilities"></script> -->
|
||||||
|
|
|
||||||
20
src/main.js
|
|
@ -15,6 +15,7 @@ import axios from 'axios'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
|
|
||||||
import store from './store/index'
|
import store from './store/index'
|
||||||
|
import { extractWMTSBoundsInfo } from '@/utils/mapbox-utils'
|
||||||
|
|
||||||
window.sessionStorage.setItem('webInfo', 'ASOS')
|
window.sessionStorage.setItem('webInfo', 'ASOS')
|
||||||
Vue.use(ElementUI, { locale: Zhlocale })
|
Vue.use(ElementUI, { locale: Zhlocale })
|
||||||
|
|
@ -31,3 +32,22 @@ new Vue({
|
||||||
components: { App },
|
components: { App },
|
||||||
template: '<App/>'
|
template: '<App/>'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 定义部分全局变量
|
||||||
|
Vue.config.maps = {};
|
||||||
|
Vue.config.firstRender = true;
|
||||||
|
Vue.config.boundsInfoList = [];
|
||||||
|
Vue.config.baseUrl = process.env.NODE_ENV === 'development' ? 'http://124.222.8.13:9801' : `${window.location.protocol}//${window.location.hostname}:${window.location.port}`
|
||||||
|
Vue.config.wwwrootBaseUrl = "_content/IDSSE.OceanExplorer.Shared";
|
||||||
|
Vue.config.sprites = ["go.png", "ship.png", "ship1.png", "ship2.png", "ship3.png", "fdz.png", "shys.png", "auv.png", "aircraft.png", "aircraft-r.png", "arrow.png", "dot.png", "dot_red.png", "default.png", "info-dark.png", "warning-dark.png", "warning-red.png", "slow_sog.png", "stop_sog.png", "海底主基站.png", "石油平台.png", "岸基站.png", "circle-red.png", "circle-green.png", "circle-blue.png", "circle-yellow.png", "circle-purple.png", "circle-black.png", "circle-white.png"];
|
||||||
|
|
||||||
|
if (Vue.config.firstRender) {
|
||||||
|
Vue.config.firstRender = !Vue.config.firstRender;
|
||||||
|
fetch(`${Vue.config.baseUrl}/geoserver/gwc/service/wmts?service=WMTS&acceptVersions=1.0.0&request=GetCapabilities`)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(async (xmlContent) => {
|
||||||
|
Vue.config.boundsInfoList = await extractWMTSBoundsInfo(xmlContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.mapboxgl.accessToken = 'pk.eyJ1IjoicWRod2kiLCJhIjoiY2xiaXppcjEyMGNhMjNwbzVrcHl4d20zOSJ9.0ETMJM3-zllFh2GRrRVUZg'
|
||||||
|
|
@ -1779,4 +1779,64 @@ header{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mapboxgl-ctrl-logo {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table {
|
||||||
|
border-spacing: 0;
|
||||||
|
font-family: 'Microsoft YaHei';
|
||||||
|
color: #006400;
|
||||||
|
/*border-collapse: collapse;
|
||||||
|
empty-cells: show;*/
|
||||||
|
table-layout: fixed;
|
||||||
|
word-break: break-all;
|
||||||
|
word-wrap: break-word;
|
||||||
|
border-collapse: collapse;
|
||||||
|
/* min-width:300px;
|
||||||
|
max-width:400px;*/
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table caption {
|
||||||
|
font: italic 85%/1 arial, sans-serif;
|
||||||
|
padding: 1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table td,
|
||||||
|
.popup-table th {
|
||||||
|
border: 1px solid rgba(0, 100, 255, 0.2);
|
||||||
|
border-width: 1px 1px 1px 1px;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 3px;
|
||||||
|
overflow: visible;
|
||||||
|
/*white-space: normal;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table thead {
|
||||||
|
font-family: 'Microsoft YaHei';
|
||||||
|
background-color: rgba(0, 100, 255, 0.2);
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table td {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table-odd td {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table-td-left td {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table-td-center td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-table-td-right td {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
export class ColorGenerator {
|
||||||
|
// 生成适合地图显示的区分度高的颜色
|
||||||
|
static generateDistinctColors(count) {
|
||||||
|
const colors = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
// 使用黄金角分布算法,确保颜色分布均匀
|
||||||
|
const hue = (i * 137.5) % 360; // 黄金角约等于 137.5 度
|
||||||
|
const saturation = 70 + Math.random() * 20; // 70-90% 饱和度
|
||||||
|
const lightness = 45 + Math.random() * 20; // 45-65% 亮度
|
||||||
|
|
||||||
|
const hsl = { h: hue, s: saturation, l: lightness };
|
||||||
|
colors.push(HSLColorGenerator.toCSS(hsl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成分类数据颜色(确保相邻类别颜色差异明显)
|
||||||
|
static generateCategoricalColors(count, startHue = 0) {
|
||||||
|
const hueStep = 360 / count;
|
||||||
|
const colors = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const hue = (startHue + i * hueStep) % 360;
|
||||||
|
const hsl = {
|
||||||
|
h: hue,
|
||||||
|
s: 80 + Math.random() * 15, // 80-95% 饱和度
|
||||||
|
l: 50 + Math.random() * 15 // 50-65% 亮度
|
||||||
|
};
|
||||||
|
colors.push(HSLColorGenerator.toCSS(hsl));
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成连续数据颜色(渐变)
|
||||||
|
static generateSequentialColors(
|
||||||
|
count,
|
||||||
|
startColor = '#f7fbff',
|
||||||
|
endColor = '#08306b'
|
||||||
|
) {
|
||||||
|
const colors = [];
|
||||||
|
|
||||||
|
// 解析起始颜色和结束颜色
|
||||||
|
const startRGB = this.parseHexColor(startColor);
|
||||||
|
const endRGB = this.parseHexColor(endColor);
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const ratio = i / (count - 1);
|
||||||
|
const r = Math.round(startRGB.r + (endRGB.r - startRGB.r) * ratio);
|
||||||
|
const g = Math.round(startRGB.g + (endRGB.g - startRGB.g) * ratio);
|
||||||
|
const b = Math.round(startRGB.b + (endRGB.b - startRGB.b) * ratio);
|
||||||
|
|
||||||
|
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成发散颜色(用于有正负值的数据)
|
||||||
|
static generateDivergingColors(
|
||||||
|
count,
|
||||||
|
negativeColor = '#d73027',
|
||||||
|
neutralColor = '#ffffbf',
|
||||||
|
positiveColor = '#1a9850'
|
||||||
|
) {
|
||||||
|
const colors = [];
|
||||||
|
const midPoint = Math.floor(count / 2);
|
||||||
|
|
||||||
|
// 负值部分
|
||||||
|
const negRGB = this.parseHexColor(negativeColor);
|
||||||
|
const neutralRGB = this.parseHexColor(neutralColor);
|
||||||
|
|
||||||
|
for (let i = 0; i < midPoint; i++) {
|
||||||
|
const ratio = i / midPoint;
|
||||||
|
const r = Math.round(negRGB.r + (neutralRGB.r - negRGB.r) * ratio);
|
||||||
|
const g = Math.round(negRGB.g + (neutralRGB.g - negRGB.g) * ratio);
|
||||||
|
const b = Math.round(negRGB.b + (neutralRGB.b - negRGB.b) * ratio);
|
||||||
|
|
||||||
|
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正值部分
|
||||||
|
const positiveRGB = this.parseHexColor(positiveColor);
|
||||||
|
|
||||||
|
for (let i = midPoint; i < count; i++) {
|
||||||
|
const ratio = (i - midPoint) / (count - midPoint - 1);
|
||||||
|
const r = Math.round(neutralRGB.r + (positiveRGB.r - neutralRGB.r) * ratio);
|
||||||
|
const g = Math.round(neutralRGB.g + (positiveRGB.g - neutralRGB.g) * ratio);
|
||||||
|
const b = Math.round(neutralRGB.b + (positiveRGB.b - neutralRGB.b) * ratio);
|
||||||
|
|
||||||
|
colors.push(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析十六进制颜色
|
||||||
|
static parseHexColor(hex) {
|
||||||
|
hex = hex.replace('#', '');
|
||||||
|
|
||||||
|
if (hex.length === 3) {
|
||||||
|
hex = hex.split('').map(c => c + c).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: parseInt(hex.substring(0, 2), 16),
|
||||||
|
g: parseInt(hex.substring(2, 4), 16),
|
||||||
|
b: parseInt(hex.substring(4, 6), 16)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HSLColorGenerator {
|
||||||
|
// 随机 HSL 颜色
|
||||||
|
static randomHSL() {
|
||||||
|
return {
|
||||||
|
h: Math.floor(Math.random() * 360),
|
||||||
|
s: Math.floor(Math.random() * 101),
|
||||||
|
l: Math.floor(Math.random() * 101)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 CSS 字符串
|
||||||
|
static toCSS(color) {
|
||||||
|
if (color.alpha !== undefined) {
|
||||||
|
return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.alpha})`;
|
||||||
|
}
|
||||||
|
return `hsl(${color.h}, ${color.s}%, ${color.l}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成鲜艳的颜色(高饱和度)
|
||||||
|
static randomVibrantHSL(minSaturation = 80, minLightness = 40) {
|
||||||
|
return {
|
||||||
|
h: Math.floor(Math.random() * 360),
|
||||||
|
s: minSaturation + Math.floor(Math.random() * (101 - minSaturation)),
|
||||||
|
l: minLightness + Math.floor(Math.random() * (61 - minLightness))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成柔和的颜色(低饱和度)
|
||||||
|
static randomPastelHSL(maxSaturation = 40, minLightness = 70) {
|
||||||
|
return {
|
||||||
|
h: Math.floor(Math.random() * 360),
|
||||||
|
s: Math.floor(Math.random() * (maxSaturation + 1)),
|
||||||
|
l: minLightness + Math.floor(Math.random() * (101 - minLightness))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为 RGB
|
||||||
|
static toRGB(hsl) {
|
||||||
|
const h = hsl.h / 360;
|
||||||
|
const s = hsl.s / 100;
|
||||||
|
const l = hsl.l / 100;
|
||||||
|
|
||||||
|
let r, g, b;
|
||||||
|
|
||||||
|
if (s === 0) {
|
||||||
|
r = g = b = l;
|
||||||
|
} else {
|
||||||
|
const hue2rgb = (p, q, t) => {
|
||||||
|
if (t < 0) t += 1;
|
||||||
|
if (t > 1) t -= 1;
|
||||||
|
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||||
|
if (t < 1 / 2) return q;
|
||||||
|
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||||
|
const p = 2 * l - q;
|
||||||
|
r = hue2rgb(p, q, h + 1 / 3);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1 / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
r: Math.round(r * 255),
|
||||||
|
g: Math.round(g * 255),
|
||||||
|
b: Math.round(b * 255),
|
||||||
|
alpha: hsl.alpha
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有图层信息
|
||||||
|
* @param {mapboxgl.Map} map - Mapbox 地图实例
|
||||||
|
* @returns {Array} 图层信息数组
|
||||||
|
*/
|
||||||
|
export function getAllLayers(map) {
|
||||||
|
if (!map || !map.getStyle()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = map.getStyle();
|
||||||
|
return style.layers || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 安全地设置图层属性
|
||||||
|
* @param {mapboxgl.Map} map - Mapbox 地图实例
|
||||||
|
* @param {string} layerId - 图层ID
|
||||||
|
* @param {string} property - 属性名
|
||||||
|
* @param {any} value - 属性值
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function safeSetPaintProperty(map, layerId, property, value) {
|
||||||
|
if (!map.getLayer(layerId)) {
|
||||||
|
console.warn(`图层 ${layerId} 不存在,无法设置属性`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
map.setPaintProperty(layerId, property, value);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`设置图层 ${layerId} 属性 ${property} 失败:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleWheel(mapId = "home") {
|
||||||
|
document.addEventListener("keydown", () => {
|
||||||
|
// 允许鼠标滚轮缩放
|
||||||
|
Vue.config.maps[mapId].scrollZoom.enable();
|
||||||
|
}, { once: false, passive: false });
|
||||||
|
document.addEventListener("keyup", () => {
|
||||||
|
// 禁止鼠标滚轮缩放
|
||||||
|
Vue.config.maps[mapId].scrollZoom.disable();
|
||||||
|
}, { once: false, passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initSprites(mapId = "home") {//../../../static/images/logo.png
|
||||||
|
for (const sprite of Vue.config.sprites) {
|
||||||
|
// Vue.config.maps[mapId].loadImage(`${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/${sprite}`, function (error, image) {
|
||||||
|
Vue.config.maps[mapId].loadImage(`./static/sprites/${sprite}`, function (error, image) {
|
||||||
|
if (error) throw error;
|
||||||
|
if (!Vue.config.maps[mapId].hasImage(sprite))
|
||||||
|
// 为图像启用 sdf: { sdf: true },启用的 sdf 的图像,在 Symbol 图层中就能够通过 icon-color 属性来重新渲染图像的颜色。
|
||||||
|
Vue.config.maps[mapId].addImage(sprite, image, { sdf: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function extractWMTSBoundsInfo(xmlContent) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(xmlContent, "text/xml");
|
||||||
|
|
||||||
|
const featureTypes = xmlDoc.getElementsByTagName("Layer");
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < featureTypes.length; i++) {
|
||||||
|
const featureType = featureTypes[i];
|
||||||
|
|
||||||
|
// 提取 Name
|
||||||
|
const nameElement = featureType.getElementsByTagName("ows:Identifier")[0];
|
||||||
|
const name = nameElement ? nameElement.textContent || "" : "";
|
||||||
|
|
||||||
|
// 提取 WGS84BoundingBox
|
||||||
|
const wgs84BoundingBox = featureType.getElementsByTagName("ows:WGS84BoundingBox")[0];
|
||||||
|
|
||||||
|
if (wgs84BoundingBox) {
|
||||||
|
const lowerCorner = wgs84BoundingBox.getElementsByTagName("ows:LowerCorner")[0].textContent;
|
||||||
|
const upperCorner = wgs84BoundingBox.getElementsByTagName("ows:UpperCorner")[0].textContent;
|
||||||
|
if (name) {
|
||||||
|
result.push({
|
||||||
|
layerName: name,
|
||||||
|
bounds: [Number(lowerCorner.split(" ")[0]), Number(lowerCorner.split(" ")[1]), Number(upperCorner.split(" ")[0]), Number(upperCorner.split(" ")[1])]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发指定图层的点击事件
|
||||||
|
* @param {mapboxgl.Map} map - Mapbox地图实例
|
||||||
|
* @param {string} layerId - 要触发的图层ID
|
||||||
|
* @param {Array} lnglat - 经纬度 [经度, 纬度]
|
||||||
|
* @param {Object} options - 附加选项
|
||||||
|
*/
|
||||||
|
export async function triggerLayerClick(mapId, sourceLayerId, layerId, lnglat, options = {}) {
|
||||||
|
if (!Vue.config.maps[mapId] || !layerId || !lnglat) {
|
||||||
|
console.error('缺少必要参数');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [lng, lat] = Array.isArray(lnglat) ? lnglat : [lnglat.lng, lnglat.lat];
|
||||||
|
|
||||||
|
// 1. 确保地图和图层存在
|
||||||
|
if (!Vue.config.maps[mapId].getLayer(layerId)) {
|
||||||
|
console.error(`图层 ${layerId} 不存在`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 将经纬度转换为屏幕坐标
|
||||||
|
const point = Vue.config.maps[mapId].project([lng, lat]);
|
||||||
|
|
||||||
|
// 3. 查询该点/源下的图层要素
|
||||||
|
const bbox = [
|
||||||
|
[point.x - 1, point.y - 1],
|
||||||
|
[point.x + 1, point.y + 1]
|
||||||
|
];
|
||||||
|
|
||||||
|
const map = Vue.config.maps[mapId];
|
||||||
|
console.log("layerId: ", layerId, " sourceLayerId: ", sourceLayerId, " options: ", options);
|
||||||
|
|
||||||
|
// 尝试从图层获取 source id(一些图层的 source 名称与 layerId 不同)
|
||||||
|
const layerObj = map.getLayer(layerId);
|
||||||
|
const sourceId = (layerObj && layerObj.source) ? layerObj.source : layerId;
|
||||||
|
const source = map.getSource(sourceId);
|
||||||
|
|
||||||
|
// 在浏览器控制台或 triggerLayerClick 中运行(替换 sourceId/sourceLayerName)
|
||||||
|
const feats = map.querySourceFeatures(sourceId, { sourceLayer: "sample_station_TS-46-1_Ship_CTDI_SY" }) || [];
|
||||||
|
console.log(sourceId,">>>>>>>>>>>>>>>>>>>>sample_station_TS-46-1_Ship_CTDI_SY",' ======sample of features:', feats.slice(0, 5));
|
||||||
|
console.log('props keys:', feats.slice(0, 5).map(f => Object.keys(f.properties || {})));
|
||||||
|
|
||||||
|
let relatedTracks = map.querySourceFeatures(sourceId, {
|
||||||
|
sourceLayer: sourceId,
|
||||||
|
// 支持的过滤条件:==, !=, >, >=, <, <=, in, !in, all, any, none, has, !has
|
||||||
|
filter: ['==', ['get', 'sample_name'], options.sample_name]
|
||||||
|
});
|
||||||
|
console.log("relatedTracks: ", relatedTracks);
|
||||||
|
|
||||||
|
// 打印样式中与该 source 关联的所有 source-layer(便于调试 vector tiles 的 layer 名称)
|
||||||
|
try {
|
||||||
|
const styleLayers = map.getStyle().layers || [];
|
||||||
|
const sourceLayerNames = new Set();
|
||||||
|
styleLayers.forEach(l => {
|
||||||
|
if (l.source === sourceId && l['source-layer']) sourceLayerNames.add(l['source-layer']);
|
||||||
|
});
|
||||||
|
console.log('candidate source-layer names for', sourceId, Array.from(sourceLayerNames));
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('failed to enumerate style layers for debugging:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用表达式 ['==', ['get', prop], value] 来做精确匹配
|
||||||
|
const propName = 'sample_name';
|
||||||
|
const propValue = options.sample_name;
|
||||||
|
const filterExpr = ['==', ['get', propName], propValue];
|
||||||
|
// const filterExpr = ['==', propName, propValue];
|
||||||
|
|
||||||
|
let features = [];
|
||||||
|
try {
|
||||||
|
if (source) {
|
||||||
|
// 如果 source 尚未加载完,等待一次 sourcedata(有时 vector tiles 尚未请求完成)
|
||||||
|
if (!map.isSourceLoaded(sourceId)) {
|
||||||
|
console.log('source not loaded yet, waiting for sourcedata:', sourceId);
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
const onData = (e) => {
|
||||||
|
if (e && e.sourceId === sourceId && map.isSourceLoaded(sourceId)) {
|
||||||
|
map.off('sourcedata', onData);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 超时保护,2秒后继续
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
map.off('sourcedata', onData);
|
||||||
|
resolve();
|
||||||
|
}, 2000);
|
||||||
|
map.on('sourcedata', onData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("sourceId: ", sourceId, " sourceLayerId: ", sourceLayerId, " options: ", options);
|
||||||
|
// 优先使用 querySourceFeatures(对 vector/geojson 源性能更好)
|
||||||
|
features = map.querySourceFeatures(sourceId, { sourceLayer: sourceLayerId, filter: filterExpr }) || [];
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('querySourceFeatures failed:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果通过 source 查询不到,再退回到渲染层查询(rendered features)作为兜底
|
||||||
|
if (!features || features.length === 0) {
|
||||||
|
try {
|
||||||
|
features = map.queryRenderedFeatures(bbox, { layers: [layerId], filter: filterExpr }) || [];
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('queryRenderedFeatures fallback failed:', err);
|
||||||
|
features = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('resolved sourceId:', sourceId, 'found features:', features.length);
|
||||||
|
|
||||||
|
// 4. 创建事件对象
|
||||||
|
const eventObject = {
|
||||||
|
lngLat: { lng, lat },
|
||||||
|
point: point,
|
||||||
|
features: features,
|
||||||
|
options: options,
|
||||||
|
originalEvent: createSimulatedEvent(point),
|
||||||
|
target: Vue.config.maps[mapId].getCanvas(),
|
||||||
|
type: 'click',
|
||||||
|
layerId: layerId,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 5. 触发图层特定的点击事件
|
||||||
|
Vue.config.maps[mapId].fire('click', eventObject, { layerId: layerId });
|
||||||
|
|
||||||
|
return {
|
||||||
|
event: eventObject,
|
||||||
|
features: features,
|
||||||
|
success: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSimulatedEvent(point) {
|
||||||
|
return {
|
||||||
|
type: 'click',
|
||||||
|
clientX: point.x,
|
||||||
|
clientY: point.y,
|
||||||
|
preventDefault: () => { },
|
||||||
|
stopPropagation: () => { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,540 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import { regInfo } from '@/api/dataSearch'
|
||||||
|
|
||||||
|
let sourceLayer = undefined;
|
||||||
|
let clickedLineIds = [];
|
||||||
|
// 单例 Popup,用于确保地图上只有一个弹框实例
|
||||||
|
let sharedPopup = null;
|
||||||
|
|
||||||
|
export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "home", workspace = 'dsds', textField = "name") {
|
||||||
|
// 添加数据源
|
||||||
|
const sourceId = `vector-${layerId}`;
|
||||||
|
let source = Vue.config.maps[mapId].getSource(sourceId);
|
||||||
|
if (!source) {
|
||||||
|
// request vector tiles with extra buffer to avoid geometry clipping at tile edges
|
||||||
|
const tilesUrl = `${Vue.config.baseUrl}/geoserver/gwc/service/tms/1.0.0/${workspace}:${layerId}@EPSG:900913@pbf/{z}/{x}/{y}.pbf?buffer=64`;
|
||||||
|
let bb = Vue.config.boundsInfoList.find(x => x.layerName.toLowerCase() == `${workspace}:${layerId}`.toLocaleLowerCase());
|
||||||
|
let bounds = bb ? bb.bounds : null;
|
||||||
|
if (bounds) {
|
||||||
|
Vue.config.maps[mapId].addSource(sourceId, {
|
||||||
|
type: 'vector',
|
||||||
|
scheme: 'tms',
|
||||||
|
tiles: [tilesUrl],
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 30,
|
||||||
|
bounds: bounds,
|
||||||
|
generateId: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Vue.config.maps[mapId].addSource(sourceId, {
|
||||||
|
type: 'vector',
|
||||||
|
scheme: 'tms',
|
||||||
|
tiles: [tilesUrl],
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 30,
|
||||||
|
generateId: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
features.forEach((feature) => {
|
||||||
|
loadVectorFeature(layerId, feature, colors[i++], visibility, mapId, textField);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadVectorFeature(layerId, feature, color, visibility, mapId, textField = "name") {
|
||||||
|
let sourceId = `vector-${layerId}`;
|
||||||
|
// symbol
|
||||||
|
// Slot 是 Mapbox 样式规范中的一个概念,它定义了图层在渲染过程中的位置和顺序。
|
||||||
|
let slot = "foreground";
|
||||||
|
let layout = {
|
||||||
|
"symbol-placement": "point",
|
||||||
|
"text-rotation-alignment": "auto",
|
||||||
|
"text-field": ["get", textField],
|
||||||
|
"text-font": ["literal", ["Arial Unicode MS Regular", "DIN Offc Pro Italic", "Open Sans Regular"]],
|
||||||
|
"text-size": 12,
|
||||||
|
"text-variable-anchor": ["top", "bottom", "left", "right"],
|
||||||
|
"text-radial-offset": 1,
|
||||||
|
"text-justify": "auto",
|
||||||
|
"text-optional": true,
|
||||||
|
"text-allow-overlap": false,
|
||||||
|
"text-ignore-placement": false,
|
||||||
|
'symbol-spacing': 1000, // 像素间距,增大值可减少标注密度
|
||||||
|
// 'text-max-angle': 300, // 角度越大,标注越少
|
||||||
|
"icon-optional": true,
|
||||||
|
"icon-allow-overlap": false,
|
||||||
|
"icon-ignore-placement": false,
|
||||||
|
"icon-image": "dot.png",
|
||||||
|
"icon-size": 0.5,
|
||||||
|
"visibility": visibility
|
||||||
|
};
|
||||||
|
let paint = {
|
||||||
|
// "text-color": color,
|
||||||
|
"text-halo-color": "#FFFFFF",
|
||||||
|
"text-halo-width": 0.1,
|
||||||
|
"icon-opacity": 1,
|
||||||
|
"text-color": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "click"], false],
|
||||||
|
"red",
|
||||||
|
["boolean", ["feature-state", "hover"], false],
|
||||||
|
"yellow",
|
||||||
|
color
|
||||||
|
],
|
||||||
|
// slot: "middle"
|
||||||
|
};
|
||||||
|
if (feature === "line") {
|
||||||
|
slot = "top";
|
||||||
|
layout = {
|
||||||
|
"visibility": visibility,
|
||||||
|
// "line-join": "round",
|
||||||
|
// "line-cap": "round"
|
||||||
|
};
|
||||||
|
paint = {
|
||||||
|
"line-opacity": 1,
|
||||||
|
"line-width": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "hover"], false],
|
||||||
|
4,
|
||||||
|
["boolean", ["feature-state", "click"], false],
|
||||||
|
2,
|
||||||
|
1.8
|
||||||
|
],
|
||||||
|
"line-color": [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "click"], false],
|
||||||
|
"red",
|
||||||
|
["boolean", ["feature-state", "hover"], false],
|
||||||
|
"yellow",
|
||||||
|
color
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = Vue.config.maps[mapId].getLayer(`${sourceId}-${feature}`);
|
||||||
|
// console.log(">>>>>>>>>>>>>>>>>>source-layer: ", layerId);
|
||||||
|
if (!layer) {
|
||||||
|
// console.log("Adding vector layer:", `${sourceId}-${feature}`);
|
||||||
|
Vue.config.maps[mapId].addLayer({
|
||||||
|
id: `${sourceId}-${feature}`,
|
||||||
|
'type': feature,
|
||||||
|
'source': sourceId,
|
||||||
|
'source-layer': layerId,
|
||||||
|
'layout': layout,
|
||||||
|
'paint': paint,
|
||||||
|
// 'slot': 'foreground'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击可交互的图层,显示相关信息
|
||||||
|
await setHighlight(`${sourceId}-${feature}`, sourceId, mapId);
|
||||||
|
changeCursor(`${sourceId}-${feature}`, mapId);
|
||||||
|
|
||||||
|
if (layerId.includes("sample_line"))
|
||||||
|
await showSampleLineInfo(`${sourceId}-${feature}`, mapId);
|
||||||
|
if (layerId.includes("sample_station"))
|
||||||
|
await showSampleStationInfo(`${sourceId}-${feature}`, mapId);
|
||||||
|
else
|
||||||
|
await showTrackInfo(`${sourceId}-${feature}`, mapId);
|
||||||
|
|
||||||
|
return sourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setHighlight(layerId, sourceId, mapId = "home") {
|
||||||
|
let hoveredLineId = null;
|
||||||
|
let clickedLineId = null;
|
||||||
|
Vue.config.maps[mapId].on('click', layerId, (e) => {
|
||||||
|
if (!e.features || e.features.length <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// // 查询点击点周围的所有要素(使用边界框)
|
||||||
|
// const bbox = [
|
||||||
|
// [e.point.x - 10, e.point.y - 10], // 左上
|
||||||
|
// [e.point.x + 10, e.point.y + 10] // 右下
|
||||||
|
// ];
|
||||||
|
// // 查询所有要素
|
||||||
|
// const allFeatures = Vue.config.maps[mapId].queryRenderedFeatures(bbox);
|
||||||
|
// console.log("allFeatures: ", allFeatures);
|
||||||
|
// // // 按图层分组
|
||||||
|
// // const featuresByLayer = groupFeaturesByLayer(allFeatures);
|
||||||
|
// // console.log('点击位置附近的所有要素:', featuresByLayer);
|
||||||
|
|
||||||
|
// console.log(e.features.length, e.features);
|
||||||
|
// console.log("1 e: ", e);
|
||||||
|
|
||||||
|
let feature = e.features[0];
|
||||||
|
if (e.features.length > 1 && e.options)
|
||||||
|
feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0];
|
||||||
|
|
||||||
|
sourceLayer = feature.sourceLayer;
|
||||||
|
// 获取当前单击的线的选择状态
|
||||||
|
let state = Vue.config.maps[mapId].getFeatureState({
|
||||||
|
source: sourceId,
|
||||||
|
sourceLayer: sourceLayer,
|
||||||
|
id: feature.id
|
||||||
|
});
|
||||||
|
if (clickedLineId) {
|
||||||
|
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: false });
|
||||||
|
}
|
||||||
|
clickedLineId = feature.id;
|
||||||
|
if (state.click === true) {
|
||||||
|
// 再次单击时取消选择
|
||||||
|
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: false });
|
||||||
|
// 隐藏信息框
|
||||||
|
// document.getElementById("cable-overlay")!.style.display = 'none';
|
||||||
|
// 过滤掉当前单击的线
|
||||||
|
clickedLineIds = clickedLineIds.filter((item) => item !== clickedLineId);
|
||||||
|
} else {
|
||||||
|
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: true });
|
||||||
|
// 显示信息框
|
||||||
|
// if (sourceId === 'GMSL Cable Data')
|
||||||
|
// document.getElementById("cable-overlay")!.style.display = 'block';
|
||||||
|
// 单击时加入选择列表(用于单击 ESC 键时取消选择状态)
|
||||||
|
clickedLineIds.push(clickedLineId);
|
||||||
|
// document.removeEventListener("keydown", removeHighlight, true);
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
removeHighlight(e, sourceId);
|
||||||
|
}, { once: true }); // once:一个布尔值,表示 listener 在添加之后最多只调用一次。如果为 true,listener 会在其被调用之后自动移除。
|
||||||
|
// }, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the user moves their mouse over the state-fill layer, we'll update the feature state for the feature under the mouse.
|
||||||
|
Vue.config.maps[mapId].on('mousemove', layerId, (e) => {
|
||||||
|
if (e.features.length <= 0)
|
||||||
|
return;
|
||||||
|
sourceLayer = e.features[0].sourceLayer;
|
||||||
|
if (hoveredLineId) {
|
||||||
|
Vue.config.maps[mapId].setFeatureState(
|
||||||
|
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
|
||||||
|
{ hover: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
hoveredLineId = e.features[0].id;
|
||||||
|
Vue.config.maps[mapId].setFeatureState(
|
||||||
|
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
|
||||||
|
{ hover: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the mouse leaves the state-fill layer, update the feature state of the previously hovered feature.
|
||||||
|
Vue.config.maps[mapId].on('mouseleave', layerId, () => {
|
||||||
|
if (hoveredLineId && sourceLayer) {
|
||||||
|
Vue.config.maps[mapId].setFeatureState(
|
||||||
|
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
|
||||||
|
{ hover: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
hoveredLineId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeCursor(layerId, mapId = "home", cursor = "pointer") {
|
||||||
|
Vue.config.maps[mapId].on("mouseenter", layerId, () => {
|
||||||
|
Vue.config.maps[mapId].getCanvas().style.cursor = cursor;
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.config.maps[mapId].on("mouseleave", layerId, () => {
|
||||||
|
Vue.config.maps[mapId].getCanvas().style.cursor = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showSampleLineInfo(layerId, mapId = "home") {
|
||||||
|
Vue.config.maps[mapId].on("click", layerId, async (e) => {
|
||||||
|
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
|
||||||
|
if (e.defaultPrevented)
|
||||||
|
return;
|
||||||
|
// 标记该 event 已触发,禁止点击事件穿透
|
||||||
|
if (typeof e.preventDefault === "function")
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const description = `
|
||||||
|
<table class="popup-table" style="min-width:380px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">测线信息</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>数据集名称: <b>${e.features[0].properties.dataset_name}</b></td>
|
||||||
|
<td>数据样品编号: <b>${e.features[0].properties.sample_name}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>赞助机构: <b>${e.features[0].properties.funding_org}</b></td>
|
||||||
|
<td>公开期限: <b>${e.features[0].properties.publish_rule}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>开始经度: <b>${e.features[0].properties.start_lon}°</b></td>
|
||||||
|
<td>结束经度: <b>${e.features[0].properties.end_lon}°</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>开始纬度: <b>${e.features[0].properties.start_lat}°</b></td>
|
||||||
|
<td>结束纬度: <b>${e.features[0].properties.end_lat}°</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>开始时间: <b>${e.features[0].properties.start_time}</b></td>
|
||||||
|
<td>结束时间: <b>${e.features[0].properties.end_time}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>测线深度: <b>${e.features[0].properties.line_deep}m</b></td>
|
||||||
|
<td>数据状态: <b>${e.features[0].properties.sample_status}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">备注信息: <b>${e.features[0].properties.sample_remark}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
showPopupDetails(mapId, e.lngLat, description, "450px");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showSampleStationInfo(layerId, mapId = "home") {
|
||||||
|
Vue.config.maps[mapId].on("click", layerId, async (e) => {
|
||||||
|
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
|
||||||
|
if (e.defaultPrevented)
|
||||||
|
return;
|
||||||
|
// 标记该 event 已触发,禁止点击事件穿透
|
||||||
|
// if (typeof e.preventDefault === "function")
|
||||||
|
// e.preventDefault();
|
||||||
|
|
||||||
|
// console.log(e.features.length, e.features);
|
||||||
|
// console.log("2 e: ", e);
|
||||||
|
|
||||||
|
let feature = e.features[0];
|
||||||
|
if (e.features.length > 1)
|
||||||
|
feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0];
|
||||||
|
|
||||||
|
const description = `
|
||||||
|
<table class="popup-table" style="min-width:380px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">站位信息</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>数据集名称: <b>${feature.properties.dataset_name}</b></td>
|
||||||
|
<td>数据样品编号: <b>${feature.properties.sample_name}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>赞助机构: <b>${feature.properties.funding_org}</b></td>
|
||||||
|
<td>公开期限: <b>${feature.properties.publish_rule}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>站位经度: <b>${feature.properties.sampling_lon}°</b></td>
|
||||||
|
<td>站位纬度: <b>${feature.properties.sampling_lat}°</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>采样时间: <b>${feature.properties.sampling_time}</b></td>
|
||||||
|
<td>采样深度: <b>${feature.properties.sampling_deep}m</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">备注信息: <b>${feature.properties.sample_remark}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
showPopupDetails(mapId, feature.geometry.coordinates, description, "450px");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showTrackInfo(layerId, mapId = "home") {
|
||||||
|
Vue.config.maps[mapId].on("click", layerId, async (e) => {
|
||||||
|
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
|
||||||
|
if (e.defaultPrevented)
|
||||||
|
return;
|
||||||
|
// 标记该 event 已触发,禁止点击事件穿透
|
||||||
|
if (typeof e.preventDefault === "function")
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let coordinate = e.lngLat;
|
||||||
|
let match = layerId.match(/vector-(.*)-[(symbol)|(line)]/);
|
||||||
|
if (!match)
|
||||||
|
return;
|
||||||
|
const voyageId = match[1];
|
||||||
|
regInfo({ voyage_name: voyageId }).then(res => {
|
||||||
|
const info = res.map.voyage;
|
||||||
|
const description = `
|
||||||
|
<table class="popup-table" style="min-width:380px;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><a style="color:#0000FF" target="_balnk" title="点击查看航次详情" href="${Vue.config.baseUrl}/ds/#/container/dataDetail?voyage_name=${info.voyage_name}">航次信息</a></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td>航次编号: <b>${info.voyage_name}</b></td>
|
||||||
|
<td>航次首席: <b>${info.voyage_officer}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>科考船舶: <b>${info.ship_name}</b></td>
|
||||||
|
<td>资助机构: <b>${info.funding_org}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>离港日期: <b>${info.voyage_start_date}</b></td>
|
||||||
|
<td>返港日期: <b>${info.voyage_end_date}</b></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">航次概况: <b>${info.voyage_remark}</b></td>
|
||||||
|
</tr>
|
||||||
|
</table>`;
|
||||||
|
|
||||||
|
showPopupDetails(mapId, coordinate, description, "420px");
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showPopupDetails(mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}) {
|
||||||
|
const map = Vue.config.maps[mapId];
|
||||||
|
if (!map)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const popupOptions = Object.assign({ offset: {}, className: "my-class", closeButton: closeButton, closeOnClick: closeOnClick }, options);
|
||||||
|
|
||||||
|
// 如果已有共享 popup,复用并更新位置/内容;否则创建新的并绑定 close 事件以便释放引用
|
||||||
|
if (sharedPopup) {
|
||||||
|
try {
|
||||||
|
sharedPopup.setLngLat(coordinate)
|
||||||
|
.setHTML(description)
|
||||||
|
.setOffset(10)
|
||||||
|
.setMaxWidth(maxWidth);
|
||||||
|
} catch (err) {
|
||||||
|
try {
|
||||||
|
// 万一现有 popup 状态异常,移除并重新创建
|
||||||
|
sharedPopup.remove();
|
||||||
|
sharedPopup = null;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("移除异常 popup 失败:", e);
|
||||||
|
}
|
||||||
|
sharedPopup = new window.mapboxgl.Popup(popupOptions)
|
||||||
|
.setLngLat(coordinate)
|
||||||
|
.setHTML(description)
|
||||||
|
.setOffset(10)
|
||||||
|
.setMaxWidth(maxWidth)
|
||||||
|
.addTo(map);
|
||||||
|
sharedPopup.on('close', () => { sharedPopup = null; });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sharedPopup = new window.mapboxgl.Popup(popupOptions)
|
||||||
|
.setLngLat(coordinate)
|
||||||
|
.setHTML(description)
|
||||||
|
.setOffset(10)
|
||||||
|
.setMaxWidth(maxWidth)
|
||||||
|
.addTo(map);
|
||||||
|
sharedPopup.on('close', () => { sharedPopup = null; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadJsonLineFeature(layerId, color, geojson, mapId) {
|
||||||
|
// console.log("layerId: ",layerId);
|
||||||
|
|
||||||
|
let source = Vue.config.maps[mapId].getSource(layerId);
|
||||||
|
if (!source) {
|
||||||
|
Vue.config.maps[mapId].addSource(layerId, {
|
||||||
|
"type": "geojson",
|
||||||
|
"data": geojson
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
source.setData(geojson);
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = Vue.config.maps[mapId].getLayer(layerId);
|
||||||
|
if (layer)
|
||||||
|
Vue.config.maps[mapId].setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
else {
|
||||||
|
Vue.config.maps[mapId].addLayer({
|
||||||
|
'id': layerId,
|
||||||
|
'type': 'line',
|
||||||
|
'source': layerId,
|
||||||
|
'layout': {
|
||||||
|
'visibility': 'visible',
|
||||||
|
'line-join': 'round',
|
||||||
|
'line-cap': 'round'
|
||||||
|
},
|
||||||
|
'paint': {
|
||||||
|
'line-color': color,
|
||||||
|
'line-width': 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击可交互的图层,显示相关信息
|
||||||
|
await setHighlight(layerId, layerId, mapId);
|
||||||
|
changeCursor(layerId, mapId);
|
||||||
|
// await showTrackInfo(`${sourceId}-${feature}`, mapId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadJsonPointFeature(layerId, color, geojson, mapId) {
|
||||||
|
// console.log("layerId: ",layerId);
|
||||||
|
|
||||||
|
let source = Vue.config.maps[mapId].getSource(layerId);
|
||||||
|
if (!source) {
|
||||||
|
Vue.config.maps[mapId].addSource(layerId, {
|
||||||
|
"type": "geojson",
|
||||||
|
"data": geojson
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
source.setData(geojson);
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = Vue.config.maps[mapId].getLayer(layerId);
|
||||||
|
if (layer)
|
||||||
|
Vue.config.maps[mapId].setLayoutProperty(layerId, "visibility", "visible");
|
||||||
|
else {
|
||||||
|
Vue.config.maps[mapId].addLayer({
|
||||||
|
'id': layerId,
|
||||||
|
'type': 'symbol',
|
||||||
|
'source': layerId,
|
||||||
|
'layout': {
|
||||||
|
'icon-image': 'dot.png',
|
||||||
|
'icon-size': 0.5,
|
||||||
|
// get the title name from the source's "title" property
|
||||||
|
'text-field': ['get', 'sample_name'],
|
||||||
|
'text-font': [
|
||||||
|
'Open Sans Semibold',
|
||||||
|
'Arial Unicode MS Bold'
|
||||||
|
],
|
||||||
|
"text-size": 10,
|
||||||
|
'text-offset': [0, 1.25],
|
||||||
|
'text-anchor': 'top',
|
||||||
|
'visibility': 'visible'
|
||||||
|
},
|
||||||
|
'paint': {
|
||||||
|
"text-color": color,
|
||||||
|
"text-halo-color": "#FFFFFF",
|
||||||
|
"text-halo-width": 0.1,
|
||||||
|
"icon-opacity": 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击可交互的图层,显示相关信息
|
||||||
|
await setHighlight(layerId, layerId, mapId);
|
||||||
|
changeCursor(layerId, mapId);
|
||||||
|
// await showTrackInfo(`${sourceId}-${feature}`, mapId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function highlightFeaturesByProperty(mapId, sourceId, layerId, propertyName = "sample_name", targetValue = "TARGET_VALUE") {
|
||||||
|
// 清空所有高亮状态(可选)
|
||||||
|
Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => {
|
||||||
|
console.log("AAAAA: ", f);
|
||||||
|
Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 标记匹配要素
|
||||||
|
Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(f => {
|
||||||
|
console.log("BBBBB: ", f);
|
||||||
|
if (f.properties && f.properties[propertyName] === targetValue) {
|
||||||
|
Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图层 paint 使用 feature-state
|
||||||
|
Vue.config.maps[mapId].setPaintProperty(layerId, 'text-color', [
|
||||||
|
'case',
|
||||||
|
['boolean', ['feature-state', 'highlight'], false],
|
||||||
|
'#FFFFFF', // 高亮颜色
|
||||||
|
"#FF0000"// 默认颜色
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
@ -26,21 +26,25 @@
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
<div class="left4" v-if="radio == '数据样品集'">
|
<div class="left4" v-if="radio == '数据样品集'">
|
||||||
<div v-for="(item, index) in dataList" :key="index" :id="item.tsy_id" class="left4_1" >
|
<div v-for="(item, index) in dataList" :key="index" :id="item.tsy_id" class="left4_1">
|
||||||
<div class="left4_top">
|
<div class="left4_top">
|
||||||
<p class="rule-text">编号规则:航次号_潜次号_设备类型_数据样品类型</p>
|
<p class="rule-text">编号规则:航次号_潜次号_设备类型_数据样品类型</p>
|
||||||
<p @click="openList(item, 'list')" style="font-weight: 400;font-size: 14px;cursor: pointer;color: #409eff" >{{ item.dataset_name }} <span class="mileage">{{ item.file_total }}</span></p>
|
<p @click="openList(item, 'list')"
|
||||||
|
style="font-weight: 400;font-size: 14px;cursor: pointer;color: #409eff">{{ item.dataset_name }} <span
|
||||||
|
class="mileage">{{ item.file_total }}</span></p>
|
||||||
<span class="org">{{ item.create_time }}</span>
|
<span class="org">{{ item.create_time }}</span>
|
||||||
</div>
|
</div>
|
||||||
<ul v-show="currenId == item.tsy_id && fold">
|
<ul v-show="currenId == item.tsy_id && fold">
|
||||||
<li :class="sample.tsy_id == currenSample ? 'bg-color' : ''" class="sample" :id="sample.tsy_id"
|
<li :class="sample.tsy_id == currenSample ? 'bg-color' : ''" class="sample" :id="sample.tsy_id"
|
||||||
v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample)">{{ sample.sample_name }}
|
v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample, item.job_type)">{{
|
||||||
|
sample.sample_name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="left4_list">
|
<div class="left4_list">
|
||||||
<div class="list1">
|
<div class="list1">
|
||||||
<p>航次:</p>
|
<p>航次:</p>
|
||||||
<span class="hover-color" style="color: #409eff" @click="turnDeatilPage(item)">{{ item.voyage_name }}</span>
|
<span class="hover-color" style="color: #409eff" @click="turnDeatilPage(item)">{{ item.voyage_name
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list1">
|
<div class="list1">
|
||||||
<p>平台类型:</p>
|
<p>平台类型:</p>
|
||||||
|
|
@ -82,7 +86,8 @@
|
||||||
<div class="left4_top">
|
<div class="left4_top">
|
||||||
<p :style="currentVoyage === item.tsy_id ? 'color: #409eff' : ''">
|
<p :style="currentVoyage === item.tsy_id ? 'color: #409eff' : ''">
|
||||||
<span style="color: #409eff" @click="turnDeatilPage(item)">{{ item.voyage_name }}</span>
|
<span style="color: #409eff" @click="turnDeatilPage(item)">{{ item.voyage_name }}</span>
|
||||||
<span class="mileage">{{ item.voyage_mileage }}海里</span></p>
|
<span class="mileage">{{ item.voyage_mileage }}海里</span>
|
||||||
|
</p>
|
||||||
<span class="org">{{ item.funding_org }}</span>
|
<span class="org">{{ item.funding_org }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="left4_list">
|
<div class="left4_list">
|
||||||
|
|
@ -115,11 +120,15 @@
|
||||||
<span>{{ item.serect_status == 'Active' ? '是' : '否' }}</span>
|
<span>{{ item.serect_status == 'Active' ? '是' : '否' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<el-button v-if="!item.highlight" icon="el-icon-s-opportunity" circle @click="addHighlightLayer(item.voyage_name, index)" />
|
<el-button v-if="!item.highlight" icon="el-icon-s-opportunity" circle
|
||||||
<el-button v-if="item.highlight" type="primary" icon="el-icon-s-opportunity" circle @click="clearHighlight" />
|
@click="addHighlightLayer(item.voyage_name, index)" />
|
||||||
<el-button v-if="!item.show" icon="el-icon-view" circle @click="addSingleLineImg(item.voyage_name, index)" />
|
<el-button v-if="item.highlight" type="primary" icon="el-icon-s-opportunity" circle
|
||||||
<el-button v-if="item.show" type="primary" icon="el-icon-view" circle @click="removeSingleLineImg(item.voyage_name, index)" />
|
@click="clearHighlight(item.voyage_name, index)" />
|
||||||
<el-button icon="el-icon-location-information" circle />
|
<el-button v-if="!item.show" icon="el-icon-view" circle
|
||||||
|
@click="addSingleLineImg(item.voyage_name, index)" />
|
||||||
|
<el-button v-if="item.show" type="primary" icon="el-icon-view" circle
|
||||||
|
@click="removeSingleLineImg(item.voyage_name, index)" />
|
||||||
|
<!-- <el-button icon="el-icon-location-information" circle /> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,58 +148,46 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 航次名称弹窗-->
|
<!-- 航次名称弹窗-->
|
||||||
<el-dialog
|
<el-dialog title="请选择航次名称" :visible.sync="dialog1" width="1000px">
|
||||||
title="请选择航次名称"
|
<el-table ref="tableData1" border highlight-current-row @current-change="handleCurrentChange1" max-height="480"
|
||||||
:visible.sync="dialog1"
|
:data="tableData1" :header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
|
||||||
width="1000px"
|
:cell-style="{ textAlign: 'center' }">
|
||||||
>
|
<el-table-column type="index" label="#" width="50" />
|
||||||
<el-table ref="tableData1" border highlight-current-row @current-change="handleCurrentChange1" max-height="480" :data="tableData1" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
|
||||||
<el-table-column type="index" label="#" width="50"/>
|
|
||||||
<el-table-column prop="name" label="航次名称" />
|
<el-table-column prop="name" label="航次名称" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 平台类型弹窗-->
|
<!-- 平台类型弹窗-->
|
||||||
<el-dialog
|
<el-dialog title="请选择平台类型" :visible.sync="dialog2" width="1000px">
|
||||||
title="请选择平台类型"
|
<el-table ref="tableData2" border @current-change="handleCurrentChange2" :data="tableData2" max-height="480"
|
||||||
:visible.sync="dialog2"
|
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
|
||||||
width="1000px"
|
:cell-style="{ textAlign: 'center' }">
|
||||||
>
|
<el-table-column type="index" label="#" width="50" />
|
||||||
<el-table ref="tableData2" border @current-change="handleCurrentChange2" :data="tableData2" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
|
||||||
<el-table-column type="index" label="#" width="50"/>
|
|
||||||
<el-table-column prop="name" label="平台类型名称" />
|
<el-table-column prop="name" label="平台类型名称" />
|
||||||
<el-table-column prop="value" label="英文类型名称" />
|
<el-table-column prop="value" label="英文类型名称" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 设备类型弹窗-->
|
<!-- 设备类型弹窗-->
|
||||||
<el-dialog
|
<el-dialog title="请选择设备类型" :visible.sync="dialog3" width="1000px">
|
||||||
title="请选择设备类型"
|
<el-table ref="tableData3" border @current-change="handleCurrentChange3" :data="tableData3" max-height="480"
|
||||||
:visible.sync="dialog3"
|
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
|
||||||
width="1000px"
|
:cell-style="{ textAlign: 'center' }">
|
||||||
>
|
<el-table-column type="index" label="#" width="50" />
|
||||||
<el-table ref="tableData3" border @current-change="handleCurrentChange3" :data="tableData3" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
|
||||||
<el-table-column type="index" label="#" width="50"/>
|
|
||||||
<el-table-column prop="name" label="设备类型名称" />
|
<el-table-column prop="name" label="设备类型名称" />
|
||||||
<el-table-column prop="value" label="英文名称" />
|
<el-table-column prop="value" label="英文名称" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 数据样品类型弹窗-->
|
<!-- 数据样品类型弹窗-->
|
||||||
<el-dialog
|
<el-dialog title="请选择数据样品类型" :visible.sync="dialog4" width="1000px">
|
||||||
title="请选择数据样品类型"
|
<el-table ref="tableData4" border @current-change="handleCurrentChange4" :data="tableData4" max-height="480"
|
||||||
:visible.sync="dialog4"
|
:header-cell-style="{ background: '#dddfe6', color: '#555960', textAlign: 'center' }"
|
||||||
width="1000px"
|
:cell-style="{ textAlign: 'center' }">
|
||||||
>
|
<el-table-column type="index" label="#" width="50" />
|
||||||
<el-table ref="tableData4" border @current-change="handleCurrentChange4" :data="tableData4" max-height="480" :header-cell-style="{ background: '#dddfe6',color: '#555960',textAlign: 'center' }" :cell-style="{ textAlign: 'center' }">
|
|
||||||
<el-table-column type="index" label="#" width="50"/>
|
|
||||||
<el-table-column prop="name" label="数据样品类型类型名称" />
|
<el-table-column prop="name" label="数据样品类型类型名称" />
|
||||||
<el-table-column prop="value" label="英文名称" />
|
<el-table-column prop="value" label="英文名称" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<!-- 航次信息弹窗-->
|
<!-- 航次信息弹窗-->
|
||||||
<el-dialog
|
<el-dialog title="航次信息" :visible.sync="dialog5" width="800px">
|
||||||
title="航次信息"
|
|
||||||
:visible.sync="dialog5"
|
|
||||||
width="800px"
|
|
||||||
>
|
|
||||||
<div class="detail_b">
|
<div class="detail_b">
|
||||||
<h3 style="padding-bottom: 10px;">{{ voyageInfo.voyage_name }}</h3>
|
<h3 style="padding-bottom: 10px;">{{ voyageInfo.voyage_name }}</h3>
|
||||||
<p>{{ voyageInfo.voyage_remark }}</p>
|
<p>{{ voyageInfo.voyage_remark }}</p>
|
||||||
|
|
@ -240,6 +237,10 @@ import Cookies from 'js-cookie'
|
||||||
import {
|
import {
|
||||||
setImageryViewModels
|
setImageryViewModels
|
||||||
} from '@/utils/common'
|
} from '@/utils/common'
|
||||||
|
import Vue from 'vue'
|
||||||
|
import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
|
||||||
|
import { ColorGenerator } from '@/utils/color-generator';
|
||||||
|
import { handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
|
||||||
|
|
||||||
const Cesium = window.Cesium
|
const Cesium = window.Cesium
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -248,6 +249,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
show: false,
|
show: false,
|
||||||
viewer: null,
|
viewer: null,
|
||||||
|
filter: null,
|
||||||
color: [Cesium.Color.RED, Cesium.Color.GREEN, Cesium.Color.BLUE, Cesium.Color.CHARTREUSE, Cesium.Color.DARKBLUE],
|
color: [Cesium.Color.RED, Cesium.Color.GREEN, Cesium.Color.BLUE, Cesium.Color.CHARTREUSE, Cesium.Color.DARKBLUE],
|
||||||
searchType: [
|
searchType: [
|
||||||
{
|
{
|
||||||
|
|
@ -362,7 +364,8 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
|
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
|
||||||
this.initCesium()
|
this.initMapbox()
|
||||||
|
// this.initCesium()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取菜单
|
// 获取菜单
|
||||||
|
|
@ -452,21 +455,35 @@ export default {
|
||||||
const arr = res.page.records
|
const arr = res.page.records
|
||||||
if (this.radio === '航次') {
|
if (this.radio === '航次') {
|
||||||
if (arr.length) {
|
if (arr.length) {
|
||||||
|
let i = 0;
|
||||||
|
const colors = ColorGenerator.generateDivergingColors(arr.length);
|
||||||
arr.forEach(item => {
|
arr.forEach(item => {
|
||||||
item.highlight = false
|
item.highlight = false
|
||||||
item.show = true
|
item.show = true
|
||||||
this.addSingleLineImg(item.voyage_name, null)
|
// this.addSingleLineImg(item.voyage_name, null)
|
||||||
|
// 加载轨迹图层
|
||||||
|
loadVectorLayer(item.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "dataSearch", "dsds");
|
||||||
|
i++;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.dataList = arr
|
this.dataList = arr
|
||||||
}
|
}
|
||||||
if (this.radio === '数据样品集') {
|
if (this.radio === '数据样品集') {
|
||||||
|
try {
|
||||||
this.getDataPoints(arr)
|
this.getDataPoints(arr)
|
||||||
}
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 添加高亮图层
|
// 添加高亮图层
|
||||||
addHighlightLayer(voyageName, i) {
|
addHighlightLayer(voyageName, i) {
|
||||||
|
Vue.config.maps["dataSearch"].setPaintProperty(`vector-${voyageName}-line`, "line-width", 4);
|
||||||
|
this.dataList[i].highlight = true;
|
||||||
|
return;
|
||||||
|
|
||||||
this.clearHighlight()
|
this.clearHighlight()
|
||||||
const highlightLayer = new Cesium.WebMapTileServiceImageryProvider({
|
const highlightLayer = new Cesium.WebMapTileServiceImageryProvider({
|
||||||
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}-highlight/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
|
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}-highlight/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
|
||||||
|
|
@ -481,8 +498,20 @@ export default {
|
||||||
this.dataList[i].highlight = true;
|
this.dataList[i].highlight = true;
|
||||||
},
|
},
|
||||||
// 清除高亮
|
// 清除高亮
|
||||||
clearHighlight() {
|
clearHighlight(voyageName, i) {
|
||||||
this.dataList.map(item => {item.highlight = false})
|
let lineWidthExpression = [
|
||||||
|
"case",
|
||||||
|
["boolean", ["feature-state", "hover"], false],
|
||||||
|
4,
|
||||||
|
["boolean", ["feature-state", "click"], false],
|
||||||
|
2,
|
||||||
|
1.8
|
||||||
|
];
|
||||||
|
Vue.config.maps["dataSearch"].setPaintProperty(`vector-${voyageName}-line`, "line-width", lineWidthExpression);
|
||||||
|
this.dataList[i].highlight = false;
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.dataList.map(item => { item.highlight = false })
|
||||||
// 移除地图上的高亮图层
|
// 移除地图上的高亮图层
|
||||||
if (this.highlightLayer) {
|
if (this.highlightLayer) {
|
||||||
this.viewer.imageryLayers.remove(this.highlightLayer);
|
this.viewer.imageryLayers.remove(this.highlightLayer);
|
||||||
|
|
@ -491,7 +520,13 @@ export default {
|
||||||
},
|
},
|
||||||
// 显示
|
// 显示
|
||||||
addSingleLineImg(voyageName, i) {
|
addSingleLineImg(voyageName, i) {
|
||||||
|
if (i !== null) {
|
||||||
|
this.dataList[i].show = true;
|
||||||
|
}
|
||||||
|
// 显示图层
|
||||||
|
Vue.config.maps["dataSearch"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "visible");
|
||||||
|
Vue.config.maps["dataSearch"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "visible");
|
||||||
|
return;
|
||||||
// 检查该航次图层是否已存在,避免重复添加
|
// 检查该航次图层是否已存在,避免重复添加
|
||||||
if (this.trackLayerMap[voyageName]) {
|
if (this.trackLayerMap[voyageName]) {
|
||||||
console.warn(`航次 ${voyageName} 的轨迹图层已存在`);
|
console.warn(`航次 ${voyageName} 的轨迹图层已存在`);
|
||||||
|
|
@ -518,6 +553,16 @@ export default {
|
||||||
|
|
||||||
// 隐藏
|
// 隐藏
|
||||||
removeSingleLineImg(voyageName, i) {
|
removeSingleLineImg(voyageName, i) {
|
||||||
|
if (i !== null) {
|
||||||
|
this.dataList[i].show = false;
|
||||||
|
}
|
||||||
|
// 隐藏图层
|
||||||
|
// let visibility = Vue.config.maps["dataSearch"].getLayoutProperty(`vector-${voyageName}-symbol`, "visibility");
|
||||||
|
// visibility = visibility == "visible" ? "none" : "visible";
|
||||||
|
Vue.config.maps["dataSearch"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "none");
|
||||||
|
Vue.config.maps["dataSearch"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "none");
|
||||||
|
return;
|
||||||
|
|
||||||
const layerInstance = this.trackLayerMap[voyageName];
|
const layerInstance = this.trackLayerMap[voyageName];
|
||||||
if (layerInstance) {
|
if (layerInstance) {
|
||||||
// 从Cesium的imageryLayers中移除该图层
|
// 从Cesium的imageryLayers中移除该图层
|
||||||
|
|
@ -581,7 +626,7 @@ export default {
|
||||||
},
|
},
|
||||||
// 清除所有轨迹图层
|
// 清除所有轨迹图层
|
||||||
clearAll() {
|
clearAll() {
|
||||||
this.clearHighlight()
|
// this.clearHighlight()
|
||||||
if (this.trackLayerMap.length) {
|
if (this.trackLayerMap.length) {
|
||||||
this.trackLayerMap.forEach(layer => {
|
this.trackLayerMap.forEach(layer => {
|
||||||
this.viewer.imageryLayers.remove(layer);
|
this.viewer.imageryLayers.remove(layer);
|
||||||
|
|
@ -597,9 +642,20 @@ export default {
|
||||||
// 获取数据样品集坐标点位
|
// 获取数据样品集坐标点位
|
||||||
getDataPoints(arr) {
|
getDataPoints(arr) {
|
||||||
if (!arr.length) return false
|
if (!arr.length) return false
|
||||||
|
let i = 0;
|
||||||
|
const colors = ColorGenerator.generateDivergingColors(arr.length);
|
||||||
arr.forEach(item => {
|
arr.forEach(item => {
|
||||||
this.addSingleLineImg(item.voyage_name, null)
|
// 添加测线/站位图层
|
||||||
this.divingImage(item)
|
if (item.job_type == "测线作业") {
|
||||||
|
loadVectorLayer(`sample_line_${item.dataset_name}`, ["line"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearch", "dsds", "sample_name");
|
||||||
|
} else {
|
||||||
|
loadVectorLayer(`sample_station_${item.dataset_name}`, ["symbol"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearch", "dsds", "sample_name");
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
|
||||||
|
// this.addSingleLineImg(item.voyage_name, null)
|
||||||
|
// this.dataList[i].show = true;
|
||||||
|
// this.divingImage(item)
|
||||||
})
|
})
|
||||||
const voyageList = arr
|
const voyageList = arr
|
||||||
voyageList.map(item => {
|
voyageList.map(item => {
|
||||||
|
|
@ -609,11 +665,11 @@ export default {
|
||||||
dataset_id: item.tsy_id
|
dataset_id: item.tsy_id
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
item.sampleArray = res.array
|
item.sampleArray = res.array
|
||||||
if (res.array.length) {
|
// if (res.array.length) {
|
||||||
res.array.forEach(sample => {
|
// res.array.forEach(sample => {
|
||||||
this.sampleLineImage(sample)
|
// this.sampleLineImage(sample)
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (item.job_type === '站位作业') {
|
if (item.job_type === '站位作业') {
|
||||||
|
|
@ -623,16 +679,105 @@ export default {
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
item.sampleArray = res.array
|
item.sampleArray = res.array
|
||||||
// 格式化坐标点
|
// 格式化坐标点
|
||||||
if (res.array.length) {
|
// if (res.array.length) {
|
||||||
res.array.forEach(sample => {
|
// res.array.forEach(sample => {
|
||||||
this.sampleLineImage(sample)
|
// this.sampleLineImage(sample)
|
||||||
})
|
// });
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.dataList = voyageList
|
this.dataList = voyageList
|
||||||
console.log(this.dataList)
|
},
|
||||||
|
|
||||||
|
// 初始化 mapbox
|
||||||
|
initMapbox() {
|
||||||
|
const map = new window.mapboxgl.Map({
|
||||||
|
container: 'cesiumContainerData',
|
||||||
|
// style: 'mapbox://styles/mapbox/standard',
|
||||||
|
style: {
|
||||||
|
"version": 8,
|
||||||
|
"name": "default_style",
|
||||||
|
// mapbox地图使用的图标
|
||||||
|
"sprite": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/sprite`,
|
||||||
|
// 使用官方标注字体(需联网)
|
||||||
|
"glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
|
||||||
|
// 自部署标注字体(无需联网),glyphs 前有两个斜杠时,可能导致404
|
||||||
|
// "glyphs": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/glyphs/{fontstack}/{range}.pbf`,
|
||||||
|
"sources": {
|
||||||
|
"影像地图": {
|
||||||
|
"type": "raster",
|
||||||
|
"tiles": [`${Vue.config.baseUrl}/geoserver/gwc/service/wmts/rest/ougp:world/EPSG:900913/EPSG:900913:{z}/{y}/{x}?format=image/jpeg`],
|
||||||
|
"tileSize": 256,
|
||||||
|
// "minzoom": 0,
|
||||||
|
// "maxzoom": baseMapMaxZoom
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"id": "影像地图",
|
||||||
|
"type": "raster",
|
||||||
|
"source": "影像地图",
|
||||||
|
"layout": { visibility: "visible" },
|
||||||
|
// "minzoom": 0,
|
||||||
|
// "maxzoom": baseMapMaxZoom
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
basemap: {
|
||||||
|
theme: 'monochrome',
|
||||||
|
lightPreset: 'night'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zoom: 5,
|
||||||
|
center: [113, 15.5],
|
||||||
|
minZoom: 2,
|
||||||
|
maxZoom: 29,
|
||||||
|
trackResize: true,
|
||||||
|
scrollZoom: false, // 禁用滚轮缩放
|
||||||
|
// 地图默认字体
|
||||||
|
localIdeographFontFamily: "Microsoft YoHei, 'Noto Sans', 'Noto Sans CJK SC', sans-serif",
|
||||||
|
pitch: 0,
|
||||||
|
bearing: 0,
|
||||||
|
// // 禁用所有旋转和俯仰角控制
|
||||||
|
// dragRotate: false,
|
||||||
|
// touchZoomRotate: false,
|
||||||
|
// pitchWithRotate: false,
|
||||||
|
// maxBounds: [[-180, -90], [180, 90]],// Set the map's geographical boundaries.
|
||||||
|
antialias: true,
|
||||||
|
attributionControl: false,
|
||||||
|
boxZoom: true,//如果为true,则启用了“缩放框”交互,按住shift并在地图上拖框放大
|
||||||
|
preserveDrawingBuffer: false,//如果为true,地图的canvas可以使用导出到png,通过map.getCanvas().toDataURL()。默认为false,会提高地图性能。
|
||||||
|
projection: 'equirectangular', // 设置投影方式,经纬度直投(解决缩放级别大于26时瓦片消失的问题)
|
||||||
|
});
|
||||||
|
Vue.config.maps["dataSearch"] = map;
|
||||||
|
|
||||||
|
// 确保窗口大小改变时,地图能正确铺满容器
|
||||||
|
window.addEventListener('resize', (event) => {
|
||||||
|
map.resize();
|
||||||
|
// // maps.forEach((key: string, value: any) => {
|
||||||
|
// // console.log(`${key}: ${value}`);
|
||||||
|
// // });
|
||||||
|
// for (let [key, value] of Object.entries(maps)) {
|
||||||
|
// // value.resize();
|
||||||
|
// }
|
||||||
|
// Object.values(maps).forEach((map) => {
|
||||||
|
// map.resize();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
|
||||||
|
// handleWheel("dataSearch");
|
||||||
|
Vue.config.maps["dataSearch"].scrollZoom.enable();
|
||||||
|
initSprites("dataSearch")
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
// 加载海岸线
|
||||||
|
// loadVectorLayer("GSHHS_f_L1", ["line"], ["#5c71c3"], "visible", "dataSearch", 'ougp');
|
||||||
|
// 加载等深线
|
||||||
|
// loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "visible", "dataSearch", 'ougp');
|
||||||
|
// loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "visible", "dataSearch", 'ougp');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 初始化cesium
|
// 初始化cesium
|
||||||
|
|
@ -711,7 +856,7 @@ export default {
|
||||||
// 增加6000米等深线
|
// 增加6000米等深线
|
||||||
const contour6000 = new Cesium.WebMapTileServiceImageryProvider(
|
const contour6000 = new Cesium.WebMapTileServiceImageryProvider(
|
||||||
{
|
{
|
||||||
url: this.CesiumHostAddr + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
url: Vue.config.baseUrl + 'geoserver/gwc/service/wmts/rest/ougp:contour6000/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png',
|
||||||
layer: 'ougp:contour6000',
|
layer: 'ougp:contour6000',
|
||||||
style: 'default',
|
style: 'default',
|
||||||
format: 'image/png',
|
format: 'image/png',
|
||||||
|
|
@ -723,7 +868,7 @@ export default {
|
||||||
},
|
},
|
||||||
turnDeatilPage(item) {
|
turnDeatilPage(item) {
|
||||||
this.dialog5 = true
|
this.dialog5 = true
|
||||||
regInfo({voyage_name: item.voyage_name}).then(res => {
|
regInfo({ voyage_name: item.voyage_name }).then(res => {
|
||||||
this.voyageInfo = res.map.voyage
|
this.voyageInfo = res.map.voyage
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -738,9 +883,17 @@ export default {
|
||||||
},
|
},
|
||||||
shousuo() {
|
shousuo() {
|
||||||
this.show = true
|
this.show = true
|
||||||
|
// 等待布局变化完成后触发地图 resize
|
||||||
|
setTimeout(() => {
|
||||||
|
Vue.config.maps["dataSearch"].resize();
|
||||||
|
}, 10);
|
||||||
},
|
},
|
||||||
toshow() {
|
toshow() {
|
||||||
this.show = false
|
this.show = false
|
||||||
|
// 等待布局变化完成后触发地图 resize
|
||||||
|
setTimeout(() => {
|
||||||
|
Vue.config.maps["dataSearch"].resize();
|
||||||
|
}, 10);
|
||||||
},
|
},
|
||||||
// 展开数据样品列表
|
// 展开数据样品列表
|
||||||
openList(item, type) {
|
openList(item, type) {
|
||||||
|
|
@ -765,8 +918,34 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 选中样品列表数据
|
// 选中样品列表数据
|
||||||
onSample(sample) {
|
onSample(sample, job_type) {
|
||||||
this.currenSample = sample.tsy_id
|
this.currenSample = sample.tsy_id
|
||||||
|
if (job_type == "测线作业" && (!sample.start_lon || !sample.start_lat || !sample.end_lon || !sample.end_lat) || job_type == "站位作业" && (!sample.sampling_lon || !sample.sampling_lat)) {
|
||||||
|
alert('该样品无坐标信息,无法定位!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let lon = 0;
|
||||||
|
let lat = 0;
|
||||||
|
if (job_type == "测线作业") {
|
||||||
|
// lon = Number(sample.start_lon);
|
||||||
|
// lat = Number(sample.start_lat);
|
||||||
|
lon = (Number(sample.start_lon) + Number(sample.end_lon)) / 2;
|
||||||
|
lat = (Number(sample.start_lat) + Number(sample.end_lat)) / 2;
|
||||||
|
}
|
||||||
|
if (job_type == "站位作业") {
|
||||||
|
lon = Number(sample.sampling_lon);
|
||||||
|
lat = Number(sample.sampling_lat);
|
||||||
|
}
|
||||||
|
Vue.config.maps["dataSearch"].setZoom(10);
|
||||||
|
Vue.config.maps["dataSearch"].flyTo({
|
||||||
|
center: [lon, lat],
|
||||||
|
speed: 10
|
||||||
|
});
|
||||||
|
let layerId = `vector-${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}-${job_type == "测线作业" ? "line" : "symbol"}`;
|
||||||
|
let sourceLayerId = `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`;
|
||||||
|
// vector-sample_station_TS-46-1_FDZ377_Slurp_SWYP-symbol、vector-sample_line_TS2-29-1_SY612_GQXJ_PJZP-line
|
||||||
|
// highlightFeaturesByProperty("dataSearch", `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`, layerId, "sample_name", sample.sample_name);
|
||||||
|
triggerLayerClick("dataSearch", sourceLayerId, layerId, [lon, lat], { "sample_name": sample.sample_name });
|
||||||
},
|
},
|
||||||
goHome() {
|
goHome() {
|
||||||
this.$router.push({ name: 'home' })
|
this.$router.push({ name: 'home' })
|
||||||
|
|
@ -782,6 +961,18 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
// #cesiumContainerData {
|
||||||
|
// // position: absolute;
|
||||||
|
// left: 0px;
|
||||||
|
// top: 0px;
|
||||||
|
// bottom: 0px;
|
||||||
|
// right: 0px;
|
||||||
|
// padding: 0px;
|
||||||
|
// margin: 0px;
|
||||||
|
// width: 100%;
|
||||||
|
// height: 100vh;
|
||||||
|
// }
|
||||||
|
|
||||||
.shuju {
|
.shuju {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -1034,7 +1225,7 @@ export default {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
color: rgba(255,255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,35 +65,19 @@
|
||||||
<div class="guiji_2">
|
<div class="guiji_2">
|
||||||
<div id="cesiumContainer" ref="cesiumContainer"
|
<div id="cesiumContainer" ref="cesiumContainer"
|
||||||
onmouseleave="document.getElementById('mouseWheelTip').style.display = 'none'"
|
onmouseleave="document.getElementById('mouseWheelTip').style.display = 'none'"
|
||||||
style="height: 100%;width:100%;">
|
onmouseenter="document.getElementById('mouseWheelTip').style.display = ''" style="height: 100%;width:100%;">
|
||||||
<div class="son" style="display: none;">弹框插槽</div>
|
<div class="son" style="display: none;">弹框插槽</div>
|
||||||
<div id="mouseWheelTip" style="display: none;">地图缩放:按下 Ctrl + 鼠标滚轮</div>
|
<div id="mouseWheelTip" style="display: none;">地图缩放:按下 Ctrl + 鼠标滚轮</div>
|
||||||
<el-popover
|
<el-popover placement="left-start" title="航次列表" width="300" trigger="hover">
|
||||||
placement="left-start"
|
|
||||||
title="航次列表"
|
|
||||||
width="300"
|
|
||||||
trigger="hover"
|
|
||||||
>
|
|
||||||
<div class="contour-line">
|
<div class="contour-line">
|
||||||
<span>等深线:</span>
|
<span>等深线:</span>
|
||||||
<el-button
|
<el-button size="mini" v-for="depth in depthOptions" :key="depth"
|
||||||
size="mini"
|
:type="selectedDepths.includes(depth) ? 'primary' : ''" @click="toggleDepth(depth)">
|
||||||
v-for="depth in depthOptions"
|
|
||||||
:key="depth"
|
|
||||||
:type="selectedDepths.includes(depth) ? 'primary' : ''"
|
|
||||||
@click="toggleDepth(depth)"
|
|
||||||
>
|
|
||||||
{{ depth }}米
|
{{ depth }}米
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table
|
<el-table ref="multipleTable" :data="voyageList" @selection-change="handleSelectionChange"
|
||||||
ref="multipleTable"
|
@row-click="handleRowClick" highlight-current-row :current-row-key="currentVoyage">
|
||||||
:data="voyageList"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
@row-click="handleRowClick"
|
|
||||||
highlight-current-row
|
|
||||||
:current-row-key="currentVoyage"
|
|
||||||
>
|
|
||||||
<el-table-column type="selection" width="50" />
|
<el-table-column type="selection" width="50" />
|
||||||
<el-table-column property="voyage_name" label="航次" />
|
<el-table-column property="voyage_name" label="航次" />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -214,37 +198,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="gongju">-->
|
<!-- <div class="gongju">-->
|
||||||
<!-- <div class="gongju_top">-->
|
<!-- <div class="gongju_top">-->
|
||||||
<!-- <img src="../../static/images/shuju5.png" alt="">-->
|
<!-- <img src="../../static/images/shuju5.png" alt="">-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- <div class="gongju_con">-->
|
<!-- <div class="gongju_con">-->
|
||||||
<!-- <ul>-->
|
<!-- <ul>-->
|
||||||
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
||||||
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
||||||
<!-- <p>blast基因分析工具</p>-->
|
<!-- <p>blast基因分析工具</p>-->
|
||||||
<!-- </li>-->
|
<!-- </li>-->
|
||||||
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
||||||
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
||||||
<!-- <p>温盐深分析工具</p>-->
|
<!-- <p>温盐深分析工具</p>-->
|
||||||
<!-- </li>-->
|
<!-- </li>-->
|
||||||
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
||||||
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
||||||
<!-- <p>影像数据物种智能识别</p>-->
|
<!-- <p>影像数据物种智能识别</p>-->
|
||||||
<!-- </li>-->
|
<!-- </li>-->
|
||||||
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
<!-- <li @click="turnPage('http://10.0.82.239/analysisTools/index', 'url')">-->
|
||||||
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
<!-- <img src="../../static/images/mofang.png" alt="">-->
|
||||||
<!-- <p>海洋物理生态耦合模式分析</p>-->
|
<!-- <p>海洋物理生态耦合模式分析</p>-->
|
||||||
<!-- </li>-->
|
<!-- </li>-->
|
||||||
<!-- </ul>-->
|
<!-- </ul>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Vue from 'vue'
|
||||||
import Footer from '../components/public/Footer'
|
import Footer from '../components/public/Footer'
|
||||||
import { getVoyage, statisticsNums } from '../api/home'
|
import { getVoyage, statisticsNums } from '../api/home'
|
||||||
import { swiper, swiperSlide } from 'vue-awesome-swiper'
|
import { swiper, swiperSlide } from 'vue-awesome-swiper'
|
||||||
|
|
@ -253,6 +238,9 @@ import { sampleDatasetList, voyageRegList } from '@/api/dataTransfer'
|
||||||
import {
|
import {
|
||||||
modifyCesiumMouseWheel, setImageryViewModels
|
modifyCesiumMouseWheel, setImageryViewModels
|
||||||
} from '@/utils/common'
|
} from '@/utils/common'
|
||||||
|
import { loadVectorLayer } from '@/utils/vector-layer-utils'
|
||||||
|
import { ColorGenerator } from '@/utils/color-generator';
|
||||||
|
import { handleWheel, initSprites } from "@/utils/mapbox-utils";
|
||||||
|
|
||||||
const Cesium = window.Cesium
|
const Cesium = window.Cesium
|
||||||
|
|
||||||
|
|
@ -336,7 +324,7 @@ export default {
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
|
this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
|
||||||
this.initCesium()
|
this.initMapbox()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 查询航次列表
|
// 查询航次列表
|
||||||
|
|
@ -345,7 +333,16 @@ export default {
|
||||||
start: 0,
|
start: 0,
|
||||||
limit: 10
|
limit: 10
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
this.voyageList = res.page.records
|
this.voyageList = res.page.records;
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const colors = ColorGenerator.generateDivergingColors(this.voyageList.length);
|
||||||
|
this.voyageList.forEach(element => {
|
||||||
|
// 加载轨迹图层
|
||||||
|
loadVectorLayer(element.voyage_name, ["line", "symbol"], [colors[i], colors[i]], "visible", "home", "dsds");
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
// 默认全选当前页
|
// 默认全选当前页
|
||||||
this.$refs.multipleTable.toggleAllSelection()
|
this.$refs.multipleTable.toggleAllSelection()
|
||||||
|
|
@ -353,6 +350,81 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 初始化 mapbox
|
||||||
|
initMapbox() {
|
||||||
|
const map = new window.mapboxgl.Map({
|
||||||
|
container: 'cesiumContainer',
|
||||||
|
// style: 'mapbox://styles/mapbox/standard',
|
||||||
|
style: {
|
||||||
|
"version": 8,
|
||||||
|
"name": "default_style",
|
||||||
|
// mapbox地图使用的图标
|
||||||
|
"sprite": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/sprite`,
|
||||||
|
// 使用官方标注字体(需联网)
|
||||||
|
"glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
|
||||||
|
// 自部署标注字体(无需联网),glyphs 前有两个斜杠时,可能导致404
|
||||||
|
// "glyphs": `${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/glyphs/{fontstack}/{range}.pbf`,
|
||||||
|
"sources": {
|
||||||
|
"影像地图": {
|
||||||
|
"type": "raster",
|
||||||
|
"tiles": ["https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"],
|
||||||
|
"tileSize": 256,
|
||||||
|
// "minzoom": 0,
|
||||||
|
// "maxzoom": baseMapMaxZoom
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"id": "影像地图",
|
||||||
|
"type": "raster",
|
||||||
|
"source": "影像地图",
|
||||||
|
"layout": { visibility: "visible" },
|
||||||
|
// "minzoom": 0,
|
||||||
|
// "maxzoom": baseMapMaxZoom
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
basemap: {
|
||||||
|
theme: 'monochrome',
|
||||||
|
lightPreset: 'night'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zoom: 5,
|
||||||
|
center: [113, 15.5],
|
||||||
|
minZoom: 2,
|
||||||
|
maxZoom: 29,
|
||||||
|
trackResize: true,
|
||||||
|
scrollZoom: false, // 禁用滚轮缩放
|
||||||
|
// 地图默认字体
|
||||||
|
localIdeographFontFamily: "Microsoft YoHei, 'Noto Sans', 'Noto Sans CJK SC', sans-serif",
|
||||||
|
pitch: 0, // 初始俯仰角为0
|
||||||
|
bearing: 0, // 初始方位角为0
|
||||||
|
// 禁用所有旋转和俯仰角控制
|
||||||
|
dragRotate: false,
|
||||||
|
touchZoomRotate: false,
|
||||||
|
pitchWithRotate: false,
|
||||||
|
// maxBounds: [[-180, -90], [180, 90]],// Set the map's geographical boundaries.
|
||||||
|
antialias: true,
|
||||||
|
attributionControl: false,
|
||||||
|
boxZoom: true,//如果为true,则启用了“缩放框”交互,按住shift并在地图上拖框放大
|
||||||
|
preserveDrawingBuffer: false,//如果为true,地图的canvas可以使用导出到png,通过map.getCanvas().toDataURL()。默认为false,会提高地图性能。
|
||||||
|
projection: 'equirectangular', // 设置投影方式,经纬度直投(解决缩放级别大于26时瓦片消失的问题)
|
||||||
|
});
|
||||||
|
Vue.config.maps["home"] = map;
|
||||||
|
|
||||||
|
handleWheel("home");
|
||||||
|
initSprites("home")
|
||||||
|
|
||||||
|
map.on('load', () => {
|
||||||
|
// 加载海岸线
|
||||||
|
// loadVectorLayer("GSHHS_f_L1", ["line"], ["#FEFEFE"], "home", 'ougp');
|
||||||
|
// 加载等深线
|
||||||
|
loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "none", "home", 'ougp');
|
||||||
|
loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "none", "home", 'ougp');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// 初始化cesium
|
// 初始化cesium
|
||||||
initCesium() {
|
initCesium() {
|
||||||
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
|
||||||
|
|
@ -432,6 +504,14 @@ export default {
|
||||||
},
|
},
|
||||||
// 行点击事件处理
|
// 行点击事件处理
|
||||||
handleRowClick(row, column, event) {
|
handleRowClick(row, column, event) {
|
||||||
|
// 原高亮逻辑改为图层显示/隐藏控制
|
||||||
|
let visibility = Vue.config.maps["home"].getLayoutProperty(`vector-${row.voyage_name}-symbol`, "visibility");
|
||||||
|
visibility = visibility == "visible" ? "none" : "visible";
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${row.voyage_name}-symbol`, "visibility", visibility);
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${row.voyage_name}-line`, "visibility", visibility);
|
||||||
|
this.$refs.multipleTable.toggleRowSelection(row);
|
||||||
|
return;
|
||||||
|
|
||||||
// 如果点击的是当前已选中的行,则取消选择
|
// 如果点击的是当前已选中的行,则取消选择
|
||||||
if (this.currentVoyage === row.voyage_name) {
|
if (this.currentVoyage === row.voyage_name) {
|
||||||
this.clearHighlight();
|
this.clearHighlight();
|
||||||
|
|
@ -492,7 +572,9 @@ export default {
|
||||||
|
|
||||||
// 移除不再需要的轨迹图层
|
// 移除不再需要的轨迹图层
|
||||||
voyagesToRemove.forEach(voyage => {
|
voyagesToRemove.forEach(voyage => {
|
||||||
this.removeSingleLineImg(voyage);
|
// this.removeSingleLineImg(voyage);
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${voyage}-symbol`, "visibility", "none");
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${voyage}-line`, "visibility", "none");
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加新选择的轨迹图层
|
// 添加新选择的轨迹图层
|
||||||
|
|
@ -500,7 +582,9 @@ export default {
|
||||||
// 从val中找出对应航次的完整数据项
|
// 从val中找出对应航次的完整数据项
|
||||||
const correspondingItem = val.find(item => item.voyage_name === voyage);
|
const correspondingItem = val.find(item => item.voyage_name === voyage);
|
||||||
if (correspondingItem) {
|
if (correspondingItem) {
|
||||||
this.addSingleLineImg(correspondingItem);
|
// this.addSingleLineImg(correspondingItem);
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${voyage}-symbol`, "visibility", "visible");
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-${voyage}-line`, "visibility", "visible");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -517,7 +601,6 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建轨迹影像图层
|
|
||||||
const trackLine = new Cesium.WebMapTileServiceImageryProvider({
|
const trackLine = new Cesium.WebMapTileServiceImageryProvider({
|
||||||
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
|
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
|
||||||
layer: `dsds:${voyageName}`,
|
layer: `dsds:${voyageName}`,
|
||||||
|
|
@ -552,10 +635,12 @@ export default {
|
||||||
const index = this.selectedDepths.indexOf(depth);
|
const index = this.selectedDepths.indexOf(depth);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
this.selectedDepths.splice(index, 1); // 如果已选中,则移除
|
this.selectedDepths.splice(index, 1); // 如果已选中,则移除
|
||||||
this.removeSingleContourLine(depth); // 移除指定深度的单条等深线
|
// this.removeSingleContourLine(depth); // 移除指定深度的单条等深线
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-contour-${depth}m-line`, "visibility", "none");
|
||||||
} else {
|
} else {
|
||||||
this.selectedDepths.push(depth); // 如果未选中,则添加
|
this.selectedDepths.push(depth); // 如果未选中,则添加
|
||||||
this.addSingleContourLine(depth); // 添加指定深度的单条等深线
|
// this.addSingleContourLine(depth); // 添加指定深度的单条等深线
|
||||||
|
Vue.config.maps["home"].setLayoutProperty(`vector-contour-${depth}m-line`, "visibility", "visible");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 添加单条指定深度的等深线
|
// 添加单条指定深度的等深线
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 373 B |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 493 B |
|
After Width: | Height: | Size: 402 B |
|
After Width: | Height: | Size: 383 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 636 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 382 B |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 382 B |
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.7 KiB |