Переглянути джерело

Merge remote-tracking branch 'origin/master'

wwh 1 місяць тому
батько
коміт
5d656760b2

+ 5 - 0
ruoyi-screen/.env.development

@@ -19,6 +19,9 @@ VITE_WEATHER_CITY=巴青
19 19
 VITE_GIS_BASE_URL=https://bqxm.alafarms.com/agis-api
20 20
 VITE_GIS_ACCESS_TOKEN=8XughK9JVapV1pqaE7HnxfHiKGrLy21L9LbsKskL
21 21
 VITE_GIS_WMTS_LAYER=baqing_wp
22
+VITE_GIS_WMTS_LAYER2=baqing_wp2
23
+# wp 为底层;zoom > 16 时 wp2 叠加在 wp 上方(不替换 wp)
24
+# VITE_GIS_WMTS_LAYER_SWITCH_ZOOM=16
22 25
 # 瓦片边长:256 
23 26
 VITE_GIS_TILE_SIZE=256
24 27
 VITE_GIS_DEFAULT_CENTER=94.054094,31.920244
@@ -28,3 +31,5 @@ VITE_GIS_MIN_ZOOM=7
28 31
 VITE_GIS_MAX_ZOOM=20
29 32
 # 瓦片请求最高 TileMatrix;与 MAX_ZOOM 相同则每级都请求新瓦片,低 1 级则顶级用放大显示
30 33
 VITE_GIS_TILE_MAX_ZOOM=19
34
+# POI 查询方式:1=中心点周边,2=多边形区域(默认 2)
35
+VITE_GIS_POI_SEARCH_MODE=1

+ 5 - 1
ruoyi-screen/.env.production

@@ -19,12 +19,16 @@ VITE_WEATHER_CITY=巴青
19 19
 VITE_GIS_BASE_URL=https://bqxm.alafarms.com/agis-api
20 20
 VITE_GIS_ACCESS_TOKEN=8XughK9JVapV1pqaE7HnxfHiKGrLy21L9LbsKskL
21 21
 VITE_GIS_WMTS_LAYER=baqing_wp
22
+VITE_GIS_WMTS_LAYER2=baqing_wp2
23
+# VITE_GIS_WMTS_LAYER_SWITCH_ZOOM=16
22 24
 VITE_GIS_TILE_SIZE=256
23
-VITE_GIS_DEFAULT_CENTER=94.052462,31.921188
25
+VITE_GIS_DEFAULT_CENTER=94.054094,31.920244
24 26
 VITE_GIS_DEFAULT_ZOOM=14
25 27
 VITE_GIS_MIN_ZOOM=7
26 28
 VITE_GIS_MAX_ZOOM=20
27 29
 VITE_GIS_TILE_MAX_ZOOM=19
30
+# POI 查询方式:1=中心点周边,2=多边形区域(默认 2)
31
+VITE_GIS_POI_SEARCH_MODE=1
28 32
 
29 33
 # 大屏免密登录(生产需后端 bigscreen.login.enabled=true 且配置 RSA 密钥)
30 34
 VITE_SCREEN_AUTO_LOGIN=true

+ 15 - 4
ruoyi-screen/src/components/GisMap.vue

@@ -9,9 +9,15 @@ import {
9 9
   isGisConfigured,
10 10
   loadGisSdk,
11 11
   resolveGisMapStyle,
12
-  createGisTransformRequest
12
+  createGisTransformRequest,
13
+  syncWmtsLayerVisibility
13 14
 } from '@/utils/gisLoader'
14
-import { parsePoiLngLat, searchNearbyPois } from '@/utils/gisPoiSearch'
15
+import {
16
+  GIS_POI_SEARCH_MODE,
17
+  getGisPoiSearchMode,
18
+  parsePoiLngLat,
19
+  searchPois
20
+} from '@/utils/gisPoiSearch'
15 21
 
16 22
 const props = defineProps({
17 23
   center: {
@@ -79,6 +85,10 @@ function bindMapListeners() {
79 85
   }
80 86
   map.on('zoomend', () => {
81 87
     snapZoomToLimits()
88
+    syncWmtsLayerVisibility(map)
89
+  })
90
+  map.on('zoom', () => {
91
+    syncWmtsLayerVisibility(map)
82 92
   })
83 93
   map.on('click', (e) => {
84 94
     logClickCoordinate(e.lngLat)
@@ -146,7 +156,7 @@ async function renderPoiMarkers() {
146 156
 
147 157
   let pois = []
148 158
   try {
149
-    pois = await searchNearbyPois({
159
+    pois = await searchPois({
150 160
       center: resolveCenter()
151 161
     })
152 162
   } catch {
@@ -426,6 +436,7 @@ async function initMap() {
426 436
       applyMapLimits()
427 437
       configureScrollZoom()
428 438
       snapZoomToLimits()
439
+      syncWmtsLayerVisibility(map)
429 440
       resizeMap()
430 441
       renderPoiMarkers()
431 442
       emit('ready', map)
@@ -445,7 +456,7 @@ watch(
445 456
       center: resolveCenter(),
446 457
       zoom: resolveZoom()
447 458
     })
448
-    if (props.showPoiMarkers) {
459
+    if (props.showPoiMarkers && getGisPoiSearchMode() === GIS_POI_SEARCH_MODE.NEARBY) {
449 460
       renderPoiMarkers()
450 461
     }
451 462
   },

+ 3 - 2
ruoyi-screen/src/components/ScreenNavBar.vue

@@ -30,14 +30,14 @@
30 30
         </div>
31 31
       </div>
32 32
     </div>
33
-    <!-- <div class="logout" @click.stop="onLogout">
33
+    <div class="logout" @click.stop="onLogout">
34 34
       <div class="screen-nav-item-zh">
35 35
         {{ logoutNav.zh }}
36 36
       </div>
37 37
       <div class="screen-nav-item-bo">
38 38
         {{ logoutNav.bo }}
39 39
       </div>
40
-    </div> -->
40
+    </div>
41 41
   </header>
42 42
 </template>
43 43
 
@@ -179,6 +179,7 @@ function onLogout() {
179 179
   position: absolute;
180 180
   right: 70px;
181 181
   top: 30px;
182
+  pointer-events: auto;
182 183
 }
183 184
 .screen-nav-item-zh {
184 185
   font-size: 18px;

+ 95 - 37
ruoyi-screen/src/utils/gisLoader.js

@@ -34,6 +34,22 @@ export function getGisWmtsLayer() {
34 34
   return (import.meta.env.VITE_GIS_WMTS_LAYER || '').trim()
35 35
 }
36 36
 
37
+/** 高 zoom 时切换的 WMTS 图层,如 baqing_wp2 */
38
+export function getGisWmtsLayer2() {
39
+  return (import.meta.env.VITE_GIS_WMTS_LAYER2 || '').trim()
40
+}
41
+
42
+/** zoom 大于该值时叠加显示 VITE_GIS_WMTS_LAYER2(wp 始终保留在底层) */
43
+export function getGisWmtsLayerSwitchZoom() {
44
+  const z = Number(import.meta.env.VITE_GIS_WMTS_LAYER_SWITCH_ZOOM)
45
+  return Number.isFinite(z) ? z : 16
46
+}
47
+
48
+export const GIS_WMTS_LAYER_IDS = {
49
+  primary: 'gis-wmts-layer',
50
+  secondary: 'gis-wmts-layer-2'
51
+}
52
+
37 53
 /** 瓦片边长(256 或 512);512 时向 WMTS 追加 WIDTH/HEIGHT 请求高清瓦片 */
38 54
 export function getGisTileSize() {
39 55
   const n = Number(import.meta.env.VITE_GIS_TILE_SIZE)
@@ -54,16 +70,15 @@ function getGisWmtsZoomOffset() {
54 70
   return Number.isFinite(n) ? n : 0
55 71
 }
56 72
 
57
-function buildWmtsTileUrl() {
73
+function buildWmtsTileUrlForLayer(layerName) {
58 74
   const base = getGisBaseUrl()
59
-  const layer = getGisWmtsLayer()
60 75
   const key = getGisAccessToken()
61 76
   const tileSize = getGisTileSize()
62 77
   const matrix = getGisWmtsTileMatrixTemplate()
63 78
   const matrixSet = encodeURIComponent(getGisWmtsTileMatrixSet())
64 79
   let url =
65 80
     `${base}/agis/maps/v1/wmts` +
66
-    `?LAYER=${encodeURIComponent(layer)}` +
81
+    `?LAYER=${encodeURIComponent(layerName)}` +
67 82
     '&SERVICE=WMTS&REQUEST=GetTile' +
68 83
     `&TileMatrix=${matrix}` +
69 84
     '&TileCol={x}&TileRow={y}' +
@@ -76,6 +91,34 @@ function buildWmtsTileUrl() {
76 91
   return url
77 92
 }
78 93
 
94
+function buildWmtsRasterSource(layerName) {
95
+  return {
96
+    type: 'raster',
97
+    tiles: [buildWmtsTileUrlForLayer(layerName)],
98
+    tileSize: getGisTileSize(),
99
+    scheme: 'xyz',
100
+    minzoom: getGisMinZoom(),
101
+    maxzoom: getGisTileMaxZoom()
102
+  }
103
+}
104
+
105
+function buildWmtsRasterLayer(id, sourceId, visible = true) {
106
+  return {
107
+    id,
108
+    type: 'raster',
109
+    source: sourceId,
110
+    layout: {
111
+      visibility: visible ? 'visible' : 'none'
112
+    },
113
+    minzoom: getGisMinZoom(),
114
+    maxzoom: 24,
115
+    paint: {
116
+      'raster-fade-duration': 300,
117
+      'raster-resampling': 'linear'
118
+    }
119
+  }
120
+}
121
+
79 122
 /**
80 123
  * 瓦片请求改写:仅处理 zoom 偏移。
81 124
  * TileMatrix 上限由 style 里 source.maxzoom 控制,勿在此改写 col/row,否则易与引擎缓存键不一致导致 canceled。
@@ -97,49 +140,64 @@ export function createGisTransformRequest() {
97 140
   }
98 141
 }
99 142
 
100
-/** 由 WMTS GetTile 模板生成 Mapbox Style(raster 源) */
143
+/** 由 WMTS GetTile 模板生成 Mapbox Style(raster 源;可选双图层) */
101 144
 export function buildWmtsMapStyle() {
102 145
   const base = getGisBaseUrl()
103 146
   const layer = getGisWmtsLayer()
147
+  const layer2 = getGisWmtsLayer2()
104 148
   const key = getGisAccessToken()
105 149
   if (!base || !layer || !key) {
106 150
     return null
107 151
   }
108
-  const tileSize = getGisTileSize()
109
-  const tileUrl = buildWmtsTileUrl()
110
-  return {
111
-    version: 8,
112
-    sources: {
113
-      'gis-wmts': {
114
-        type: 'raster',
115
-        tiles: [tileUrl],
116
-        tileSize,
117
-        scheme: 'xyz',
118
-        minzoom: getGisMinZoom(),
119
-        maxzoom: getGisTileMaxZoom()
152
+
153
+  const sources = {
154
+    'gis-wmts': buildWmtsRasterSource(layer)
155
+  }
156
+  const layers = [
157
+    {
158
+      id: 'gis-bg',
159
+      type: 'background',
160
+      paint: {
161
+        'background-color': '#1a3a32'
120 162
       }
121 163
     },
122
-    layers: [
123
-      {
124
-        id: 'gis-bg',
125
-        type: 'background',
126
-        paint: {
127
-          'background-color': '#1a3a32'
128
-        }
129
-      },
130
-      {
131
-        id: 'gis-wmts-layer',
132
-        type: 'raster',
133
-        source: 'gis-wmts',
134
-        minzoom: getGisMinZoom(),
135
-        // 勿与 VITE_GIS_MAX_ZOOM 绑死:zoom 略大于 max 时(easeTo 过冲)图层会被引擎整层隐藏
136
-        maxzoom: 24,
137
-        paint: {
138
-          'raster-fade-duration': 300,
139
-          'raster-resampling': 'linear'
140
-        }
141
-      }
142
-    ]
164
+    buildWmtsRasterLayer(GIS_WMTS_LAYER_IDS.primary, 'gis-wmts', true)
165
+  ]
166
+
167
+  if (layer2) {
168
+    sources['gis-wmts-2'] = buildWmtsRasterSource(layer2)
169
+    layers.push(buildWmtsRasterLayer(GIS_WMTS_LAYER_IDS.secondary, 'gis-wmts-2', false))
170
+  }
171
+
172
+  return {
173
+    version: 8,
174
+    sources,
175
+    layers
176
+  }
177
+}
178
+
179
+/** 根据 zoom 控制 wp2 叠加层显隐:wp 始终在底层,zoom > 16 时 wp2 叠在上方 */
180
+export function syncWmtsLayerVisibility(map) {
181
+  if (!map || typeof map.getZoom !== 'function') {
182
+    return
183
+  }
184
+  const layer2 = getGisWmtsLayer2()
185
+  if (!layer2 || typeof map.getLayer !== 'function') {
186
+    return
187
+  }
188
+  if (!map.getLayer(GIS_WMTS_LAYER_IDS.secondary)) {
189
+    return
190
+  }
191
+
192
+  const z = map.getZoom()
193
+  const showOverlay = Number.isFinite(z) && z > getGisWmtsLayerSwitchZoom()
194
+
195
+  if (typeof map.setLayoutProperty === 'function') {
196
+    map.setLayoutProperty(
197
+      GIS_WMTS_LAYER_IDS.secondary,
198
+      'visibility',
199
+      showOverlay ? 'visible' : 'none'
200
+    )
143 201
   }
144 202
 }
145 203
 

+ 80 - 6
ruoyi-screen/src/utils/gisPoiSearch.js

@@ -3,6 +3,28 @@ import { getGisAccessToken, getGisBaseUrl, getGisDefaultCenter } from './gisLoad
3 3
 /** GIS POI 接口成功状态码 */
4 4
 const POI_STATUS_OK = 2000
5 5
 
6
+/** POI 查询方式:1=中心点周边,2=多边形区域(默认) */
7
+export const GIS_POI_SEARCH_MODE = {
8
+  NEARBY: 1,
9
+  POLYGON: 2
10
+}
11
+
12
+/** 巴青大屏 POI 多边形范围([经度, 纬度]) */
13
+export const BA_QING_POI_POLYGON = [
14
+  [94.039551, 31.927503],
15
+  [94.061348, 31.922841],
16
+  [94.058913, 31.916612],
17
+  [94.038479, 31.922559]
18
+]
19
+
20
+/**
21
+ * 读取 POI 搜索模式(VITE_GIS_POI_SEARCH_MODE:1 中心点,2 多边形,默认 2)
22
+ */
23
+export function getGisPoiSearchMode() {
24
+  const n = Number(import.meta.env.VITE_GIS_POI_SEARCH_MODE)
25
+  return n === GIS_POI_SEARCH_MODE.NEARBY ? GIS_POI_SEARCH_MODE.NEARBY : GIS_POI_SEARCH_MODE.POLYGON
26
+}
27
+
6 28
 /**
7 29
  * 解析 POI location 字段(文档格式:纬度,经度)为地图坐标 [经度, 纬度]
8 30
  */
@@ -18,6 +40,22 @@ export function parsePoiLngLat(location) {
18 40
   return [lng, lat]
19 41
 }
20 42
 
43
+/**
44
+ * 将 [lng, lat] 顶点列表转为接口 polygon 参数(纬度,经度;首尾闭合)
45
+ */
46
+export function formatPolygonParam(points) {
47
+  if (!Array.isArray(points) || points.length < 3) {
48
+    return ''
49
+  }
50
+  const ring = points.map(([lng, lat]) => [Number(lng), Number(lat)])
51
+  const [firstLng, firstLat] = ring[0]
52
+  const [lastLng, lastLat] = ring[ring.length - 1]
53
+  if (firstLng !== lastLng || firstLat !== lastLat) {
54
+    ring.push([firstLng, firstLat])
55
+  }
56
+  return ring.map(([lng, lat]) => `${lat},${lng}`).join(';')
57
+}
58
+
21 59
 function normalizePoiList(data) {
22 60
   if (data?.status !== POI_STATUS_OK || !Array.isArray(data?.body?.pois)) {
23 61
     return []
@@ -62,10 +100,46 @@ export async function searchNearbyPois(options = {}) {
62 100
   return normalizePoiList(await res.json())
63 101
 }
64 102
 
65
-/*
66
- * 多边形区域搜索(GIS 接口文档 §2.3)— 暂不使用
67
- *
68
- * export const BA_QING_POI_POLYGON = [...]
69
- * export function formatPolygonParam(points) { ... }
70
- * export async function searchPolygonPois(options = {}) { ... }
103
+/**
104
+ * 多边形区域搜索(GIS 接口文档 §2.3 /agis/search/v1/pois/polygon)
105
+ * @param {{ polygon?: [number, number][], pageNum?: number, pageSize?: number }} [options]
71 106
  */
107
+export async function searchPolygonPois(options = {}) {
108
+  const base = getGisBaseUrl()
109
+  const key = getGisAccessToken()
110
+  if (!base || !key) {
111
+    return []
112
+  }
113
+
114
+  const points = options.polygon || BA_QING_POI_POLYGON
115
+  const polygon = formatPolygonParam(points)
116
+  if (!polygon) {
117
+    return []
118
+  }
119
+
120
+  const params = new URLSearchParams({
121
+    polygon,
122
+    page_num: String(options.pageNum ?? 1),
123
+    page_size: String(options.pageSize ?? 1000),
124
+    key
125
+  })
126
+
127
+  const url = `${base}/agis/search/v1/pois/polygon?${params.toString()}`
128
+  const res = await fetch(url)
129
+  if (!res.ok) {
130
+    return []
131
+  }
132
+
133
+  return normalizePoiList(await res.json())
134
+}
135
+
136
+/**
137
+ * 按 VITE_GIS_POI_SEARCH_MODE 自动选择 POI 搜索方式
138
+ * @param {{ center?: [number, number], polygon?: [number, number][], radius?: number, pageNum?: number, pageSize?: number }} [options]
139
+ */
140
+export async function searchPois(options = {}) {
141
+  if (getGisPoiSearchMode() === GIS_POI_SEARCH_MODE.NEARBY) {
142
+    return searchNearbyPois(options)
143
+  }
144
+  return searchPolygonPois(options)
145
+}