|
@@ -94,7 +94,17 @@
|
|
|
</div>
|
|
</div>
|
|
|
<div class="toolbar-right">当前坐标: {{ currentCoordinate }}</div>
|
|
<div class="toolbar-right">当前坐标: {{ currentCoordinate }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div id="gisMap" ref="mapRef" class="gis-map"></div>
|
|
|
|
|
|
|
+ <GisMapCanvas
|
|
|
|
|
+ ref="gisMapRef"
|
|
|
|
|
+ :points="mapPoints"
|
|
|
|
|
+ :lines="mapLines"
|
|
|
|
|
+ :center="centerPoint"
|
|
|
|
|
+ :zoom="15"
|
|
|
|
|
+ @point-click="openFacilityDetail"
|
|
|
|
|
+ @line-click="openFacilityDetail"
|
|
|
|
|
+ @map-click="detailOpen = false"
|
|
|
|
|
+ @error="mapError = $event"
|
|
|
|
|
+ />
|
|
|
<div v-if="mapError" class="map-error">
|
|
<div v-if="mapError" class="map-error">
|
|
|
<el-result icon="warning" title="供水设施图层加载失败" :sub-title="mapError">
|
|
<el-result icon="warning" title="供水设施图层加载失败" :sub-title="mapError">
|
|
|
<template #extra><el-button type="primary" @click="loadGisData">重试</el-button></template>
|
|
<template #extra><el-button type="primary" @click="loadGisData">重试</el-button></template>
|
|
@@ -130,9 +140,9 @@
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
|
-import { FullScreen, Location, MapLocation, Minus, Plus, RefreshRight, Search } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import { FullScreen, Location, MapLocation, RefreshRight, Search } from '@element-plus/icons-vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
|
-import { convertCoord } from '@/utils/coordTransform'
|
|
|
|
|
|
|
+import GisMapCanvas from '@/components/GisMapCanvas.vue'
|
|
|
import { getWaterFacilityOverview, getWaterFacilityFeatures, getWaterFacilityDetail } from '@/api/pipeNetwork/waterSupply'
|
|
import { getWaterFacilityOverview, getWaterFacilityFeatures, getWaterFacilityDetail } from '@/api/pipeNetwork/waterSupply'
|
|
|
import { normalizeGeoJson, unwrapResponse } from '@/utils/waterSupplyModel'
|
|
import { normalizeGeoJson, unwrapResponse } from '@/utils/waterSupplyModel'
|
|
|
import { getRequestErrorMessage } from '@/utils/waterFacilityQuery'
|
|
import { getRequestErrorMessage } from '@/utils/waterFacilityQuery'
|
|
@@ -140,14 +150,6 @@ import { buildFacilityMapModel, filterFacilityMapModel, findFacilityFeature } fr
|
|
|
|
|
|
|
|
const centerPoint = { lng: 110.386, lat: 28.445 }
|
|
const centerPoint = { lng: 110.386, lat: 28.445 }
|
|
|
|
|
|
|
|
-const MAP_BOUNDS = {
|
|
|
|
|
- southWest: { lng: 110.355, lat: 28.428 },
|
|
|
|
|
- northEast: { lng: 110.410, lat: 28.460 }
|
|
|
|
|
-}
|
|
|
|
|
-const MAP_MIN_ZOOM = 13
|
|
|
|
|
-const MAP_MAX_ZOOM = 19
|
|
|
|
|
-const MAP_DEFAULT_ZOOM = 15
|
|
|
|
|
-
|
|
|
|
|
const defaultLayerState = () => ({
|
|
const defaultLayerState = () => ({
|
|
|
source: true,
|
|
source: true,
|
|
|
plant: true,
|
|
plant: true,
|
|
@@ -180,8 +182,8 @@ const labelMap = {
|
|
|
user: '用水户'
|
|
user: '用水户'
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const mapRef = ref(null)
|
|
|
|
|
const screenRef = ref(null)
|
|
const screenRef = ref(null)
|
|
|
|
|
+const gisMapRef = ref(null)
|
|
|
const searchKeyword = ref('')
|
|
const searchKeyword = ref('')
|
|
|
const currentCoordinate = ref(`${centerPoint.lng}, ${centerPoint.lat}`)
|
|
const currentCoordinate = ref(`${centerPoint.lng}, ${centerPoint.lat}`)
|
|
|
const mapError = ref('')
|
|
const mapError = ref('')
|
|
@@ -196,9 +198,6 @@ const detailError = ref('')
|
|
|
const detailRecord = ref(null)
|
|
const detailRecord = ref(null)
|
|
|
const detailTarget = ref(null)
|
|
const detailTarget = ref(null)
|
|
|
|
|
|
|
|
-let mapInstance = null
|
|
|
|
|
-let overlayClicked = false
|
|
|
|
|
-
|
|
|
|
|
const typeCount = type => {
|
|
const typeCount = type => {
|
|
|
const row = facilityOverview.value.find(item => item.facilityType === type)
|
|
const row = facilityOverview.value.find(item => item.facilityType === type)
|
|
|
return Number(row?.totalCount ?? row?.count ?? 0)
|
|
return Number(row?.totalCount ?? row?.count ?? 0)
|
|
@@ -220,216 +219,32 @@ const detailEntries = computed(() => Object.entries(detailRecord.value || {})
|
|
|
.filter(([, value]) => value !== null && value !== undefined && typeof value !== 'object')
|
|
.filter(([, value]) => value !== null && value !== undefined && typeof value !== 'object')
|
|
|
.map(([label, value]) => ({ label, value: value === '' ? '-' : value })))
|
|
.map(([label, value]) => ({ label, value: value === '' ? '-' : value })))
|
|
|
|
|
|
|
|
|
|
+const mapPoints = computed(() => visibleMapModel.value.points.map(point => ({
|
|
|
|
|
+ ...point,
|
|
|
|
|
+ color: colorMap[point.type] || '#457b9d',
|
|
|
|
|
+ name: point.name || point.code || '供水设施'
|
|
|
|
|
+})))
|
|
|
|
|
+
|
|
|
|
|
+const mapLines = computed(() => visibleMapModel.value.lines.map(line => ({
|
|
|
|
|
+ ...line,
|
|
|
|
|
+ color: colorMap[line.type] || '#fcbf49',
|
|
|
|
|
+ name: line.name || '供水管网'
|
|
|
|
|
+})))
|
|
|
|
|
+
|
|
|
watch(layerState, () => {
|
|
watch(layerState, () => {
|
|
|
- renderMapScene()
|
|
|
|
|
|
|
+ gisMapRef.value?.fitBounds()
|
|
|
}, { deep: true })
|
|
}, { deep: true })
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
await nextTick()
|
|
await nextTick()
|
|
|
await loadGisData()
|
|
await loadGisData()
|
|
|
- waitForBMapGL()
|
|
|
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
document.addEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
onBeforeUnmount(() => {
|
|
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
document.removeEventListener('fullscreenchange', handleFullscreenChange)
|
|
|
- if (mapInstance) {
|
|
|
|
|
- mapInstance.clearOverlays()
|
|
|
|
|
- mapInstance = null
|
|
|
|
|
- }
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-function waitForBMapGL() {
|
|
|
|
|
- if (typeof BMapGL !== 'undefined') {
|
|
|
|
|
- initMap()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let attempts = 0
|
|
|
|
|
- const maxAttempts = 50
|
|
|
|
|
- const timer = setInterval(() => {
|
|
|
|
|
- attempts++
|
|
|
|
|
- if (typeof BMapGL !== 'undefined') {
|
|
|
|
|
- clearInterval(timer)
|
|
|
|
|
- initMap()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (attempts >= maxAttempts) {
|
|
|
|
|
- clearInterval(timer)
|
|
|
|
|
- mapError.value = '百度地图脚本加载超时,请刷新页面重试。'
|
|
|
|
|
- }
|
|
|
|
|
- }, 200)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function initMap() {
|
|
|
|
|
- const el = document.getElementById('gisMap')
|
|
|
|
|
- if (!el) {
|
|
|
|
|
- mapError.value = '地图容器未找到。'
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- mapInstance = new BMapGL.Map('gisMap', { enableMapClick: true })
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('创建地图实例失败:', e)
|
|
|
|
|
- mapError.value = '百度地图初始化失败,请检查AK和网络连接。'
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const [clng, clat] = convertCoord(centerPoint.lng, centerPoint.lat)
|
|
|
|
|
- const centerBMapPoint = new BMapGL.Point(clng, clat)
|
|
|
|
|
- mapInstance.centerAndZoom(centerBMapPoint, MAP_DEFAULT_ZOOM)
|
|
|
|
|
- mapInstance.enableScrollWheelZoom(true)
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('设置地图中心失败:', e)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- if (typeof mapInstance.setMinZoom === 'function') {
|
|
|
|
|
- mapInstance.setMinZoom(MAP_MIN_ZOOM)
|
|
|
|
|
- }
|
|
|
|
|
- if (typeof mapInstance.setMaxZoom === 'function') {
|
|
|
|
|
- mapInstance.setMaxZoom(MAP_MAX_ZOOM)
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn('设置缩放限制失败:', e)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- if (typeof mapInstance.setMaxBounds === 'function') {
|
|
|
|
|
- const sw = new BMapGL.Point(MAP_BOUNDS.southWest.lng, MAP_BOUNDS.southWest.lat)
|
|
|
|
|
- const ne = new BMapGL.Point(MAP_BOUNDS.northEast.lng, MAP_BOUNDS.northEast.lat)
|
|
|
|
|
- mapInstance.setMaxBounds(new BMapGL.Bounds(sw, ne))
|
|
|
|
|
- }
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn('设置地图范围限制失败:', e)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- mapInstance.addEventListener('click', () => {
|
|
|
|
|
- if (overlayClicked) {
|
|
|
|
|
- overlayClicked = false
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- detailOpen.value = false
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- mapInstance.addEventListener('mousemove', (event) => {
|
|
|
|
|
- try {
|
|
|
|
|
- if (event && event.latlng) {
|
|
|
|
|
- currentCoordinate.value = `${event.latlng.lng.toFixed(4)}, ${event.latlng.lat.toFixed(4)}`
|
|
|
|
|
- }
|
|
|
|
|
- } catch (_) { /* ignore mousemove errors */ }
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- renderMapScene()
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.error('渲染地图要素失败:', e)
|
|
|
|
|
- mapError.value = '地图要素渲染失败。'
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function renderMapScene() {
|
|
|
|
|
- if (!mapInstance || typeof BMapGL === 'undefined') return
|
|
|
|
|
-
|
|
|
|
|
- try { mapInstance.clearOverlays() } catch (_) { /* ignore */ }
|
|
|
|
|
- try { drawPipelines() } catch (e) { console.warn('绘制供水管网失败:', e) }
|
|
|
|
|
- try { drawPoints() } catch (e) { console.warn('绘制供水设施失败:', e) }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function drawPipelines() {
|
|
|
|
|
- pipelineData.value.forEach(item => {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!layerState[item.type]) return
|
|
|
|
|
- const pts = item.points.map(p => { const [lng, lat] = convertCoord(p.lng, p.lat); return new BMapGL.Point(lng, lat) })
|
|
|
|
|
- const polyline = new BMapGL.Polyline(pts, {
|
|
|
|
|
- strokeColor: colorMap[item.type],
|
|
|
|
|
- strokeWeight: 6,
|
|
|
|
|
- strokeOpacity: 0.92
|
|
|
|
|
- })
|
|
|
|
|
- try {
|
|
|
|
|
- polyline.addEventListener('click', (e) => {
|
|
|
|
|
- overlayClicked = true
|
|
|
|
|
- e.domEvent && e.domEvent.stopPropagation && e.domEvent.stopPropagation()
|
|
|
|
|
- openFacilityDetail(item)
|
|
|
|
|
- })
|
|
|
|
|
- } catch (_) { /* polyline click listener failed */ }
|
|
|
|
|
- mapInstance.addOverlay(polyline)
|
|
|
|
|
-
|
|
|
|
|
- const rawMid = getMidPoint(item.points)
|
|
|
|
|
- const mid = { lng: convertCoord(rawMid.lng, rawMid.lat)[0], lat: convertCoord(rawMid.lng, rawMid.lat)[1] }
|
|
|
|
|
- addPipeLabel(mid.lng, mid.lat, item.name, colorMap[item.type], item)
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn(`绘制供水管网 ${item.name} 失败:`, e)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function drawPoints() {
|
|
|
|
|
- pointData.value.forEach(item => {
|
|
|
|
|
- try {
|
|
|
|
|
- if (!layerState[item.type]) return
|
|
|
|
|
- const [plng, plat] = convertCoord(item.lng, item.lat)
|
|
|
|
|
- addPointMarker(plng, plat, colorMap[item.type], item.name, 'dot', () => {
|
|
|
|
|
- openFacilityDetail(item)
|
|
|
|
|
- })
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- console.warn(`绘制供水设施 ${item.name} 失败:`, e)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function addPipeLabel(lng, lat, text, color, rawItem) {
|
|
|
|
|
- const point = new BMapGL.Point(lng, lat)
|
|
|
|
|
- const html = `<div class="pipe-label" style="color:${color}">${text}</div>`
|
|
|
|
|
- const label = new BMapGL.Label(html, {
|
|
|
|
|
- position: point,
|
|
|
|
|
- offset: new BMapGL.Size(-28, -8)
|
|
|
|
|
- })
|
|
|
|
|
- label.setStyle({ border: 'none', background: 'transparent', padding: '0' })
|
|
|
|
|
- try {
|
|
|
|
|
- label.addEventListener('click', (e) => {
|
|
|
|
|
- overlayClicked = true
|
|
|
|
|
- e.domEvent && e.domEvent.stopPropagation && e.domEvent.stopPropagation()
|
|
|
|
|
- openFacilityDetail(rawItem)
|
|
|
|
|
- })
|
|
|
|
|
- } catch (_) { /* label click listener failed */ }
|
|
|
|
|
- mapInstance.addOverlay(label)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function addPointMarker(lng, lat, color, name, shape, onClick) {
|
|
|
|
|
- const point = new BMapGL.Point(lng, lat)
|
|
|
|
|
- const size = 22
|
|
|
|
|
- const borderRadius = shape === 'square' ? '3px' : '50%'
|
|
|
|
|
- const html = `<div class="marker-wrap" style="cursor:pointer">
|
|
|
|
|
- <div style="width:${size}px;height:${size}px;background:${color};border-radius:${borderRadius};border:3px solid #fff;box-shadow:0 2px 8px rgba(0,0,0,0.25);margin:0 auto;"></div>
|
|
|
|
|
- <div class="marker-text">${name}</div>
|
|
|
|
|
- </div>`
|
|
|
|
|
- const label = new BMapGL.Label(html, {
|
|
|
|
|
- position: point,
|
|
|
|
|
- offset: new BMapGL.Size(-size / 2 - 3, -size - 8)
|
|
|
|
|
- })
|
|
|
|
|
- label.setStyle({ border: 'none', background: 'transparent', padding: '0' })
|
|
|
|
|
- try {
|
|
|
|
|
- label.addEventListener('click', (e) => {
|
|
|
|
|
- overlayClicked = true
|
|
|
|
|
- e.domEvent && e.domEvent.stopPropagation && e.domEvent.stopPropagation()
|
|
|
|
|
- onClick && onClick()
|
|
|
|
|
- })
|
|
|
|
|
- } catch (_) { /* marker click listener failed */ }
|
|
|
|
|
- mapInstance.addOverlay(label)
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function getMidPoint(points) {
|
|
|
|
|
- const first = points[0]
|
|
|
|
|
- const last = points[points.length - 1]
|
|
|
|
|
- return {
|
|
|
|
|
- lng: (first.lng + last.lng) / 2,
|
|
|
|
|
- lat: (first.lat + last.lat) / 2
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
async function loadGisData() {
|
|
async function loadGisData() {
|
|
|
mapError.value = ''
|
|
mapError.value = ''
|
|
|
dataLoaded.value = false
|
|
dataLoaded.value = false
|
|
@@ -445,7 +260,8 @@ async function loadGisData() {
|
|
|
pipelineData.value = model.lines
|
|
pipelineData.value = model.lines
|
|
|
pointData.value = model.points
|
|
pointData.value = model.points
|
|
|
dataLoaded.value = true
|
|
dataLoaded.value = true
|
|
|
- if (mapInstance && typeof BMapGL !== 'undefined') renderMapScene()
|
|
|
|
|
|
|
+ await nextTick()
|
|
|
|
|
+ gisMapRef.value?.fitBounds()
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
console.error('加载供水GIS数据失败', e)
|
|
console.error('加载供水GIS数据失败', e)
|
|
|
facilityOverview.value = []
|
|
facilityOverview.value = []
|
|
@@ -470,13 +286,8 @@ function handleSearch() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const [mlng, mlat] = convertCoord(matched.lng, matched.lat)
|
|
|
|
|
- if (mapInstance && typeof BMapGL !== 'undefined') {
|
|
|
|
|
- mapInstance.centerAndZoom(new BMapGL.Point(mlng, mlat), 18)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ gisMapRef.value?.locatePoint(matched)
|
|
|
openFacilityDetail(matched)
|
|
openFacilityDetail(matched)
|
|
|
-
|
|
|
|
|
ElMessage.success(`已定位到 ${matched.name}`)
|
|
ElMessage.success(`已定位到 ${matched.name}`)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -516,23 +327,15 @@ async function refreshScene() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function locateCenter() {
|
|
function locateCenter() {
|
|
|
- if (!mapInstance) return
|
|
|
|
|
- const [clng, clat] = convertCoord(centerPoint.lng, centerPoint.lat)
|
|
|
|
|
- mapInstance.centerAndZoom(new BMapGL.Point(clng, clat), MAP_DEFAULT_ZOOM)
|
|
|
|
|
|
|
+ gisMapRef.value?.locatePoint(centerPoint)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function zoomIn() {
|
|
function zoomIn() {
|
|
|
- if (!mapInstance) return
|
|
|
|
|
- const z = mapInstance.getZoom()
|
|
|
|
|
- if (z < MAP_MAX_ZOOM) mapInstance.zoomIn()
|
|
|
|
|
- else ElMessage.info('已放大至最大级别')
|
|
|
|
|
|
|
+ gisMapRef.value?.zoomIn()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function zoomOut() {
|
|
function zoomOut() {
|
|
|
- if (!mapInstance) return
|
|
|
|
|
- const z = mapInstance.getZoom()
|
|
|
|
|
- if (z > MAP_MIN_ZOOM) mapInstance.zoomOut()
|
|
|
|
|
- else ElMessage.info('已缩小至最小级别')
|
|
|
|
|
|
|
+ gisMapRef.value?.zoomOut()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function toggleFullscreen() {
|
|
function toggleFullscreen() {
|
|
@@ -542,15 +345,11 @@ function toggleFullscreen() {
|
|
|
target.requestFullscreen?.()
|
|
target.requestFullscreen?.()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- document.exitFullscreen?.()
|
|
|
|
|
|
|
+ document.exitFullscreen()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function handleFullscreenChange() {
|
|
function handleFullscreenChange() {
|
|
|
- if (!mapInstance) return
|
|
|
|
|
- const [clng, clat] = convertCoord(centerPoint.lng, centerPoint.lat)
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
|
- mapInstance.centerAndZoom(new BMapGL.Point(clng, clat), mapInstance.getZoom())
|
|
|
|
|
- }, 200)
|
|
|
|
|
|
|
+ gisMapRef.value?.locatePoint(centerPoint)
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|