diff --git a/.eslintrc.js b/.eslintrc.js
index 64de64c..5d8f0d7 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,6 +10,8 @@ module.exports = {
es6: true
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
+ // 完全禁用 ESLint
+ lintOnSave: false,
// add your custom rules here
// it is base on https://github.com/vuejs/eslint-config-vue
diff --git a/config/index.js b/config/index.js
index 0cd0707..a184996 100644
--- a/config/index.js
+++ b/config/index.js
@@ -51,7 +51,7 @@ module.exports = {
// Various Dev Server settings
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,
errorOverlay: true,
notifyOnErrors: true,
diff --git a/index.html b/index.html
index c9bf061..17ebb31 100644
--- a/index.html
+++ b/index.html
@@ -12,7 +12,13 @@
-
+
+
+
+
+
+
+
diff --git a/src/api/statistics.js b/src/api/statistics.js
index ace4db1..25df2a6 100644
--- a/src/api/statistics.js
+++ b/src/api/statistics.js
@@ -70,6 +70,26 @@ export function hovDiving(data) {
})
}
+// 遥控潜水器潜次统计
+export function rovDiving(data) {
+ return request({
+ url: '/report/platform/rov/diving.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
+// 无人航行潜水器潜次统计
+export function auvDiving(data) {
+ return request({
+ url: '/report/platform/auv/diving.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
// 载人潜水器参航单位统计
export function hovUnit(data) {
return request({
@@ -80,6 +100,24 @@ export function hovUnit(data) {
})
}
+export function rovUnit(data) {
+ return request({
+ url: '/report/platform/rov/unit.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
+export function auvUnit(data) {
+ return request({
+ url: '/report/platform/auv/unit.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
// 载人潜水器参航单位统计(年度)
export function hovUnitYear(data) {
return request({
@@ -90,6 +128,24 @@ export function hovUnitYear(data) {
})
}
+export function rovUnitYear(data) {
+ return request({
+ url: '/report/platform/rov/unit/year.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
+export function auvUnitYear(data) {
+ return request({
+ url: '/report/platform/auv/unit/year.htm',
+ method: 'post',
+ type: 'application/x-www-form-urlencoded',
+ data
+ })
+}
+
// 无人航行潜水器统计
export function platformAuv(data) {
return request({
@@ -100,37 +156,7 @@ export function platformAuv(data) {
})
}
-/** 无人航行潜水器潜次统计 */
-export function auvDiving(data) {
- return request({
- url: '/report/platform/auv/diving.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
-/** 无人航行潜水器参航单位统计 */
-export function auvUnit(data) {
- return request({
- url: '/report/platform/auv/unit.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
-/** 无人航行潜水器参航单位统计(年度) */
-export function auvUnitYear(data) {
- return request({
- url: '/report/platform/auv/unit/year.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
-// 遥控潜水器潜水器统计
+// 遥控潜水器汇总统计
export function platformRov(data) {
return request({
url: '/report/platform/rov.htm',
@@ -140,36 +166,6 @@ export function platformRov(data) {
})
}
-/** 遥控潜水器潜次统计 */
-export function rovDiving(data) {
- return request({
- url: '/report/platform/rov/diving.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
-/** 遥控潜水器参航单位统计 */
-export function rovUnit(data) {
- return request({
- url: '/report/platform/rov/unit.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
-/** 遥控潜水器参航单位统计(年度) */
-export function rovUnitYear(data) {
- return request({
- url: '/report/platform/rov/unit/year.htm',
- method: 'post',
- type: 'application/x-www-form-urlencoded',
- data
- })
-}
-
/**
* ds1 报表独立域名(参航科学家/单位汇总等),与全局 VUE_APP_BASE_API 解耦。
* @returns {string}
diff --git a/src/main.js b/src/main.js
index c3ae942..02bc37d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,7 @@ import axios from 'axios'
import qs from 'qs'
import store from './store/index'
+import { extractWMTSBoundsInfo } from '@/utils/mapbox-utils'
window.sessionStorage.setItem('webInfo', 'ASOS')
Vue.use(ElementUI, { locale: Zhlocale })
@@ -31,3 +32,29 @@ new Vue({
components: { App },
template: ''
})
+
+// 定义部分全局变量
+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'
+
+Vue.config.tiles = {
+ "world_horizon_1": `${Vue.config.baseUrl}/geoserver/gwc/service/wmts/rest/ougp:world_horizon_1/EPSG:900913/EPSG:900913:{z}/{y}/{x}?format=image/jpeg`,
+ "World_Imagery": "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
+ "GEBCO_basemap_NCEI": "https://tiles.arcgis.com/tiles/C8EMgrsFcRFL6LrL/arcgis/rest/services/GEBCO_basemap_NCEI/MapServer/tile/{z}/{y}/{x}",
+ "gaode":"https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&scl=2&style=8&x={x}&y={y}&z={z}"
+};
\ No newline at end of file
diff --git a/src/style/style.scss b/src/style/style.scss
index b2f8902..a4eaa71 100644
--- a/src/style/style.scss
+++ b/src/style/style.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/src/utils/color-generator.js b/src/utils/color-generator.js
new file mode 100644
index 0000000..8d7c566
--- /dev/null
+++ b/src/utils/color-generator.js
@@ -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
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/utils/mapbox-utils.js b/src/utils/mapbox-utils.js
new file mode 100644
index 0000000..0a9b773
--- /dev/null
+++ b/src/utils/mapbox-utils.js
@@ -0,0 +1,331 @@
+import Vue from 'vue'
+import { loadVectorLayer } from '@/utils/vector-layer-utils'
+
+export function initMapbox(mapId, options = {}) {
+ const map = new window.mapboxgl.Map({
+ container: mapId,
+ // 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.tiles[options.tileKey || "gaode"]
+ ],
+ "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: options.zoom || 5,
+ center: options.center || [112, 20],
+ minZoom: options.minZoom || 2,
+ maxZoom: options.maxZoom || 29,
+ trackResize: true,
+ scrollZoom: false, // 禁用滚轮缩放
+ // 地图默认字体
+ localIdeographFontFamily: "Microsoft YoHei, 'Noto Sans', 'Noto Sans CJK SC', sans-serif",
+ pitch: 0,
+ bearing: 0,
+ // 旋转和俯仰角控制
+ dragRotate: options.dragRotate || false,
+ touchZoomRotate: options.touchZoomRotate || false,
+ pitchWithRotate: options.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[mapId] = 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("voyageMap_ship");
+ Vue.config.maps[mapId].scrollZoom.enable();
+ initSprites(mapId)
+
+ map.on('load', () => {
+ // 加载海岸线
+ if (options.coastlineOn || false)
+ loadVectorLayer("GSHHS_f_L1", ["line"], ["#5c71c3"], "visible", mapId, 'ougp');
+ // 加载等深线
+ if (options.contourOn || false) {
+ loadVectorLayer("contour-4500m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp');
+ loadVectorLayer("contour-6000m", ["line"], ["#FEFEFE"], "visible", mapId, 'ougp');
+ }
+ });
+}
+
+/**
+ * 获取所有图层信息
+ * @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 = "homeMap") {
+ 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 = "homeMap") {//../../../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: () => { }
+ };
+}
\ No newline at end of file
diff --git a/src/utils/vector-layer-utils.js b/src/utils/vector-layer-utils.js
new file mode 100644
index 0000000..b149f65
--- /dev/null
+++ b/src/utils/vector-layer-utils.js
@@ -0,0 +1,538 @@
+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 = "homeMap", 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 = "homeMap") {
+ 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 = "homeMap", 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 = "homeMap") {
+ Vue.config.maps[mapId].on("click", layerId, async (e) => {
+ // 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
+ if (e.defaultPrevented)
+ return;
+ // 标记该 event 已触发,禁止点击事件穿透
+ if (typeof e.preventDefault === "function")
+ e.preventDefault();
+
+ const description = `
+
`;
+
+ showPopupDetails(mapId, e.lngLat, description, "450px");
+ });
+}
+
+export function showSampleStationInfo(layerId, mapId = "homeMap") {
+ 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 = `
+ `;
+
+ showPopupDetails(mapId, feature.geometry.coordinates, description, "450px");
+ });
+}
+
+export function showTrackInfo(layerId, mapId = "homeMap") {
+ 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 = `
+ `;
+
+ 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, text_field, mapId) {
+ 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', text_field],
+ '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"// 默认颜色
+ ]);
+}
\ No newline at end of file
diff --git a/src/views/dataSearch.vue b/src/views/dataSearch.vue
index b87affe..da382f9 100644
--- a/src/views/dataSearch.vue
+++ b/src/views/dataSearch.vue
@@ -26,7 +26,7 @@
-
编号规则:潜次号_平台_设备类型_样品数据类型
+
编号规则:航次号_潜次号_设备类型_数据样品类型
@@ -35,7 +35,7 @@
- {{ sample.sample_name }}
+ v-for="(sample, i) in item.sampleArray" :key="i" @click.stop="onSample(sample, item.job_type)">{{ sample.sample_name }}
@@ -83,7 +83,8 @@
{{ item.voyage_name }}
- {{ item.voyage_mileage }}海里
+
{{ item.voyage_mileage }}海里
+
{{ item.funding_org }}
@@ -116,11 +117,15 @@
{{ item.serect_status == 'Active' ? '是' : '否' }}
-
-
-
-
-
+
+
+
+
+
@@ -133,65 +138,53 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
{{ voyageInfo.voyage_name }}
{{ voyageInfo.voyage_remark }}
@@ -241,6 +234,10 @@ import Cookies from 'js-cookie'
import {
setImageryViewModels
} from '@/utils/common'
+import Vue from 'vue'
+import { loadVectorLayer, loadJsonLineFeature, loadJsonPointFeature, highlightFeaturesByProperty } from '@/utils/vector-layer-utils'
+import { ColorGenerator } from '@/utils/color-generator';
+import { initMapbox, handleWheel, initSprites, triggerLayerClick } from "@/utils/mapbox-utils";
const Cesium = window.Cesium
export default {
@@ -249,6 +246,7 @@ export default {
return {
show: false,
viewer: null,
+ filter: null,
color: [Cesium.Color.RED, Cesium.Color.GREEN, Cesium.Color.BLUE, Cesium.Color.CHARTREUSE, Cesium.Color.DARKBLUE],
searchType: [
{
@@ -362,8 +360,18 @@ export default {
},
},
mounted() {
- this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
- this.initCesium()
+ initMapbox("dataSearchMap", {
+ tileKey: "world_horizon_1",
+ zoom: 4,
+ center: [122, 15],
+ minZoom: 2,
+ maxZoom: 29,
+ coastlineOn:false,
+ contourOn:false
+ });
+
+ // this.imageryViewModels = setImageryViewModels(this.CesiumHostAddr)
+ // this.initCesium()
},
methods: {
// 获取菜单
@@ -453,21 +461,35 @@ export default {
const arr = res.page.records
if (this.radio === '航次') {
if (arr.length) {
+ let i = 0;
+ const colors = ColorGenerator.generateDivergingColors(arr.length);
arr.forEach(item => {
item.highlight = false
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", "dataSearchMap", "dsds");
+ i++;
})
}
this.dataList = arr
}
if (this.radio === '数据样品集') {
- this.getDataPoints(arr)
+ try {
+ this.getDataPoints(arr)
+ }
+ catch (err) {
+ console.log(err);
+ }
}
})
},
// 添加高亮图层
addHighlightLayer(voyageName, i) {
+ Vue.config.maps["dataSearchMap"].setPaintProperty(`vector-${voyageName}-line`, "line-width", 4);
+ this.dataList[i].highlight = true;
+ return;
+
this.clearHighlight()
const highlightLayer = new Cesium.WebMapTileServiceImageryProvider({
url: `${this.CesiumHostAddr}geoserver/gwc/service/wmts/rest/dsds:${voyageName}-highlight/{TileMatrixSet}/{TileMatrixSet}:{TileMatrix}/{TileRow}/{TileCol}?format=image/png`,
@@ -482,8 +504,20 @@ export default {
this.dataList[i].highlight = true;
},
// 清除高亮
- clearHighlight() {
- this.dataList.map(item => {item.highlight = false})
+ clearHighlight(voyageName, i) {
+ let lineWidthExpression = [
+ "case",
+ ["boolean", ["feature-state", "hover"], false],
+ 4,
+ ["boolean", ["feature-state", "click"], false],
+ 2,
+ 1.8
+ ];
+ Vue.config.maps["dataSearchMap"].setPaintProperty(`vector-${voyageName}-line`, "line-width", lineWidthExpression);
+ this.dataList[i].highlight = false;
+ return;
+
+ this.dataList.map(item => { item.highlight = false })
// 移除地图上的高亮图层
if (this.highlightLayer) {
this.viewer.imageryLayers.remove(this.highlightLayer);
@@ -492,7 +526,13 @@ export default {
},
// 显示
addSingleLineImg(voyageName, i) {
-
+ if (i !== null) {
+ this.dataList[i].show = true;
+ }
+ // 显示图层
+ Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "visible");
+ Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "visible");
+ return;
// 检查该航次图层是否已存在,避免重复添加
if (this.trackLayerMap[voyageName]) {
console.warn(`航次 ${voyageName} 的轨迹图层已存在`);
@@ -519,6 +559,16 @@ export default {
// 隐藏
removeSingleLineImg(voyageName, i) {
+ if (i !== null) {
+ this.dataList[i].show = false;
+ }
+ // 隐藏图层
+ // let visibility = Vue.config.maps["dataSearchMap"].getLayoutProperty(`vector-${voyageName}-symbol`, "visibility");
+ // visibility = visibility == "visible" ? "none" : "visible";
+ Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-symbol`, "visibility", "none");
+ Vue.config.maps["dataSearchMap"].setLayoutProperty(`vector-${voyageName}-line`, "visibility", "none");
+ return;
+
const layerInstance = this.trackLayerMap[voyageName];
if (layerInstance) {
// 从Cesium的imageryLayers中移除该图层
@@ -582,7 +632,7 @@ export default {
},
// 清除所有轨迹图层
clearAll() {
- this.clearHighlight()
+ // this.clearHighlight()
if (this.trackLayerMap.length) {
this.trackLayerMap.forEach(layer => {
this.viewer.imageryLayers.remove(layer);
@@ -598,9 +648,20 @@ export default {
// 获取数据样品集坐标点位
getDataPoints(arr) {
if (!arr.length) return false
+ let i = 0;
+ const colors = ColorGenerator.generateDivergingColors(arr.length);
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", "dataSearchMap", "dsds", "sample_name");
+ } else {
+ loadVectorLayer(`sample_station_${item.dataset_name}`, ["symbol"], ["#FFFFFF", "#FFFFFF"], "visible", "dataSearchMap", "dsds", "sample_name");
+ }
+ i++;
+
+ // this.addSingleLineImg(item.voyage_name, null)
+ // this.dataList[i].show = true;
+ // this.divingImage(item)
})
const voyageList = arr
voyageList.map(item => {
@@ -610,11 +671,11 @@ export default {
dataset_id: item.tsy_id
}).then(res => {
item.sampleArray = res.array
- if (res.array.length) {
- res.array.forEach(sample => {
- this.sampleLineImage(sample)
- })
- }
+ // if (res.array.length) {
+ // res.array.forEach(sample => {
+ // this.sampleLineImage(sample)
+ // })
+ // }
})
}
if (item.job_type === '站位作业') {
@@ -624,22 +685,21 @@ export default {
}).then(res => {
item.sampleArray = res.array
// 格式化坐标点
- if (res.array.length) {
- res.array.forEach(sample => {
- this.sampleLineImage(sample)
- })
- }
+ // if (res.array.length) {
+ // res.array.forEach(sample => {
+ // this.sampleLineImage(sample)
+ // });
+ // }
})
}
})
this.dataList = voyageList
- console.log(this.dataList)
},
// 初始化cesium
initCesium() {
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJkY2Y5NWY2Ni1mODQ1LTQ3YTUtYjc4Zi1jYzhjMGI2YzcxMWYiLCJpZCI6MTI5ODMwLCJpYXQiOjE2Nzk0Njk5NTd9.DTH54ioOH-HLqeNIetBe9hFyrPOX2Vp1AQmZzw8TIZ4'
- this.viewer = new Cesium.Viewer('cesiumContainerData', {
+ this.viewer = new Cesium.Viewer('dataSearchMap', {
sceneMode: Cesium.SceneMode.SCENE2D,
shadows: false,
timeline: false,
@@ -712,7 +772,7 @@ export default {
// 增加6000米等深线
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',
style: 'default',
format: 'image/png',
@@ -724,7 +784,7 @@ export default {
},
turnDeatilPage(item) {
this.dialog5 = true
- regInfo({voyage_name: item.voyage_name}).then(res => {
+ regInfo({ voyage_name: item.voyage_name }).then(res => {
this.voyageInfo = res.map.voyage
})
},
@@ -739,9 +799,17 @@ export default {
},
shousuo() {
this.show = true
+ // 等待布局变化完成后触发地图 resize
+ setTimeout(() => {
+ Vue.config.maps["dataSearchMap"].resize();
+ }, 10);
},
toshow() {
this.show = false
+ // 等待布局变化完成后触发地图 resize
+ setTimeout(() => {
+ Vue.config.maps["dataSearchMap"].resize();
+ }, 10);
},
// 展开数据样品列表
openList(item, type) {
@@ -766,8 +834,34 @@ export default {
}
},
// 选中样品列表数据
- onSample(sample) {
+ onSample(sample, job_type) {
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["dataSearchMap"].setZoom(10);
+ Vue.config.maps["dataSearchMap"].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("dataSearchMap", `${job_type == "测线作业" ? "sample_line" : "sample_station"}_${sample.dataset_name}`, layerId, "sample_name", sample.sample_name);
+ triggerLayerClick("dataSearchMap", sourceLayerId, layerId, [lon, lat], { "sample_name": sample.sample_name });
},
goHome() {
this.$router.push({ name: 'home' })
@@ -783,6 +877,18 @@ export default {