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 {