添加栖息地底图

This commit is contained in:
扈兆增 2026-07-31 11:19:35 +08:00
parent 1d1c0a89d8
commit 6a14a4bed0
7 changed files with 160 additions and 20 deletions

View File

@ -11,9 +11,9 @@ VITE_APP_BASE_URL = 'http://localhost:8093'
# 测试环境
# VITE_APP_BASE_URL = 'http://172.16.21.142:8093'
# 汤伟
# VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
VITE_APP_BASE_URL = 'http://10.84.111.235:8093'
# 李林
VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
# VITE_APP_BASE_URL = 'http://10.84.111.25:8093'
## 开发环境 附件服务地址
VITE_APP_ATTACHMENT_URL = 'https://211.99.26.225:12125'

View File

@ -191,7 +191,7 @@ export const layerConfig2Flat = (data: any): any[] => {
arr.forEach((item: any) => {
const { type } = item;
if (type) {
if (type == 'GISMap') {
if (type == 'GISMap' || type == 'geojson') {
if (item?.children && item.children.length > 0) {
f(item.children);
} else {

View File

@ -11,7 +11,6 @@ import Fill from 'ol/style/Fill';
import Stroke from 'ol/style/Stroke';
import Icon from 'ol/style/Icon';
import Text from 'ol/style/Text';
import Circle from 'ol/style/Circle';
import WMTS from 'ol/source/WMTS';
import WMTSTileGrid from 'ol/tilegrid/WMTS';
import { get as getProjection, fromLonLat } from 'ol/proj';
@ -77,6 +76,7 @@ export class MapOl implements MapInterface {
private currentClipGeoJson: any | null = null;
private clipRequestController: AbortController | null = null;
private hoveredFeatureId: string | number | null = null;
private wfsHighlightLayer: VectorLayer | null = null;
private popupManager: PopupManager;
private regionMaskManager: RegionMaskManager;
private isBatchPopupMode = false;
@ -166,6 +166,19 @@ export class MapOl implements MapInterface {
this.popupManager.setMap(this.map);
this.regionMaskManager.setContext(this.map, this.view);
// WFS 河段悬停高亮图层:与 demo 项目保持一致,命中要素复制到该图层以加深色描边高亮
this.wfsHighlightLayer = new VectorLayer({
source: new VectorSource(),
style: new Style({
stroke: new Stroke({
color: '#FFFF00',
width: 2
})
}),
zIndex: 20
});
this.map.addLayer(this.wfsHighlightLayer);
this.view.on('change:resolution', () => {
this.handleZoomChange();
});
@ -193,12 +206,69 @@ export class MapOl implements MapInterface {
});
this.map.on('pointermove', evt => {
// fh_qxd 河段悬停高亮
let hitLayer: any = null;
const hitFeature = this.map.forEachFeatureAtPixel(
evt.pixel,
(feature, layer) => {
hitLayer = layer;
return feature;
},
{
hitTolerance: 0,
layerFilter: layer => layer.get('key') === 'fh_qxd'
}
);
// 高亮图层存在时,命中要素复制到高亮图层,未命中则清空
if (this.wfsHighlightLayer) {
const highlightSource = this.wfsHighlightLayer.getSource();
if (hitFeature) {
// 根据命中图层的底色动态调整高亮颜色(统一加深)
const baseColor = hitLayer?.get('bjColor') || '';
// 读取命中图层原始描边宽度,保持高亮只变颜色不加粗
let highlightWidth = 5;
const layerStyle = hitLayer?.getStyle?.();
if (
layerStyle &&
typeof layerStyle === 'object' &&
!Array.isArray(layerStyle)
) {
const strokeWidth = (layerStyle as Style)
.getStroke?.()
?.getWidth?.();
if (typeof strokeWidth === 'number' && strokeWidth > 0) {
highlightWidth = strokeWidth;
}
}
this.wfsHighlightLayer.setStyle(
new Style({
stroke: new Stroke({
color: '#37ff00ff',
width: 4
})
})
);
highlightSource?.clear();
highlightSource?.addFeature(hitFeature);
} else {
highlightSource?.clear();
}
}
// 命中河段时直接切换鼠标样式(与 demo 一致popup 是节流回调不能依赖它)
const targetElement = this.map?.getTargetElement() as HTMLElement;
if (targetElement) {
targetElement.style.cursor = hitFeature ? 'pointer' : '';
}
this.popupManager.handlePointerMove(evt.pixel, payload => {
this.hoveredFeatureId = payload.hoveredId;
const targetElement = this.map?.getTargetElement() as HTMLElement;
if (targetElement) {
targetElement.style.cursor = payload.hoveredId ? 'pointer' : '';
// 命中河段或 hover popup 命中锚点时显示 pointer 光标
targetElement.style.cursor =
payload.hoveredId || hitFeature ? 'pointer' : '';
}
// 批量 popup 模式下不触发 hover popup
@ -1572,19 +1642,66 @@ export class MapOl implements MapInterface {
} else {
wmtsLayer.setZIndex(-100);
}
wmtsLayer.set('key', layer.key);
wmtsLayer.type = 'layer';
wmtsLayer.setVisible(isShow);
this.layerRegistry.set(layer.key, wmtsLayer);
this.map.addLayer(wmtsLayer);
layer._layer = wmtsLayer;
} else if (layer.type === 'wfs') {
if (!this.view) return;
// 先加载数据,在原始 GeoJSON 层面对坐标做 4326 有效性筛选,删除混入的异常投影数据
const isCoordIn4326Range = (lon: number, lat: number) =>
lon >= -180 && lon <= 180 && lat >= -90 && lat <= 90;
const isValidGeoJsonFeature = (feature: any): boolean => {
const geom = feature?.geometry;
if (!geom) return false;
const flat = geom.coordinates.flat(Infinity) as number[];
for (let i = 0; i < flat.length; i += 2) {
if (!isCoordIn4326Range(flat[i], flat[i + 1])) return false;
}
return true;
};
const vectorSource = new VectorSource();
// 先加载数据,过滤异常坐标,再创建图层添加到地图
fetch(layer.url)
.then(res => res.json())
.then(data => {
data.features = (data.features || []).filter(isValidGeoJsonFeature);
const features = new GeoJSON().readFeatures(data, {
// dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857' // 确保转换到地图使用的投影
});
vectorSource.addFeatures(features);
})
.catch(err => console.error(`WFS 图层加载失败 [${layer.key}]:`, err));
const wfsLayer = new VectorLayer({
source: vectorSource,
style: new Style({
stroke: new Stroke({
color: '#038303',
width: 3
})
}),
zIndex: -98,
visible: layer.visible !== false
});
wfsLayer.set('bjColor', layer.color);
wfsLayer.set('key', layer.key);
wfsLayer.setVisible(isShow);
this.layerRegistry.set(layer.key, wfsLayer);
this.map.addLayer(wfsLayer);
layer._layer = wfsLayer;
} else if (layer.type == 'raster-dem') {
const tileLayer = new TileLayer({
source: new XYZ({
url: layer.url,
wrapX: true,
wrapX: false,
crossOrigin: 'anonymous'
})
});
tileLayer.set('key', layer.key);
tileLayer.setZIndex(-100);
tileLayer.setVisible(isShow);
tileLayer.type = 'layer';

View File

@ -91,9 +91,8 @@ export const useMapOrchestrator = () => {
// 备注:统一把当前页面中的 GIS 基础图层初始化到地图实例,避免在图层树组件里做配置解析和加层编排。
const ensureBaseLayersInitialized = (configs: any[] = []) => {
const flatLayerConfigs = layerConfig2Flat(configs);
flatLayerConfigs.forEach((item: any) => {
if (item.type !== 'GISMap' || !item.key) {
if ((item.type !== 'GISMap' && item.type !== 'geojson') || !item.key) {
return;
}
@ -110,6 +109,12 @@ export const useMapOrchestrator = () => {
return;
}
// 备注:保证 config.key 与树节点 key 一致,否则 layerRegistry 会注册到错误/空的 key 下,
// 导致图层树勾选时 hasBaseLayer 查不到而重复创建图层。
if (item.config.key !== item.key) {
item.config = { ...item.config, key: item.key };
}
if (item.key === 'customBaseLayer') {
const serializableBaseLayerConfig = {
key: item.config?.key,
@ -134,7 +139,7 @@ export const useMapOrchestrator = () => {
mapClass.controlBaseLayerTreeShowAndHidden(
item.key,
item.config?.id || item.key,
item.config?.key || item.key,
item.checked === 1
);
});

View File

@ -781,7 +781,10 @@ export const useMapStore = defineStore('map', () => {
restoreLayerLegendVisibility(key);
mapClass.mdLayerTreeShowOrHidden(key, true);
}
} else if (layerItem && layerItem.type === 'GISMap') {
} else if (
layerItem &&
(layerItem.type === 'GISMap' || layerItem.type === 'geojson')
) {
// 处理 GISMap 类型的图层:勾选时若地图实例中不存在,则先重新加载再控制显隐。
if (key && key !== '-' && key !== 'powerBaseStation') {
const shouldBeVisible = checkKeys.includes(key);
@ -900,7 +903,8 @@ export const useMapStore = defineStore('map', () => {
}
}
const allPointData = mapDataStore.rebuildPointDataFromCache(allLayerKeys);
const allPointData =
mapDataStore.rebuildPointDataFromCache(allLayerKeys);
attachNearbyPointRuntimeMeta(allPointData);
const runtimeCheckedKeys = getRuntimeCheckedLayerKeys();
const treeCheckedKeys = mapConfigStore.extractCheckedLayerKeys(
@ -1084,7 +1088,7 @@ export const useMapStore = defineStore('map', () => {
legendItem.nameEn
);
if (layerItem?.type === 'GISMap') {
if (layerItem?.type === 'GISMap' || layerItem?.type === 'geojson') {
mapClass.controlBaseLayerTreeShowAndHidden(
layerKey,
layerItem.config?.id || layerKey,

View File

@ -1,10 +1,20 @@
<script setup lang="ts">
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
import RightDrawer from "@/components/RightDrawer/index.vue";
import QiXiDiBaoHuGongZuoKaiZhan from "@/modules/qixidibaohugongzuokaizhanQK/index.vue"
import QixidijchuXx from "@/modules/qixidijchuXx/index.vue"
import QiXiDiShuiWenBianHua from "@/modules/qixidishuiwenbianhua/index.vue"
import QiXiDiLiuLiangBianHua from "@/modules/qixidiliuliangbianhua/index.vue"
import JidiSelectorMod from '@/modules/jidiSelectorMod.vue';
import RightDrawer from '@/components/RightDrawer/index.vue';
import QiXiDiBaoHuGongZuoKaiZhan from '@/modules/qixidibaohugongzuokaizhanQK/index.vue';
import QixidijchuXx from '@/modules/qixidijchuXx/index.vue';
import QiXiDiShuiWenBianHua from '@/modules/qixidishuiwenbianhua/index.vue';
import QiXiDiLiuLiangBianHua from '@/modules/qixidiliuliangbianhua/index.vue';
import { ref } from 'vue';
const addMapLayer = () => {
console.log('叠加栖息地图层');
};
const showSingle = () => {
console.log('单个展示');
};
</script>
<template>
@ -14,6 +24,10 @@ import QiXiDiLiuLiangBianHua from "@/modules/qixidiliuliangbianhua/index.vue"
</div>
<div class="rightContent">
<RightDrawer>
<a-button type="primary" @click="addMapLayer">
叠加栖息地图层
</a-button>
<a-button type="primary" @click="showSingle"> 单个展示 </a-button>
<QiXiDiBaoHuGongZuoKaiZhan />
<QiXiDiShuiWenBianHua />
<QiXiDiLiuLiangBianHua />

View File

@ -79,7 +79,7 @@
/>
</a-form-item>
<a-form-item
label="gisJson"
label="地图参数"
name="paramJson"
v-if="isInterface && formData.type === 'geojson'"
>