上图使用对江阴区域geojson数据做反转,对江阴以外的区域加遮罩。核心代码:
// 创建遮罩层
const maskLayer = createMaskLayer()
function createMaskLayer() {
// 世界范围坐标 (EPSG:3857)
const worldExtent = [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
// 创建世界范围多边形
const worldPolygon = new Polygon([[
[worldExtent[0], worldExtent[1]],
[worldExtent[2], worldExtent[1]],
[worldExtent[2], worldExtent[3]],
[worldExtent[0], worldExtent[3]],
[worldExtent[0], worldExtent[1]]
]])
// 解析江阴区域GeoJSON
const jiangyinFeatures = new GeoJSON().readFeatures(jiangyinArea.jiangyinBoundery, {
featureProjection: 'EPSG:3857'
})
// 获取江阴区域坐标(只取第一个多边形)
const jiangyinCoords = jiangyinFeatures[0].getGeometry().getCoordinates()
// 创建遮罩Feature:在世界多边形中"挖空"江阴区域
const maskFeature = new Feature({
geometry: new Polygon([
worldPolygon.getCoordinates()[0], // 外环:世界范围
...jiangyinCoords.map(ring => ring.slice().reverse()) // 内环:江阴区域(坐标需反转)
])
})
// 创建矢量图层
return new VectorLayer({
source: new VectorSource({ features: [maskFeature] }),
style: new Style({
fill: new Fill({
color: 'rgba(0, 0, 0, 0.5)' // 半透明黑色遮罩
}),
stroke: new Stroke({
color: 'transparent' // 隐藏边界线
})
}),
zIndex: 1 // 确保在底图之上
})
}
上图使用geoserver加载管线数据瓦片。这样加载出来的瓦片图层非常流畅,可以任意拖拽、放大缩小,几乎没有卡顿。核心代码:
// GeoServer WMS 图层 ---供水管网
let curheight = 300;
const wmsSource = new TileWMS({
url: '/geoserver/work1/wms', // 通过代理转发
params: {
'SERVICE': 'WMS',
'VERSION': '1.1.0',
'REQUEST': 'GetMap',
'LAYERS': 'work1:pipe_network',
'STYLES': 'line_green',
'FORMAT': 'image/png', // 必须用image/png或image/jpeg
// 'FORMAT': 'application/json;type=utfgrid',
'SRS': 'EPSG:3857', // 1.1.1用SRS,1.3.0用CRS
'TILED': false, // 必须为false,否则GeoServer可能返回空图
'TRANSPARENT': true,
// 'CQL_FILTER' 动态设置,初始值为300
'CQL_FILTER': `height > ${curheight}`,
// 'maxFeatures': 5000, //控制显示密度,随机过滤,而不是密度过滤
// 'format_options': 'cluster:true;cluster_distance:60;cluster_max:5', //仅适用于 application/vnd.mapbox-vector-tile 等矢量格式,对 PNG/JPG 栅格格式无效
// 'CQL_FILTER': 'ST_Area(geom) > 10', // 过滤小面积要素,小短线,小面积图形
// 'CQL_FILTER': 'Length(geom) >= 110', // 过滤小短线
// BBOX参数由OpenLayers自动生成并拼接,无需手动设置
},
serverType: 'geoserver',
tileGrid: new TileGrid({
extent: [13356624.394745126, 3722943.2809553347, 13424618.821758036, 3757885.6374882166], // EPSG:3857 全球范围
resolutions: [
611.49622628141, // 级别 8
305.748113140705, // 级别 9
152.8740565703525, // 级别 10
76.43702828517625, // 级别 11
38.21851414258813, // 级别 12
19.109257071294063, // 级别 13
9.554628535647032, // 级别 14
4.777314267823516, // 级别 15
2.388657133911758, // 级别 16
1.194328566955879, // 级别 17
0.5971642834779395, // 级别 18
0.29858214173896974, // 级别 19
0.14929107086948487, // 级别 20
0.0746455354243517, // 级别 21
0.0373227677121758, // 级别 22
0.0186613838560879, // 级别 23
]
}),
crossOrigin: 'anonymous'
// projection: 'EPSG:3857', // OpenLayers 6+不需要此项,view的projection决定
})
wmsLayer = new TileLayer({
source: wmsSource,
opacity: 1,
cacheSize: 256, // 缓存256个WMS瓦片
visible: false // 初始状态显示
})
wmsLayer.set('layerName', 'layerName01')
上图展示根据经纬度得到的点位,撒点并展示详细信息。
所有代码(天地图key需自行申请):
<template>
<div class="tiandi-map-container">
<div id="ol-map" class="ol-map-full"></div>
<!-- 管线点击信息-->
<div v-if="popupVisible" :style="popupStyle" class="ol-popup">
<div>
<strong>管线高度:</strong>
<span>{{ markerInfo.height }}</span>
</div>
<button @click="popupVisible = false" style="margin-top: 8px;">关闭</button>
</div>
<!-- 右上角专题按钮区 -->
<div class="map-topic-panel" ref="typePanel">
<div v-for="topic in topicData" :key="topic.title" class="topic-row">
<div class="topic-title">{{ topic.title }}</div>
<div class="topic-btns-cols">
<div v-for="(btnList,index) in topic.buttons" :key="index" class="topic-btns" >
<div v-for="btn in btnList" :key="btn.label" class="topic-btn"
:class="{ 'topic-btn-active': activeButtons.includes(btn.label) }" @click="handleButtonClick(btn.label)">
<div class="topic-btn-icon">
<img :src="btn.icon" :alt="btn.label" />
</div>
<div class="topic-btn-label">
<span v-for="(line, index) in btn.labelLines" :key="index">{{ line }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import 'ol/ol.css'
import Map from 'ol/Map'
import View from 'ol/View'
import { defaults as defaultInteractions } from 'ol/interaction';
import TileLayer from 'ol/layer/Tile'
import TileWMS from 'ol/source/TileWMS'
import TileGrid from 'ol/tilegrid/TileGrid'
import { fromLonLat } from 'ol/proj'
import { transformExtent } from 'ol/proj';
import XYZ from 'ol/source/XYZ'
import VectorLayer from 'ol/layer/Vector'
import VectorSource from 'ol/source/Vector'
import GeoJSON from 'ol/format/GeoJSON'
import { Fill, Stroke, Style, Text, Icon } from 'ol/style'
import jiangyinArea from '@/utils/jiangyinArea'
import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import {normalMarkers} from "../../../utils/markerImages";
import {searchAlarmList} from "../../../api/screen/map";
import {Polygon} from "ol/geom";
// coordinate变量放在外部,方便调用
let coordinate = null
//状态框位置
const rightPosition = ref(false)
const typePanel = ref(null)
//自定义点标记数据
let autoMarkerList = {
'电导率':[]
}
// 自定义点标记图层
let autoMarkerLayerList = {
'电导率':null
}
// 管线WMS图层变量(全局访问)
let wmsLayer = null
let wmsLayer2 = null
let wmsLayer3 = null
//geoserver图层
let markerList = {
'雨水井':null,
'污水井':null,
'阀门':null,
'排水户':null,
'消防栓':null,
'污水泵站':null,
'加压站':null,
}
// 地图变量
let map = null
function changeStatusPositon(isCloseTop,isCloseSides){
typePanel.value.style.transform = `translateX(${(isCloseTop&&isCloseSides) ? '900px' : '0'})`;
}
function getDianDaoLvStatusDict(status){
if([0,'0'].includes(status)){
return '离线'
}else if([1,'1'].includes(status)){
return '在线'
}else if([2,'2'].includes(status)){
return '维护'
}else if([3,'3'].includes(status)){
return '报警'
}
}
// 按钮点击处理函数
async function handleButtonClick(buttonLabel) {
const index = activeButtons.value.indexOf(buttonLabel)
if (index > -1) {
// 如果按钮已激活,则取消激活
activeButtons.value.splice(index, 1)
} else {
// 如果按钮未激活,则添加激活状态
activeButtons.value.push(buttonLabel)
}
//自定义点标记
if (buttonLabel === '电导率') {
if (activeButtons.value.includes(buttonLabel)) {
let response = await searchAlarmList()
let res = response.data
for(let i=0;i<res.length;i++){
let obj = {
markerImg: normalMarkers.diandaolv,
latLng:res[i].latLng,
deviceName:res[i].deviceName,
status_dict:getDianDaoLvStatusDict(res[i].status),
conductivityLowerLimit:res[i].conductivityLowerLimit,
conductivityUpperLimit:res[i].conductivityUpperLimit,
}
autoMarkerList['电导率'].push(obj)
}
} else {
autoMarkerList['电导率'] = []
}
updateAutoMarkerLayerList()
return;
}
//geoserver点标记
if (!['供水管网', '污水管网', '雨水管网'].includes(buttonLabel)) {
markerList[buttonLabel].setVisible(activeButtons.value.includes(buttonLabel))
return
}
// 控制管线WMS图层显示/隐藏
if (buttonLabel === '供水管网') {
wmsLayer.setVisible(activeButtons.value.includes('供水管网'))
return
}
if (buttonLabel === '污水管网') {
wmsLayer2.setVisible(activeButtons.value.includes('污水管网'))
return
}
if (buttonLabel === '雨水管网') {
wmsLayer3.setVisible(activeButtons.value.includes('雨水管网'))
}
}
// 当前激活的按钮数组
const activeButtons = ref([])
// 专题按钮数据
const topicData = ref([
{
title: '供水专题',
buttons: [
[ {
label: '阀门',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_famen.png', import.meta.url).href,
labelLines: ['阀门']
},
{
label: '供水管网',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_gongshui.png', import.meta.url).href,
labelLines: ['供水管网']
},{
label: '消防栓',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_xiaofangshuan.png', import.meta.url).href,
labelLines: ['消防栓']
},{
label: '加压站',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_jiayazhan.png', import.meta.url).href,
labelLines: ['加压站']
},],
// [ {
// label: '水厂',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_famen.png', import.meta.url).href,
// labelLines: ['水厂']
// },
// {
// label: '水源地',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_gongshui.png', import.meta.url).href,
// labelLines: ['水源地']
// }]
]
},
{
title: '排水专题',
buttons: [
[ {
label: '雨水井',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_yushuijing.png', import.meta.url).href,
labelLines: ['雨水井']
},
{
label: '污水井',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_wushuijing.png', import.meta.url).href,
labelLines: ['污水井']
},
{
label: '污水管网',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_wushui.png', import.meta.url).href,
labelLines: ['污水管网']
},{
label: '雨水管网',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_yushui.png', import.meta.url).href,
labelLines: ['雨水管网']
},],
[{
label: '排水户',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_paishuihu.png', import.meta.url).href,
labelLines: ['排水户']
}, {
label: '污水泵站',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_wushuibengzhan.png', import.meta.url).href,
labelLines: ['污水泵站']
},]
]
},
{
title: '报警专题',
buttons: [
// [
// {
// label: '高品质水',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_gaopinzhishui.png', import.meta.url).href,
// labelLines: ['高品质水']
// },
// {
// label: '测压点',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_ceyadian.png', import.meta.url).href,
// labelLines: ['测压点']
// },
// {
// label: '泵房',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_bengfang.png', import.meta.url).href,
// labelLines: ['泵房']
// },
// ],
// [ {
// label: '施工',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_shigong.png', import.meta.url).href,
// labelLines: ['施工']
// },
// {
// label: '工单',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_gongdan.png', import.meta.url).href,
// labelLines: ['工单']
// },
// {
// label: '重大设备故障',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_shebeiguzhang.png', import.meta.url).href,
// labelLines: ['重大设备', '故障']
// },
// {
// label: '重大工程',
// icon: new URL('@/assets/images/mapImgs/btn_icons/btn_zhongdagongcheng.png', import.meta.url).href,
// labelLines: ['重大工程']
// },
// ],
[ {
label: '电导率',
icon: new URL('@/assets/images/mapImgs/btn_icons/btn_diandaolv.png', import.meta.url).href,
labelLines: ['电导率']
}]
]
}
])
// 弹窗相关响应式变量
const popupVisible = ref(false)
const markerInfo = ref({
height: ''
})
const popupStyle = ref({
position: 'absolute',
left: '0px',
top: '0px',
background: 'white',
border: '1px solid #ccc',
padding: '8px',
borderRadius: '4px',
zIndex: 1000,
minWidth: '120px',
boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
})
/*
天地图默认的坐标系为 EPSG:4326(WGS84,经纬度坐标系),
OpenLayers 默认是 EPSG:3857。
解决方法:确保WMS参数SRS/CRS为EPSG:3857,FORMAT为image/png,且GeoServer图层已正确投影和切片。
*/
const lnglatCenter = [120.284794, 31.841642]
const MIN_ZOOM = 11.49
const MAX_ZOOM = 18
// 节流函数 - 用于WMS参数更新
function throttle(func, limit) {
let inThrottle
return function () {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
// 创建带文本标注的样式函数
function createBoundaryStyle(feature) {
const name = feature.get('name') || feature.get('properties.name')
return new Style({
fill: new Fill({
color: 'rgba(185,224,242, 0.1)' // 半透明填充
}),
stroke: new Stroke({
color: 'rgba(176,225,254, 0.8)', // 边框
width: 2
}),
text: new Text({
text: name,
font: '14px Arial',
fill: new Fill({
color: '#FFF'
}),
offsetY: 0,
placement: 'point'
})
})
}
function initMap() {
// 天地图key轮换 zq
const tdtKeys = [
];
// 计数器用localStorage
let tdtKeyIndex = parseInt(localStorage.getItem('tdtKeyIndex'), 10);
if (isNaN(tdtKeyIndex)) {
tdtKeyIndex = 0;
} else {
tdtKeyIndex = (tdtKeyIndex + 1) % tdtKeys.length;
}
localStorage.setItem('tdtKeyIndex', tdtKeyIndex);
const tdtKey = tdtKeys[tdtKeyIndex];
// 天地图矢量底图服务URL,随机选择天地图二级域名 t0-t7
const tdtSubdomain = Math.floor(Math.random() * 8)
const tdtUrl = `https://t${tdtSubdomain}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${tdtKey}`
const tdtLayer = new TileLayer({
source: new XYZ({
url: tdtUrl,
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
crossOrigin: 'anonymous', // 避免跨域导致瓦片加载异常
}),
cacheSize: 512 // 缓存512个瓦片
})
// 江阴边界填充图层
const boundarySource = new VectorSource({
features: new GeoJSON().readFeatures(jiangyinArea.jiangyinArea, {
featureProjection: 'EPSG:3857'
})
})
const boundaryLayer = new VectorLayer({
source: boundarySource,
style: createBoundaryStyle,
zIndex: 1 // 确保在底图之上,WMS图层之下
})
// 创建遮罩层
const maskLayer = createMaskLayer()
function createMaskLayer() {
// 世界范围坐标 (EPSG:3857)
const worldExtent = [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
// 创建世界范围多边形
const worldPolygon = new Polygon([[
[worldExtent[0], worldExtent[1]],
[worldExtent[2], worldExtent[1]],
[worldExtent[2], worldExtent[3]],
[worldExtent[0], worldExtent[3]],
[worldExtent[0], worldExtent[1]]
]])
// 解析江阴区域GeoJSON
const jiangyinFeatures = new GeoJSON().readFeatures(jiangyinArea.jiangyinBoundery, {
featureProjection: 'EPSG:3857'
})
// 获取江阴区域坐标(只取第一个多边形)
const jiangyinCoords = jiangyinFeatures[0].getGeometry().getCoordinates()
// 创建遮罩Feature:在世界多边形中"挖空"江阴区域
const maskFeature = new Feature({
geometry: new Polygon([
worldPolygon.getCoordinates()[0], // 外环:世界范围
...jiangyinCoords.map(ring => ring.slice().reverse()) // 内环:江阴区域(坐标需反转)
])
})
// 创建矢量图层
return new VectorLayer({
source: new VectorSource({ features: [maskFeature] }),
style: new Style({
fill: new Fill({
color: 'rgba(0, 0, 0, 0.5)' // 半透明黑色遮罩
}),
stroke: new Stroke({
color: 'transparent' // 隐藏边界线
})
}),
zIndex: 1 // 确保在底图之上
})
}
// GeoServer WMS 图层 ---供水管网
let curheight = 300;
const wmsSource = new TileWMS({
url: '/geoserver/work1/wms', // 通过代理转发
params: {
'SERVICE': 'WMS',
'VERSION': '1.1.0',
'REQUEST': 'GetMap',
'LAYERS': 'work1:pipe_network',
'STYLES': 'line_green',
'FORMAT': 'image/png', // 必须用image/png或image/jpeg
// 'FORMAT': 'application/json;type=utfgrid',
'SRS': 'EPSG:3857', // 1.1.1用SRS,1.3.0用CRS
'TILED': false, // 必须为false,否则GeoServer可能返回空图
'TRANSPARENT': true,
// 'CQL_FILTER' 动态设置,初始值为300
'CQL_FILTER': `height > ${curheight}`,
// 'maxFeatures': 5000, //控制显示密度,随机过滤,而不是密度过滤
// 'format_options': 'cluster:true;cluster_distance:60;cluster_max:5', //仅适用于 application/vnd.mapbox-vector-tile 等矢量格式,对 PNG/JPG 栅格格式无效
// 'CQL_FILTER': 'ST_Area(geom) > 10', // 过滤小面积要素,小短线,小面积图形
// 'CQL_FILTER': 'Length(geom) >= 110', // 过滤小短线
// BBOX参数由OpenLayers自动生成并拼接,无需手动设置
},
serverType: 'geoserver',
tileGrid: new TileGrid({
extent: [13356624.394745126, 3722943.2809553347, 13424618.821758036, 3757885.6374882166], // EPSG:3857 全球范围
resolutions: [
611.49622628141, // 级别 8
305.748113140705, // 级别 9
152.8740565703525, // 级别 10
76.43702828517625, // 级别 11
38.21851414258813, // 级别 12
19.109257071294063, // 级别 13
9.554628535647032, // 级别 14
4.777314267823516, // 级别 15
2.388657133911758, // 级别 16
1.194328566955879, // 级别 17
0.5971642834779395, // 级别 18
0.29858214173896974, // 级别 19
0.14929107086948487, // 级别 20
0.0746455354243517, // 级别 21
0.0373227677121758, // 级别 22
0.0186613838560879, // 级别 23
]
}),
crossOrigin: 'anonymous'
// projection: 'EPSG:3857', // OpenLayers 6+不需要此项,view的projection决定
})
// GeoServer WMS 图层 ---污水管网
const wmsSource2 = new TileWMS({
url: '/geoserver/work1/wms', // 通过代理转发
params: {
'SERVICE': 'WMS',
'VERSION': '1.1.0',
'REQUEST': 'GetMap',
'LAYERS': 'work1:pipe_dirty',
'STYLES': 'line_orange',
'FORMAT': 'image/png', // 必须用image/png或image/jpeg
// 'FORMAT': 'application/json;type=utfgrid',
'SRS': 'EPSG:3857', // 1.1.1用SRS,1.3.0用CRS
'TILED': false, // 必须为false,否则GeoServer可能返回空图
'TRANSPARENT': true,
// 'CQL_FILTER' 动态设置,初始值为300
'CQL_FILTER': `height > ${curheight} AND pipe_type = '污水管网'`,
// 'maxFeatures': 5000, //控制显示密度,随机过滤,而不是密度过滤
// 'format_options': 'cluster:true;cluster_distance:60;cluster_max:5', //仅适用于 application/vnd.mapbox-vector-tile 等矢量格式,对 PNG/JPG 栅格格式无效
// 'CQL_FILTER': 'ST_Area(geom) > 10', // 过滤小面积要素,小短线,小面积图形
// 'CQL_FILTER': 'Length(geom) >= 110', // 过滤小短线
// BBOX参数由OpenLayers自动生成并拼接,无需手动设置
},
serverType: 'geoserver',
tileGrid: new TileGrid({
extent: [13356624.394745126, 3722943.2809553347, 13424618.821758036, 3757885.6374882166], // EPSG:3857 全球范围
resolutions: [
611.49622628141, // 级别 8
305.748113140705, // 级别 9
152.8740565703525, // 级别 10
76.43702828517625, // 级别 11
38.21851414258813, // 级别 12
19.109257071294063, // 级别 13
9.554628535647032, // 级别 14
4.777314267823516, // 级别 15
2.388657133911758, // 级别 16
1.194328566955879, // 级别 17
0.5971642834779395, // 级别 18
0.29858214173896974, // 级别 19
0.14929107086948487, // 级别 20
0.0746455354243517, // 级别 21
0.0373227677121758, // 级别 22
0.0186613838560879, // 级别 23
]
}),
crossOrigin: 'anonymous'
// projection: 'EPSG:3857', // OpenLayers 6+不需要此项,view的projection决定
})
// GeoServer WMS 图层 ---污水管网
const wmsSource3 = new TileWMS({
url: '/geoserver/work1/wms', // 通过代理转发
params: {
'SERVICE': 'WMS',
'VERSION': '1.1.0',
'REQUEST': 'GetMap',
'LAYERS': 'work1:pipe_dirty',
'STYLES': 'line_blue',
'FORMAT': 'image/png', // 必须用image/png或image/jpeg
// 'FORMAT': 'application/json;type=utfgrid',
'SRS': 'EPSG:3857', // 1.1.1用SRS,1.3.0用CRS
'TILED': false, // 必须为false,否则GeoServer可能返回空图
'TRANSPARENT': true,
// 'CQL_FILTER' 动态设置,初始值为300
'CQL_FILTER': `height > ${curheight} AND pipe_type = '雨水管网'`,
// 'maxFeatures': 5000, //控制显示密度,随机过滤,而不是密度过滤
// 'format_options': 'cluster:true;cluster_distance:60;cluster_max:5', //仅适用于 application/vnd.mapbox-vector-tile 等矢量格式,对 PNG/JPG 栅格格式无效
// 'CQL_FILTER': 'ST_Area(geom) > 10', // 过滤小面积要素,小短线,小面积图形
// 'CQL_FILTER': 'Length(geom) >= 110', // 过滤小短线
// BBOX参数由OpenLayers自动生成并拼接,无需手动设置
},
serverType: 'geoserver',
tileGrid: new TileGrid({
extent: [13356624.394745126, 3722943.2809553347, 13424618.821758036, 3757885.6374882166], // EPSG:3857 全球范围
resolutions: [
611.49622628141, // 级别 8
305.748113140705, // 级别 9
152.8740565703525, // 级别 10
76.43702828517625, // 级别 11
38.21851414258813, // 级别 12
19.109257071294063, // 级别 13
9.554628535647032, // 级别 14
4.777314267823516, // 级别 15
2.388657133911758, // 级别 16
1.194328566955879, // 级别 17
0.5971642834779395, // 级别 18
0.29858214173896974, // 级别 19
0.14929107086948487, // 级别 20
0.0746455354243517, // 级别 21
0.0373227677121758, // 级别 22
0.0186613838560879, // 级别 23
]
}),
crossOrigin: 'anonymous'
// projection: 'EPSG:3857', // OpenLayers 6+不需要此项,view的projection决定
})
wmsLayer = new TileLayer({
source: wmsSource,
opacity: 1,
cacheSize: 256, // 缓存256个WMS瓦片
visible: false // 初始状态显示
})
wmsLayer.set('layerName', 'layerName01')
wmsLayer2 = new TileLayer({
source: wmsSource2,
opacity: 1,
cacheSize: 256, // 缓存256个WMS瓦片
visible: false // 初始状态显示
})
wmsLayer2.set('layerName', 'layerName02')
wmsLayer3 = new TileLayer({
source: wmsSource3,
opacity: 1,
cacheSize: 256, // 缓存256个WMS瓦片
visible: false // 初始状态显示
})
wmsLayer3.set('layerName', 'layerName03')
//点位
let markerSourceList = {
'雨水井':null,
'污水井':null,
'阀门':null,
'排水户':null,
'消防栓':null,
'污水泵站':null,
'加压站':null,
}
function getMarkerSource(CQL_FILTER,STYLES,LAYERS){
let params = {
'SERVICE': 'WMS',
'VERSION': '1.1.0',
'REQUEST': 'GetMap',
'FORMAT': 'image/png', // 必须用image/png或image/jpeg
// 'FORMAT': 'application/json;type=utfgrid',
'SRS': 'EPSG:3857', // 1.1.1用SRS,1.3.0用CRS
'TILED': false, // 必须为false,否则GeoServer可能返回空图
'TRANSPARENT': true,
// 'CQL_FILTER' 动态设置,初始值为300
// 'maxFeatures': 5000, //控制显示密度,随机过滤,而不是密度过滤
// 'format_options': 'cluster:true;cluster_distance:60;cluster_max:5', //仅适用于 application/vnd.mapbox-vector-tile 等矢量格式,对 PNG/JPG 栅格格式无效
// 'CQL_FILTER': 'ST_Area(geom) > 10', // 过滤小面积要素,小短线,小面积图形
// 'CQL_FILTER': 'Length(geom) >= 110', // 过滤小短线
// BBOX参数由OpenLayers自动生成并拼接,无需手动设置
}
if(CQL_FILTER){
params.CQL_FILTER = CQL_FILTER
}
if(STYLES){
params.STYLES = STYLES
}
if(LAYERS){
params.LAYERS = LAYERS
}
return new TileWMS({
url: '/geoserver/work1/wms', // 通过代理转发
params: params,
serverType: 'geoserver',
tileGrid: new TileGrid({
extent: [13356624.394745126, 3722943.2809553347, 13424618.821758036, 3757885.6374882166], // EPSG:3857 全球范围
resolutions: [
611.49622628141, // 级别 8
305.748113140705, // 级别 9
152.8740565703525, // 级别 10
76.43702828517625, // 级别 11
38.21851414258813, // 级别 12
19.109257071294063, // 级别 13
9.554628535647032, // 级别 14
4.777314267823516, // 级别 15
2.388657133911758, // 级别 16
1.194328566955879, // 级别 17
0.5971642834779395, // 级别 18
0.29858214173896974, // 级别 19
0.14929107086948487, // 级别 20
0.0746455354243517, // 级别 21
0.0373227677121758, // 级别 22
0.0186613838560879, // 级别 23
]
}),
crossOrigin: 'anonymous'
// projection: 'EPSG:3857', // OpenLayers 6+不需要此项,view的projection决定
})
}
markerSourceList['雨水井'] =getMarkerSource('dno=10','point_rain','work1:dirty_point')
markerSourceList['污水井'] =getMarkerSource('dno=13','point_sewage_well','work1:dirty_point')
markerSourceList['阀门'] =getMarkerSource('','point_valve','work1:control')
markerSourceList['排水户'] =getMarkerSource('','point_psh','work1:psh')
markerSourceList['消防栓'] =getMarkerSource('','point_fire_hyd','work1:fire_hyd')
markerSourceList['污水泵站'] =getMarkerSource('SUBTYPE=2','point_dirty_pump','work1:ps_pump')
markerSourceList['加压站'] =getMarkerSource('SUBTYPE=1','point_boosting_station','work1:boosting_station')
function getMarkerLayer(source) {
return new TileLayer({
source: source,
opacity: 1,
cacheSize: 256, // 缓存256个WMS瓦片
visible: false // 初始状态显示
})
}
markerList['雨水井'] =getMarkerLayer(markerSourceList['雨水井'])
markerList['污水井'] =getMarkerLayer(markerSourceList['污水井'])
markerList['阀门'] =getMarkerLayer(markerSourceList['阀门'])
markerList['排水户'] =getMarkerLayer(markerSourceList['排水户'])
markerList['消防栓'] =getMarkerLayer(markerSourceList['消防栓'])
markerList['污水泵站'] =getMarkerLayer(markerSourceList['污水泵站'])
markerList['加压站'] =getMarkerLayer(markerSourceList['加压站'])
markerList['雨水井'].set('layerName', '雨水井')
markerList['污水井'].set('layerName', '污水井')
markerList['阀门'].set('layerName', '阀门')
markerList['排水户'].set('layerName', '排水户')
markerList['消防栓'].set('layerName', '消防栓')
markerList['污水泵站'].set('layerName', '污水泵站')
markerList['加压站'].set('layerName', '加压站')
autoMarkerLayerList['电导率'] = new VectorLayer({
source: new VectorSource(),
})
const extent4326 = [119.75487990670948, 31.606645090235276, 120.86752346270671, 32.08937971257144];
const extent3857 = transformExtent(extent4326, 'EPSG:4326', 'EPSG:3857');
//创建地图
map = new Map({
target: 'ol-map',
layers: [
tdtLayer,
boundaryLayer, // 添加边界图层
maskLayer,//江阴以外的遮罩
wmsLayer,
wmsLayer2,
wmsLayer3,
markerList['雨水井'],
markerList['污水井'],
markerList['阀门'],
markerList['排水户'],
markerList['消防栓'],
markerList['污水泵站'],
markerList['加压站'],
autoMarkerLayerList['电导率']
],
view: new View({
center: fromLonLat(lnglatCenter), // fromLonLat 默认转换为 EPSG:3857
zoom: MIN_ZOOM,
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
projection: 'EPSG:3857',
// 添加以下属性限制拖动范围
extent: extent3857,
constrainOnlyCenter: true, // 只限制中心点,不限制整个视图
constrainResolution: false, // 可选:限制分辨率不能超出范围
smoothExtentConstraint: true // 平滑约束
}),
interactions: defaultInteractions({
// 启用以鼠标位置为中心的缩放
mouseWheelZoom: {
duration: 250,
constrainResolution: false, // 允许中间缩放级别
onFocusOnly: false // 确保不需要聚焦也能缩放
}
})
})
// 节流处理的WMS参数更新函数
const updateWmsParams = throttle((zoom) => {
try {
let curheight = 300;
if (zoom >= 12 && zoom < 13) curheight = 250;
else if (zoom >= 13 && zoom < 14) curheight = 200;
else if (zoom >= 14 && zoom < 15) curheight = 150;
else if (zoom >= 15 && zoom < 16) curheight = 100;
else if (zoom >= 16 && zoom < 17) curheight = 50;
else if (zoom >= 17 && zoom <= 18) curheight = 0;
else if (zoom < 12) curheight = 300;
// 确保WMS源存在且有效
if (wmsSource && typeof wmsSource.updateParams === 'function') {
wmsSource.updateParams({
'CQL_FILTER': `height > ${curheight}`
});
}
if (wmsSource2 && typeof wmsSource2.updateParams === 'function') {
wmsSource2.updateParams({
'CQL_FILTER': `height > ${curheight} AND pipe_type = '污水管网'`
});
}
if (wmsSource3 && typeof wmsSource3.updateParams === 'function') {
wmsSource3.updateParams({
'CQL_FILTER': `height > ${curheight} AND pipe_type = '雨水管网'`
});
}
} catch (error) {
console.warn('WMS参数更新失败:', error);
}
}, 50); // 50ms节流,确保及时更新
// 弹窗位置更新函数,外部可调用
function updatePopupPosition(coord) {
const mapDiv = document.getElementById('ol-map');
if (mapDiv && coord) {
const pixel = map.getPixelFromCoordinate(coord);
// 使弹窗不超出地图容器
let left = pixel[0] + 10;
let top = pixel[1] - 10;
// 限制弹窗在地图容器内
const popupWidth = 180;
const popupHeight = 60;
if (left + popupWidth > mapDiv.offsetWidth) {
left = mapDiv.offsetWidth - popupWidth - 10;
}
if (top + popupHeight > mapDiv.offsetHeight) {
top = mapDiv.offsetHeight - popupHeight - 10;
}
if (left < 0) left = 10;
if (top < 0) top = 10;
popupStyle.value = {
...popupStyle.value,
left: left + 'px',
top: top + 'px'
}
}
}
// 合并的事件监听器:处理缩放限制和WMS参数更新,更新管线详情弹框位置
map.getView().on('change:resolution', function () {
try {
const view = map.getView()
const zoom = view.getZoom()
updateAutoMarkerLayerList()
// 节流更新WMS参数
updateWmsParams(zoom)
if (popupVisible.value && coordinate) {
updatePopupPosition(coordinate);
}
} catch (error) {
console.error('地图缩放事件处理失败:', error);
}
})
// 地图点击事件,获取WMS要素详情
// 修复:使用async/await确保能正确获取source字段,且log能触发
const handleMapClick = throttle(function (evt) {
// 如果WMS图层不可见,则不处理点击事件
if (!wmsLayer.getVisible() && !wmsLayer2.getVisible() && !wmsLayer3.getVisible() && !activeButtons.value.includes('加压站')) {
return;
}
const viewResolution = map.getView().getResolution();
coordinate = evt.coordinate; // 赋值到外部变量
// 构造GetFeatureInfo请求URL
function getUrl(wmsSourceType,QUERY_LAYERS){
return wmsSourceType.getFeatureInfoUrl(
coordinate,
viewResolution,
'EPSG:3857',
{
'REQUEST': 'GetFeatureInfo',
'INFO_FORMAT': 'application/json',
'QUERY_LAYERS': QUERY_LAYERS,
'FEATURE_COUNT': 1
}
);
}
let urls = []
let markerList = []
if(wmsLayer.getVisible()){
const url1 = getUrl(wmsSource,'work1:pipe_network')
urls.push(url1)
}
if(wmsLayer2.getVisible()){
const url2 = getUrl(wmsSource2,'work1:pipe_dirty')
urls.push(url2)
}
if(wmsLayer3.getVisible()){
const url3 = getUrl(wmsSource3,'work1:pipe_dirty')
urls.push(url3)
}
// if(activeButtons.value.includes('加压站')){
// const markerListJiaya = getUrl(markerSourceList['加压站'],'work1:boosting_station')
// markerList.push(markerListJiaya)
// }
handlerUrl(urls,markerList)
}, 50); // 50ms防抖
const handlerUrl = async (urls,markerList) => {
try {
// 遍历所有管线
for (const url of urls) {
if (!url) continue; // 跳过无效URL
const response = await fetch(url);
const data = await response.json();
// 检查是否有要素信息
if (data && data.features && data.features.length > 0) {
const feature = data.features[0];
// 显示弹窗(使用对应图层的属性)
markerInfo.value.height = feature.properties?.height ?? '--';
updatePopupPosition(coordinate); // 更新弹窗位置
popupVisible.value = true;
return
}
}
//遍历标记
for (const marker of markerList) {
if (!marker) continue; // 跳过无效URL
const response = await fetch(marker);
const data = await response.json();
// 检查是否有要素信息
if (data && data.features && data.features.length > 0) {
const feature = data.features[0];
// console.log(feature)
}
}
// 所有图层都无结果时关闭弹窗
popupVisible.value = false;
} catch (error) {
popupVisible.value = false;
}
}
map.on('singleclick', handleMapClick);
// 在点击地图其他地方关闭弹窗时也清除高亮
map.on('pointerdown', function (evt) {
// 如果不是单击(即不是singleclick事件),关闭弹窗并清除高亮
if (!evt.originalEvent || evt.originalEvent.type !== 'click') {
popupVisible.value = false;
}
});
updateAutoMarkerLayerList()
// 点击地图输出经纬度坐标
// map.on('singleclick', function (evt) {
// // evt.coordinate 是投影坐标(EPSG:3857),需要转换为经纬度
// const lonLat = toLonLat(evt.coordinate);
// console.log('点击位置经纬度:', lonLat);
// });
}
function updateAutoMarkerLayerList(){
for(let i=0;i<Object.keys(autoMarkerList).length;i++){
let buttonLabel = Object.keys(autoMarkerList)[i]
//自定义点标记图层
if(!autoMarkerList[buttonLabel]){
return
}
autoMarkerLayerList[buttonLabel].getSource().clear()
if(autoMarkerList[buttonLabel].length>0){
// 添加标记点到图层
autoMarkerList[buttonLabel].forEach(item => {
if(!item.latLng || !item.latLng.includes(',')){
return
}
const show = map.getView().getZoom() >= 15;
// 创建 Feature 时,geometry 单独传递
let latlng = item.latLng.split(',')
const marker = new Feature({
geometry: new Point(fromLonLat([Number(latlng[0]),Number(latlng[1])])), // 经度, 纬度记得转换
});
// 设置标记样式
marker.setStyle(new Style({
image: new Icon({
anchor: [0.5, 0.5], // 图标中心对齐坐标点,必须这样,否则偏移巨大
src: item.markerImg, // 图标URL
// size: [200, 200], // 指明图片原始大小,不一致直接不能正常显示
scale: 0.3,
}),
text: show ? new Text({
text: getDetailText(buttonLabel,item),
offsetY: -120,
font: '14px Microsoft YaHei',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({ color: '#fff', width: 2 }),
backgroundFill: new Fill({ color: 'rgba(255,255,255,0.8)' }),
padding: [8, 10, 8, 10],
textBaseline: 'top',
overflow: true,
maxLines: 5,
lineHeight: 1.4
}) : null
}));
autoMarkerLayerList[buttonLabel].getSource().addFeature(marker);
});
}
}
}
// // 辅助函数生成格式化文本
function getDetailText(buttonLabel,f) {
if(buttonLabel === '电导率'){
return [
`【${f.deviceName}】`,
`状态: ${f.status_dict}`,
`下限: ${f.conductivityLowerLimit}`,
`上限: ${f.conductivityUpperLimit}`,
].join('\n');
}
}
onMounted(() => {
// 确保DOM元素完全加载
setTimeout(() => {
initMap();
}, 100);
})
defineExpose({
changeStatusPositon
})
</script>
<style>
.tiandi-map-container {
width: 100%;
height: 100%;
position: relative;
}
.ol-map-full {
width: 100%;
height: 100%;
position: relative;
}
.ol-popup {
pointer-events: auto;
}
.map-topic-panel {
position: absolute;
top: 0px;
right: 1100px;
width: 338px;
height: 340px;
background: linear-gradient(90deg, rgba(10, 41, 88, 0.3) 0%, rgba(20, 49, 90, 0.8) 52%, rgba(14, 33, 61, 0.3) 100%);
border-radius: 12px;
overflow-y: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
z-index: 2000;
padding: 16px 12px 12px 12px;
display: flex;
flex-direction: column;
gap: 8px;
transition: all 0.6s;
}
.map-topic-panel::-webkit-scrollbar {
width: 4px;
background: transparent;
}
.map-topic-panel::-webkit-scrollbar-thumb {
background: rgba(25, 78, 108, 0.5);
border-radius: 2px;
}
.topic-row {
margin-bottom: 6px;
}
.topic-title {
color: #fff;
font-size: 15px;
font-weight: bold;
margin-bottom: 8px;
letter-spacing: 1px;
}
.topic-btns-cols{
display: flex;
flex-direction: column;
gap: 8px;
}
.topic-btns {
display: flex;
flex-direction: row;
gap: 8px;
}
.topic-btn {
width: 72px;
height: 74px;
background: rgba(15, 53, 75, 0.3);
border-radius: 7px;
border: 1px solid rgba(25, 78, 108, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: box-shadow 0.2s;
}
.topic-btn:hover {
box-shadow: 0 0 8px 2px rgba(20, 49, 90, 0.18);
}
.topic-btn-active {
background: rgba(0, 50, 108, 0.8) !important;
border-radius: 7px;
border: 1px solid #00CEFF !important;
}
.topic-btn-icon {
width: 28px;
height: 28px;
background: rgba(53, 155, 255, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6px;
}
.topic-btn img {
width: 16px;
height: 16px;
display: block;
}
.topic-btn-label {
color: #fff;
font-size: 14px;
text-align: center;
line-height: 1.2;
min-height: 28px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* 保证多行和单行按钮视觉对齐 */
}
.topic-btn-label span {
display: block;
margin: 0;
padding: 0;
}
/* 状态筛选区域样式 */
.status-filter-panel {
position: absolute;
top: 316px;
right: 1100px;
width: 338px;
height: 100px;
background: linear-gradient(90deg, rgba(10, 41, 88, 0.3) 0%, rgba(20, 49, 90, 0.8) 52%, rgba(14, 33, 61, 0.3) 100%);
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
z-index: 2000;
padding: 16px 12px 12px 12px;
transition: all 0.6s;
}
.status-title {
color: #fff;
font-size: 15px;
font-weight: bold;
margin-bottom: 12px;
letter-spacing: 1px;
}
.status-checkboxes {
display: flex;
flex-direction: row;
gap: 8px;
}
.status-checkbox {
display: flex;
align-items: center;
cursor: pointer;
color: #fff;
font-size: 14px;
}
.status-checkbox input[type="checkbox"] {
margin-right: 8px;
width: 16px;
height: 16px;
accent-color: rgba(53, 155, 255, 0.5);
}
.status-label {
user-select: none;
}
</style>