地图服务相关逻辑

This commit is contained in:
hym 2025-12-25 16:45:57 +08:00
parent 7978bd3a61
commit f2015af2b7
1 changed files with 236 additions and 0 deletions

236
src/utils/mapbox-utils.js Normal file
View File

@ -0,0 +1,236 @@
import Vue from 'vue'
/**
* 获取所有图层信息
* @param {mapboxgl.Map} map - Mapbox 地图实例
* @returns {Array} 图层信息数组
*/
export function getAllLayers(map) {
if (!map || !map.getStyle()) {
return [];
}
const style = map.getStyle();
return style.layers || [];
}
/**
* 安全地设置图层属性
* @param {mapboxgl.Map} map - Mapbox 地图实例
* @param {string} layerId - 图层ID
* @param {string} property - 属性名
* @param {any} value - 属性值
* @returns {boolean}
*/
export function safeSetPaintProperty(map, layerId, property, value) {
if (!map.getLayer(layerId)) {
console.warn(`图层 ${layerId} 不存在,无法设置属性`);
return false;
}
try {
map.setPaintProperty(layerId, property, value);
return true;
} catch (error) {
console.error(`设置图层 ${layerId} 属性 ${property} 失败:`, error);
return false;
}
}
export function handleWheel(mapId = "home") {
document.addEventListener("keydown", () => {
// 允许鼠标滚轮缩放
Vue.config.maps[mapId].scrollZoom.enable();
}, { once: false, passive: false });
document.addEventListener("keyup", () => {
// 禁止鼠标滚轮缩放
Vue.config.maps[mapId].scrollZoom.disable();
}, { once: false, passive: false });
}
export function initSprites(mapId = "home") {//../../../static/images/logo.png
for (const sprite of Vue.config.sprites) {
// Vue.config.maps[mapId].loadImage(`${Vue.config.baseUrl}/oe/${Vue.config.wwwrootBaseUrl}/sprites/${sprite}`, function (error, image) {
Vue.config.maps[mapId].loadImage(`./static/sprites/${sprite}`, function (error, image) {
if (error) throw error;
if (!Vue.config.maps[mapId].hasImage(sprite))
// 为图像启用 sdf { sdf: true },启用的 sdf 的图像,在 Symbol 图层中就能够通过 icon-color 属性来重新渲染图像的颜色。
Vue.config.maps[mapId].addImage(sprite, image, { sdf: false });
});
}
}
export async function extractWMTSBoundsInfo(xmlContent) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlContent, "text/xml");
const featureTypes = xmlDoc.getElementsByTagName("Layer");
const result = [];
for (let i = 0; i < featureTypes.length; i++) {
const featureType = featureTypes[i];
// 提取 Name
const nameElement = featureType.getElementsByTagName("ows:Identifier")[0];
const name = nameElement ? nameElement.textContent || "" : "";
// 提取 WGS84BoundingBox
const wgs84BoundingBox = featureType.getElementsByTagName("ows:WGS84BoundingBox")[0];
if (wgs84BoundingBox) {
const lowerCorner = wgs84BoundingBox.getElementsByTagName("ows:LowerCorner")[0].textContent;
const upperCorner = wgs84BoundingBox.getElementsByTagName("ows:UpperCorner")[0].textContent;
if (name) {
result.push({
layerName: name,
bounds: [Number(lowerCorner.split(" ")[0]), Number(lowerCorner.split(" ")[1]), Number(upperCorner.split(" ")[0]), Number(upperCorner.split(" ")[1])]
});
}
}
}
return result;
}
/**
* 触发指定图层的点击事件
* @param {mapboxgl.Map} map - Mapbox地图实例
* @param {string} layerId - 要触发的图层ID
* @param {Array} lnglat - 经纬度 [经度, 纬度]
* @param {Object} options - 附加选项
*/
export async function triggerLayerClick(mapId, sourceLayerId, layerId, lnglat, options = {}) {
if (!Vue.config.maps[mapId] || !layerId || !lnglat) {
console.error('缺少必要参数');
return null;
}
const [lng, lat] = Array.isArray(lnglat) ? lnglat : [lnglat.lng, lnglat.lat];
// 1. 确保地图和图层存在
if (!Vue.config.maps[mapId].getLayer(layerId)) {
console.error(`图层 ${layerId} 不存在`);
return null;
}
// 2. 将经纬度转换为屏幕坐标
const point = Vue.config.maps[mapId].project([lng, lat]);
// 3. 查询该点/源下的图层要素
const bbox = [
[point.x - 1, point.y - 1],
[point.x + 1, point.y + 1]
];
const map = Vue.config.maps[mapId];
console.log("layerId: ", layerId, " sourceLayerId: ", sourceLayerId, " options: ", options);
// 尝试从图层获取 source id一些图层的 source 名称与 layerId 不同)
const layerObj = map.getLayer(layerId);
const sourceId = (layerObj && layerObj.source) ? layerObj.source : layerId;
const source = map.getSource(sourceId);
// 在浏览器控制台或 triggerLayerClick 中运行(替换 sourceId/sourceLayerName
const feats = map.querySourceFeatures(sourceId, { sourceLayer: "sample_station_TS-46-1_Ship_CTDI_SY" }) || [];
console.log(sourceId,">>>>>>>>>>>>>>>>>>>>sample_station_TS-46-1_Ship_CTDI_SY",' ======sample of features:', feats.slice(0, 5));
console.log('props keys:', feats.slice(0, 5).map(f => Object.keys(f.properties || {})));
let relatedTracks = map.querySourceFeatures(sourceId, {
sourceLayer: sourceId,
// 支持的过滤条件:==, !=, >, >=, <, <=, in, !in, all, any, none, has, !has
filter: ['==', ['get', 'sample_name'], options.sample_name]
});
console.log("relatedTracks: ", relatedTracks);
// 打印样式中与该 source 关联的所有 source-layer便于调试 vector tiles 的 layer 名称)
try {
const styleLayers = map.getStyle().layers || [];
const sourceLayerNames = new Set();
styleLayers.forEach(l => {
if (l.source === sourceId && l['source-layer']) sourceLayerNames.add(l['source-layer']);
});
console.log('candidate source-layer names for', sourceId, Array.from(sourceLayerNames));
} catch (err) {
console.warn('failed to enumerate style layers for debugging:', err);
}
// 使用表达式 ['==', ['get', prop], value] 来做精确匹配
const propName = 'sample_name';
const propValue = options.sample_name;
const filterExpr = ['==', ['get', propName], propValue];
// const filterExpr = ['==', propName, propValue];
let features = [];
try {
if (source) {
// 如果 source 尚未加载完,等待一次 sourcedata有时 vector tiles 尚未请求完成)
if (!map.isSourceLoaded(sourceId)) {
console.log('source not loaded yet, waiting for sourcedata:', sourceId);
await new Promise((resolve) => {
const onData = (e) => {
if (e && e.sourceId === sourceId && map.isSourceLoaded(sourceId)) {
map.off('sourcedata', onData);
resolve();
}
};
// 超时保护2秒后继续
const timeout = setTimeout(() => {
map.off('sourcedata', onData);
resolve();
}, 2000);
map.on('sourcedata', onData);
});
}
console.log("sourceId: ", sourceId, " sourceLayerId: ", sourceLayerId, " options: ", options);
// 优先使用 querySourceFeatures对 vector/geojson 源性能更好)
features = map.querySourceFeatures(sourceId, { sourceLayer: sourceLayerId, filter: filterExpr }) || [];
}
} catch (err) {
console.warn('querySourceFeatures failed:', err);
}
// 如果通过 source 查询不到再退回到渲染层查询rendered features作为兜底
if (!features || features.length === 0) {
try {
features = map.queryRenderedFeatures(bbox, { layers: [layerId], filter: filterExpr }) || [];
} catch (err) {
console.warn('queryRenderedFeatures fallback failed:', err);
features = [];
}
}
console.log('resolved sourceId:', sourceId, 'found features:', features.length);
// 4. 创建事件对象
const eventObject = {
lngLat: { lng, lat },
point: point,
features: features,
options: options,
originalEvent: createSimulatedEvent(point),
target: Vue.config.maps[mapId].getCanvas(),
type: 'click',
layerId: layerId,
timestamp: Date.now()
};
// 5. 触发图层特定的点击事件
Vue.config.maps[mapId].fire('click', eventObject, { layerId: layerId });
return {
event: eventObject,
features: features,
success: true
};
}
function createSimulatedEvent(point) {
return {
type: 'click',
clientX: point.x,
clientY: point.y,
preventDefault: () => { },
stopPropagation: () => { }
};
}