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

754 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Vue from 'vue'
import { regInfo } from '@/api/dataSearch'
let sourceLayer = undefined;
let clickedLineIds = [];
// 存储可复用的每个地图对象的唯一 Popup 实例key 为 mapId以避免频繁创建/销毁 Popup 导致的性能问题和潜在内存泄漏
let sharedPopup = {};
// 存储上一条点击的线
let lastLineId;
// 默认的线宽表达式
export let lineWidthExpression = [
"case",
["boolean", ["feature-state", "hover"], false],
4,
["boolean", ["feature-state", "click"], false],
2,
1.8
];
export function setMapLayoutProperty(mapId, visibility = "none") {
// 设置所有图层的显示/隐藏状态(根据图层类型或名称过滤底图图层,避免误操作)
const layers = Vue.config.maps[mapId].getStyle().layers;
layers.forEach(layer => {
// 跳过底图
if (layer.id !== '影像地图' && layer.type !== 'raster') {
Vue.config.maps[mapId].setLayoutProperty(layer.id, 'visibility', visibility);
}
});
}
export function setMapPaintProperty({ mapId, property, value }) {
const layers = Vue.config.maps[mapId].getStyle().layers;
layers.forEach(layer => {
// 跳过底图和非测线图层
if (layer.id !== '影像地图' && layer.type !== 'raster' && layer.id.endsWith("-line")) {
Vue.config.maps[mapId].setPaintProperty(layer.id, property, lineWidthExpression);
}
});
// Vue.config.maps[mapId].setPaintProperty(layerId, "line-width", 14);
}
export function loadVectorLayer({ layerId, features = ["line", "symbol"], colors = ["#FEFEFE", "#FF0000"], visibility = "visible", mapId = "homeMap", workspace = 'dsds', textField = "name", callbackOnClick }) {
// 添加数据源
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, color: colors[i++], visibility, mapId, textField, callbackOnClick });
});
}
export async function loadVectorFeature({ layerId, feature, color, visibility, mapId, textField = "name", callbackOnClick }) {
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'
});
}
else {
// 图层已存在则设置为可见
Vue.config.maps[mapId].setLayoutProperty(`${sourceId}-${feature}`, "visibility", "visible");
}
// 点击可交互的图层,显示相关信息
await setHighlight(`${sourceId}-${feature}`, sourceId, mapId);
changeCursor(`${sourceId}-${feature}`, mapId);
if (layerId.includes("sample_line"))
await showSampleLineInfo(`${sourceId}-${feature}`, mapId, callbackOnClick);
if (layerId.includes("sample_station"))
await showSampleStationInfo(`${sourceId}-${feature}`, mapId, callbackOnClick);
else if (sourceId.includes("-SY") || sourceId.includes("-FDZ")) {
await showDiveInfo(`${sourceId}-${feature}`, mapId);
console.log(mapId, `${sourceId}-${feature}`);
}
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, async (e) => {
sourceLayer = e.features[0].sourceLayer;
// 排除没有 sourceLayer 的情况(如点击事件中 features 可能来自多个图层,某些图层缺乏 sourceLayer 属性会导致后续 setFeatureState 报错)
if (!sourceLayer)
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);
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 });
// 单击时加入选择列表(用于单击 ESC 键时取消选择状态)
clickedLineIds.push(clickedLineId);
// document.removeEventListener("keydown", removeHighlight, true);
// document.addEventListener("keydown", (e) => {
// removeHighlight(e, sourceId);
// }, { once: true }); // once一个布尔值表示 listener 在添加之后最多只调用一次。如果为 truelistener 会在其被调用之后自动移除。
}
});
// 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) => {
sourceLayer = e.features[0].sourceLayer;
// 排除没有 sourceLayer 的情况(如点击事件中 features 可能来自多个图层,某些图层缺乏 sourceLayer 属性会导致后续 setFeatureState 报错)
if (!sourceLayer)
return;
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 async function showUnitReport(mapId, unit, coordinate, callbackOnClose = null) {
const result = await (await fetch(`${Vue.config.baseUrl}/ds/report/unit/total.htm?unit=${unit}`)).json();
const unitReports = result.array || [];
if (!unitReports.length) {
alert("未查询到相关单位的统计信息");
return;
}
const description = `
<table class="popup-table" style="min-width:280px;">
<thead>
<tr>
<td colspan="2">单位参航信息</td>
</tr>
</thead>
<tr>
<td colspan="2">单位名称: <b>${unitReports[0].unit_name}</b></td>
</tr>
<tr>
<td>参航次数: <b>${unitReports[0].voyage_total || "/"}</b></td>
<td>参航天数: <b>${unitReports[0].voyage_days || "/"}</b></td>
</tr>
<tr>
<td>参航人数: <b>${unitReports[0].member_total || "/"}</b></td>
<td>下潜次数: <b>${unitReports[0].drving_total || "/"}</b></td>
</tr>
<tr>
<td>首潜人数: <b>${unitReports[0].f_drving_total || "/"}</b></td>
<td>首次参航人数: <b>${unitReports[0].f_voyage_total || "/"}</b></td>
</tr>
</table>`;
showPopupDetails({ mapId, coordinate, description, maxWidth: "300px", callbackOnClose: callbackOnClose });
}
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", callbackOnClick = null) {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 在左侧列表中高亮对应项
if (callbackOnClick && typeof callbackOnClick === "function") {
callbackOnClick(e.features[0]);
}
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
if (typeof e.preventDefault === "function")
e.preventDefault();
// console.log("showSampleLineInfo: ", layerId, e.features[0]);
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">测线信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${e.features[0].properties.dataset_name}</b></td>
<td>数据样品编号: <b>${e.features[0].properties.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${e.features[0].properties.funding_org}</b></td>
<td>公开期限: <b>${e.features[0].properties.publish_rule}</b></td>
</tr>
<tr>
<td>开始经度: <b>${e.features[0].properties.start_lon}°</b></td>
<td>结束经度: <b>${e.features[0].properties.end_lon}°</b></td>
</tr>
<tr>
<td>开始纬度: <b>${e.features[0].properties.start_lat}°</b></td>
<td>结束纬度: <b>${e.features[0].properties.end_lat}°</b></td>
</tr>
<tr>
<td>开始时间: <b>${e.features[0].properties.start_time}</b></td>
<td>结束时间: <b>${e.features[0].properties.end_time}</b></td>
</tr>
<tr>
<td>测线深度: <b>${e.features[0].properties.line_deep}m</b></td>
<td>数据状态: <b>${e.features[0].properties.sample_status}</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${e.features[0].properties.sample_remark}</b></td>
</tr>
</table>`;
showPopupDetails({ mapId, coordinate: e.lngLat, description, maxWidth: "450px" });
});
}
export function showSampleStationInfo(layerId, mapId = "homeMap", callbackOnClick = null) {
Vue.config.maps[mapId].on("click", layerId, async (e) => {
// 在左侧列表中高亮对应项
if (callbackOnClick && typeof callbackOnClick === "function") {
callbackOnClick(e.features[0]);
}
// 禁止点击事件穿透 - 判断同一个 event 是否已经触发,用于避免图层内标记点注册的点击事件重复触发(点位分布密集时容易出现)
if (e.defaultPrevented)
return;
// 标记该 event 已触发,禁止点击事件穿透
// if (typeof e.preventDefault === "function")
// e.preventDefault();
let feature = e.features[0];
if (e.features.length > 1)
feature = e.features.filter(f => f.properties.sample_name == e.options.sample_name)[0];
const description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">站位信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${feature.properties.dataset_name}</b></td>
<td>样品编号: <b>${feature.properties.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${feature.properties.funding_org}</b></td>
<td>公开期限: <b>${feature.properties.publish_rule}</b></td>
</tr>
<tr>
<td>站位经度: <b>${feature.properties.sampling_lon}°</b></td>
<td>站位纬度: <b>${feature.properties.sampling_lat}°</b></td>
</tr>
<tr>
<td>采样时间: <b>${feature.properties.sampling_time}</b></td>
<td>采样深度: <b>${feature.properties.sampling_deep}m</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${feature.properties.sample_remark}</b></td>
</tr>
</table>`;
showPopupDetails({ mapId, coordinate: feature.geometry.coordinates, description, maxWidth: "450px" });
});
}
export function showHighlightSampleInfo({ mapId, job_type, sample, layerId, sourceLayerId, callbackOnClick }) {
let description = "";
let coordinate;
if (job_type == "测线作业") {
// // 恢复其它测线的线宽
// // setMapPaintProperty({ mapId, property: "line-width", value: lineWidthExpression });
// // 恢复上一条高亮测线的宽度
// Vue.config.maps[mapId].setPaintProperty(lastLineId, "line-width", lineWidthExpression);
// // 高亮当前选择的测线
// Vue.config.maps[mapId].setPaintProperty(layerId, "line-width", 5);
// if (lastLineId)
// Vue.config.maps[mapId].setFeatureState(
// { sourceId: sourceLayerId, id: lastLineId },
// { hover: false }
// );
// Vue.config.maps[mapId].setFeatureState(
// { sourceId: sourceLayerId, id: layerId },
// { hover: true }
// );
// if (lastLineId)
// Vue.config.maps[mapId].setPaintProperty(lastLineId, 'line-color', '#FFFFFF')
// Vue.config.maps[mapId].setPaintProperty(layerId, 'line-color', '#FFFF00')
// lastLineId = layerId;
coordinate = [(Number(sample.start_lon) + Number(sample.end_lon)) / 2, (Number(sample.start_lat) + Number(sample.end_lat)) / 2];
description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">测线信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${sample.dataset_name}</b></td>
<td>数据样品编号: <b>${sample.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${sample.funding_org}</b></td>
<td>公开期限: <b>${sample.publish_rule}</b></td>
</tr>
<tr>
<td>开始经度: <b>${sample.start_lon}°</b></td>
<td>结束经度: <b>${sample.end_lon}°</b></td>
</tr>
<tr>
<td>开始纬度: <b>${sample.start_lat}°</b></td>
<td>结束纬度: <b>${sample.end_lat}°</b></td>
</tr>
<tr>
<td>开始时间: <b>${sample.start_time}</b></td>
<td>结束时间: <b>${sample.end_time}</b></td>
</tr>
<tr>
<td>测线深度: <b>${sample.line_deep}m</b></td>
<td>数据状态: <b>${sample.sample_status}</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${sample.sample_remark}</b></td>
</tr>
</table>`;
}
else if (job_type == "站位作业") {
coordinate = [sample.sampling_lon, sample.sampling_lat];
description = `
<table class="popup-table" style="min-width:380px;">
<thead>
<tr>
<td colspan="2">站位信息</td>
</tr>
</thead>
<tr>
<td>数据集名称: <b>${sample.dataset_name}</b></td>
<td>样品编号: <b>${sample.sample_name}</b></td>
</tr>
<tr>
<td>赞助机构: <b>${sample.funding_org}</b></td>
<td>公开期限: <b>${sample.publish_rule}</b></td>
</tr>
<tr>
<td>站位经度: <b>${sample.sampling_lon}°</b></td>
<td>站位纬度: <b>${sample.sampling_lat}°</b></td>
</tr>
<tr>
<td>采样时间: <b>${sample.sampling_time}</b></td>
<td>采样深度: <b>${sample.sampling_deep}m</b></td>
</tr>
<tr>
<td colspan="2">备注信息: <b>${sample.sample_remark}</b></td>
</tr>
</table>`;
}
showPopupDetails({ mapId, coordinate, description, maxWidth: "450px", callbackOnClose: callbackOnClick });
}
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 = `
<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({ coordinate, mapId, description, maxWidth: "420px" });
})
});
}
export function showDiveInfo(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 feature = e.features[0];
let coordinate = e.lngLat;
const description = `
<table class="popup-table" style="min-width:280px;">
<thead>
<tr>
<td colspan="2">潜次信息</td>
</tr>
</thead>
<tr>
<td colspan="2">潜次编号: <b>${feature.properties.name}</b></td>
</tr>
<tr>
<td colspan="2">created_at: <b>${feature.properties.created_at}</b></td>
</tr>
</table>`;
showPopupDetails({ mapId, coordinate, description, maxWidth: "300px" });
})
}
export function showPopupDetails({ mapId, coordinate, description, maxWidth = "300px", closeButton = true, closeOnClick = true, options = {}, callbackOnClose }) {
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[mapId]) {
try {
sharedPopup[mapId].setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(map);
} catch (err) {
console.error("设置 popup 失败:", err);
try {
// 万一现有 popup 状态异常,移除并重新创建
sharedPopup[mapId].remove();
sharedPopup[mapId] = null;
} catch (e) {
console.error("移除异常 popup 失败:", e);
}
sharedPopup[mapId] = new window.mapboxgl.Popup(popupOptions)
.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(map);
sharedPopup[mapId].on('close', () => {
// 关闭弹窗时执行回调(如有)
if (callbackOnClose && typeof callbackOnClose === "function") {
callbackOnClose();
}
});
}
} else {
sharedPopup[mapId] = new window.mapboxgl.Popup(popupOptions)
.setLngLat(coordinate)
.setHTML(description)
.setOffset(10)
.setMaxWidth(maxWidth)
.addTo(map);
sharedPopup[mapId].on('close', () => {
// 关闭弹窗时执行回调(如有)
if (callbackOnClose && typeof callbackOnClose === "function") {
callbackOnClose();
}
});
}
}
export async function loadJsonLineFeature(layerId, color, geojson, 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': '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 => {
Vue.config.maps[mapId].setFeatureState({ source: sourceId, id: f.id }, { highlight: false });
});
// 标记匹配要素
Vue.config.maps[mapId].querySourceFeatures(sourceId).forEach(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"// 默认颜色
]);
}