DSDSWeb/src/utils/vector-layer-utils.js

237 lines
9.3 KiB
JavaScript
Raw Normal View History

2025-11-21 23:02:28 +08:00
import Vue from 'vue'
import { regInfo } from '@/api/dataSearch'
let sourceLayer = undefined;
let clickedLineIds = [];
export function loadVectorLayer(layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "home", workspace = 'dsds') {
// 添加数据源
const sourceId = `vector-${layerId}`;
let source = Vue.config.maps[mapId].getSource(sourceId);
if (!source)
Vue.config.maps[mapId].addSource(sourceId, {
type: 'vector',
scheme: 'tms',
tiles: [`${Vue.config.baseUrl}/geoserver/gwc/service/tms/1.0.0/${workspace}:${layerId}@EPSG:900913@pbf/{z}/{x}/{y}.pbf`],
minzoom: 0,
maxzoom: 30,
generateId: true
});
let i = 0;
features.forEach((feature) => {
loadVectorFeature(layerId, feature, colors[i++], visibility, mapId);
});
}
export async function loadVectorFeature(layerId, feature, color, visibility, mapId) {
let sourceId = `vector-${layerId}`;
// symbol
// Slot 是 Mapbox 样式规范中的一个概念,它定义了图层在渲染过程中的位置和顺序。
let slot = "foreground";
let layout = {
"symbol-placement": "point",
"text-rotation-alignment": "auto",
"text-field": ["get", "name"],
"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": 1,
"visibility": visibility
};
let paint = {
"text-color": color,
"text-halo-color": "#FFFFFF",
"text-halo-width": 0.1,
"icon-opacity": 1,
// slot: "middle"
};
if (feature === "line") {
slot = "top";
layout = {
"visibility": visibility
};
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}`);
if (!layer) {
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);
await showTrackInfo(`${sourceId}-${feature}`, mapId);
return sourceId;
}
export function setHighlight(layerId, sourceId, mapId = "home") {
let hoveredLineId = null;
let clickedLineId = null;
Vue.config.maps[mapId].on('click', layerId, (e) => {
if (!e.features || e.features.length <= 0)
return;
sourceLayer = e.features[0].sourceLayer;
// 获取当前单击的线的选择状态
let state = Vue.config.maps[mapId].getFeatureState({
source: sourceId,
sourceLayer: sourceLayer,
id: e.features[0].id
});
if (clickedLineId) {
Vue.config.maps[mapId].setFeatureState({ source: sourceId, sourceLayer: sourceLayer, id: clickedLineId }, { click: false });
}
clickedLineId = e.features[0].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 在添加之后最多只调用一次。如果为 truelistener 会在其被调用之后自动移除。
// }, true);
}
});
// When the user moves their mouse over the state-fill layer, we'll update the feature state for the feature under the mouse.
Vue.config.maps[mapId].on('mousemove', layerId, (e) => {
if (e.features.length <= 0)
return;
sourceLayer = e.features[0].sourceLayer;
if (hoveredLineId) {
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: false }
);
}
hoveredLineId = e.features[0].id;
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: true }
);
});
// When the mouse leaves the state-fill layer, update the feature state of the previously hovered feature.
Vue.config.maps[mapId].on('mouseleave', layerId, () => {
if (hoveredLineId && sourceLayer) {
Vue.config.maps[mapId].setFeatureState(
{ source: sourceId, sourceLayer: sourceLayer, id: hoveredLineId },
{ hover: false }
);
}
hoveredLineId = null;
});
}
export function changeCursor(layerId, mapId = "home", cursor = "pointer") {
Vue.config.maps[mapId].on("mouseenter", layerId, () => {
Vue.config.maps[mapId].getCanvas().style.cursor = cursor;
});
Vue.config.maps[mapId].on("mouseleave", layerId, () => {
Vue.config.maps[mapId].getCanvas().style.cursor = "";
});
}
export function showTrackInfo(layerId, mapId = "home") {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
e.preventDefault();
let coordinate = e.lngLat;
let match = layerId.match(/vector-(.*)-[(symbol)|(line)]/);
if (!match)
return;
const voyageId = match[1];
regInfo({ voyage_name: voyageId }).then(res => {
const info = res.map.voyage;
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2"><a style="color:#0000FF" target="_balnk" title="点击查看航次详情" href="${Vue.config.baseUrl}/ds/#/container/dataDetail?voyage_name=${info.voyage_name}">航次信息</a></td>
</tr>
</thead>
<tr>
<td>航次编号: <b>${info.voyage_name}</b></td>
<td>航次首席: <b>${info.voyage_officer}</b></td>
</tr>
<tr>
<td>科考船舶: <b>${info.ship_name}</b></td>
<td>资助机构: <b>${info.funding_org}</b></td>
</tr>
<tr>
<td>离港日期: <b>${info.voyage_start_date}</b></td>
<td>返港日期: <b>${info.voyage_end_date}</b></td>
</tr>
<tr>
<td colspan="2">航次概况: <b>${info.voyage_remark}</b></td>
</tr>
</table>`;
showPopupDetails(mapId, coordinate, description, "420px");
})
});
}
export function showPopupDetails(mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}) {
new window.mapboxgl.Popup({ offset: {}, className: "my-class", closeButton: closeButton, closeOnClick: closeOnClick })
.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(Vue.config.maps[mapId]);
}