|
|
@@ -11,6 +11,7 @@ import {
|
|
11
|
11
|
resolveGisMapStyle,
|
|
12
|
12
|
createGisTransformRequest
|
|
13
|
13
|
} from '@/utils/gisLoader'
|
|
|
14
|
+import { parsePoiLngLat, searchNearbyPois } from '@/utils/gisPoiSearch'
|
|
14
|
15
|
|
|
15
|
16
|
const props = defineProps({
|
|
16
|
17
|
center: {
|
|
|
@@ -32,6 +33,11 @@ const props = defineProps({
|
|
32
|
33
|
showScale: {
|
|
33
|
34
|
type: Boolean,
|
|
34
|
35
|
default: false
|
|
|
36
|
+ },
|
|
|
37
|
+ /** 是否在地图加载后展示周边 POI Marker */
|
|
|
38
|
+ showPoiMarkers: {
|
|
|
39
|
+ type: Boolean,
|
|
|
40
|
+ default: true
|
|
35
|
41
|
}
|
|
36
|
42
|
})
|
|
37
|
43
|
|
|
|
@@ -53,6 +59,7 @@ const WHEEL_ZOOM_RATE = 0.0022
|
|
53
|
59
|
let wheelDeltaAccum = 0
|
|
54
|
60
|
let wheelDebounceTimer = null
|
|
55
|
61
|
let lastWheelAround = null
|
|
|
62
|
+let poiMarkers = []
|
|
56
|
63
|
|
|
57
|
64
|
function formatLngLat(lngLat) {
|
|
58
|
65
|
if (!lngLat) {
|
|
|
@@ -113,6 +120,105 @@ function bindZoomDebugListeners() {
|
|
113
|
120
|
})
|
|
114
|
121
|
}
|
|
115
|
122
|
|
|
|
123
|
+function clearPoiMarkers() {
|
|
|
124
|
+ poiMarkers.forEach((marker) => {
|
|
|
125
|
+ try {
|
|
|
126
|
+ marker.remove()
|
|
|
127
|
+ } catch {
|
|
|
128
|
+ /* ignore */
|
|
|
129
|
+ }
|
|
|
130
|
+ })
|
|
|
131
|
+ poiMarkers = []
|
|
|
132
|
+}
|
|
|
133
|
+
|
|
|
134
|
+function escapeHtml(text) {
|
|
|
135
|
+ return String(text || '')
|
|
|
136
|
+ .replace(/&/g, '&')
|
|
|
137
|
+ .replace(/</g, '<')
|
|
|
138
|
+ .replace(/>/g, '>')
|
|
|
139
|
+ .replace(/"/g, '"')
|
|
|
140
|
+}
|
|
|
141
|
+
|
|
|
142
|
+function buildPoiPopupHtml(poi) {
|
|
|
143
|
+ const name = escapeHtml(poi.name || '未知地点')
|
|
|
144
|
+ const address = poi.address ? escapeHtml(poi.address) : ''
|
|
|
145
|
+ const distance = poi.distance ? `${escapeHtml(poi.distance)}m` : ''
|
|
|
146
|
+ const parts = [`<div style="font-weight:600;margin-bottom:4px">${name}</div>`]
|
|
|
147
|
+ if (address) {
|
|
|
148
|
+ parts.push(`<div style="font-size:12px;opacity:0.85">${address}</div>`)
|
|
|
149
|
+ }
|
|
|
150
|
+ if (distance) {
|
|
|
151
|
+ parts.push(`<div style="font-size:12px;opacity:0.7;margin-top:4px">距中心 ${distance}</div>`)
|
|
|
152
|
+ }
|
|
|
153
|
+ return parts.join('')
|
|
|
154
|
+}
|
|
|
155
|
+
|
|
|
156
|
+/** 自定义 Marker:图标下方常驻显示 POI 名称 */
|
|
|
157
|
+function createPoiMarkerElement(name) {
|
|
|
158
|
+ const wrap = document.createElement('div')
|
|
|
159
|
+ wrap.className = 'gis-poi-marker'
|
|
|
160
|
+ wrap.style.pointerEvents = 'auto'
|
|
|
161
|
+
|
|
|
162
|
+ const label = document.createElement('div')
|
|
|
163
|
+ label.className = 'gis-poi-marker__label'
|
|
|
164
|
+ label.textContent = String(name || '未知地点')
|
|
|
165
|
+ label.title = label.textContent
|
|
|
166
|
+
|
|
|
167
|
+ const dot = document.createElement('div')
|
|
|
168
|
+ dot.className = 'gis-poi-marker__dot'
|
|
|
169
|
+
|
|
|
170
|
+ wrap.appendChild(label)
|
|
|
171
|
+ wrap.appendChild(dot)
|
|
|
172
|
+ return wrap
|
|
|
173
|
+}
|
|
|
174
|
+
|
|
|
175
|
+async function renderNearbyPoiMarkers() {
|
|
|
176
|
+ if (!props.showPoiMarkers || !map || !cmmapglRef) {
|
|
|
177
|
+ return
|
|
|
178
|
+ }
|
|
|
179
|
+ clearPoiMarkers()
|
|
|
180
|
+
|
|
|
181
|
+ const center = resolveCenter()
|
|
|
182
|
+ let pois = []
|
|
|
183
|
+ try {
|
|
|
184
|
+ pois = await searchNearbyPois({
|
|
|
185
|
+ center,
|
|
|
186
|
+ radius: 5000,
|
|
|
187
|
+ pageSize: 50,
|
|
|
188
|
+ pageNum: 1
|
|
|
189
|
+ })
|
|
|
190
|
+ } catch {
|
|
|
191
|
+ return
|
|
|
192
|
+ }
|
|
|
193
|
+
|
|
|
194
|
+ const Marker = cmmapglRef.Marker
|
|
|
195
|
+ const Popup = cmmapglRef.Popup
|
|
|
196
|
+ if (typeof Marker !== 'function') {
|
|
|
197
|
+ return
|
|
|
198
|
+ }
|
|
|
199
|
+
|
|
|
200
|
+ pois.forEach((poi) => {
|
|
|
201
|
+ const lngLat = parsePoiLngLat(poi.location)
|
|
|
202
|
+ if (!lngLat) {
|
|
|
203
|
+ return
|
|
|
204
|
+ }
|
|
|
205
|
+ const markerOpts = {
|
|
|
206
|
+ element: createPoiMarkerElement(poi.name),
|
|
|
207
|
+ anchor: 'bottom'
|
|
|
208
|
+ }
|
|
|
209
|
+ const marker = new Marker(markerOpts).setLngLat(lngLat)
|
|
|
210
|
+ if (typeof Popup === 'function') {
|
|
|
211
|
+ marker.setPopup(
|
|
|
212
|
+ new Popup({ offset: 20, closeOnClick: true, maxWidth: '260px' }).setHTML(
|
|
|
213
|
+ buildPoiPopupHtml(poi)
|
|
|
214
|
+ )
|
|
|
215
|
+ )
|
|
|
216
|
+ }
|
|
|
217
|
+ marker.addTo(map)
|
|
|
218
|
+ poiMarkers.push(marker)
|
|
|
219
|
+ })
|
|
|
220
|
+}
|
|
|
221
|
+
|
|
116
|
222
|
function destroyMap() {
|
|
117
|
223
|
if (zoomLogTimer) {
|
|
118
|
224
|
clearTimeout(zoomLogTimer)
|
|
|
@@ -126,6 +232,7 @@ function destroyMap() {
|
|
126
|
232
|
lastWheelAround = null
|
|
127
|
233
|
lastMouseZoomLogAt = 0
|
|
128
|
234
|
clampingZoom = false
|
|
|
235
|
+ clearPoiMarkers()
|
|
129
|
236
|
if (map) {
|
|
130
|
237
|
map.remove()
|
|
131
|
238
|
map = null
|
|
|
@@ -363,6 +470,7 @@ async function initMap() {
|
|
363
|
470
|
configureScrollZoom()
|
|
364
|
471
|
snapZoomToLimits()
|
|
365
|
472
|
resizeMap()
|
|
|
473
|
+ renderNearbyPoiMarkers()
|
|
366
|
474
|
emit('ready', map)
|
|
367
|
475
|
})
|
|
368
|
476
|
map.on('click', (e) => emit('click', e))
|
|
|
@@ -381,10 +489,25 @@ watch(
|
|
381
|
489
|
center: resolveCenter(),
|
|
382
|
490
|
zoom: resolveZoom()
|
|
383
|
491
|
})
|
|
|
492
|
+ if (props.showPoiMarkers) {
|
|
|
493
|
+ renderNearbyPoiMarkers()
|
|
|
494
|
+ }
|
|
384
|
495
|
},
|
|
385
|
496
|
{ deep: true }
|
|
386
|
497
|
)
|
|
387
|
498
|
|
|
|
499
|
+watch(
|
|
|
500
|
+ () => props.showPoiMarkers,
|
|
|
501
|
+ (show) => {
|
|
|
502
|
+ if (!map || !cmmapglRef) return
|
|
|
503
|
+ if (show) {
|
|
|
504
|
+ renderNearbyPoiMarkers()
|
|
|
505
|
+ } else {
|
|
|
506
|
+ clearPoiMarkers()
|
|
|
507
|
+ }
|
|
|
508
|
+ }
|
|
|
509
|
+)
|
|
|
510
|
+
|
|
388
|
511
|
onMounted(() => {
|
|
389
|
512
|
initMap()
|
|
390
|
513
|
})
|
|
|
@@ -416,3 +539,42 @@ defineExpose({ resizeMap, getMap: () => map })
|
|
416
|
539
|
height: 100%;
|
|
417
|
540
|
}
|
|
418
|
541
|
</style>
|
|
|
542
|
+
|
|
|
543
|
+<!-- Marker DOM 由地图 SDK 挂载,需非 scoped 样式 -->
|
|
|
544
|
+<style>
|
|
|
545
|
+.gis-poi-marker {
|
|
|
546
|
+ display: flex;
|
|
|
547
|
+ flex-direction: column;
|
|
|
548
|
+ align-items: center;
|
|
|
549
|
+ cursor: pointer;
|
|
|
550
|
+ transform: translateY(-2px);
|
|
|
551
|
+}
|
|
|
552
|
+
|
|
|
553
|
+.gis-poi-marker__label {
|
|
|
554
|
+ max-width: 108px;
|
|
|
555
|
+ margin-bottom: 3px;
|
|
|
556
|
+ padding: 2px 6px;
|
|
|
557
|
+ border-radius: 4px;
|
|
|
558
|
+ background: rgba(8, 28, 24, 0.82);
|
|
|
559
|
+ border: 1px solid rgba(240, 180, 41, 0.45);
|
|
|
560
|
+ color: #eef8f4;
|
|
|
561
|
+ font-size: 11px;
|
|
|
562
|
+ line-height: 1.35;
|
|
|
563
|
+ text-align: center;
|
|
|
564
|
+ white-space: nowrap;
|
|
|
565
|
+ overflow: hidden;
|
|
|
566
|
+ text-overflow: ellipsis;
|
|
|
567
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
|
|
|
568
|
+ pointer-events: none;
|
|
|
569
|
+}
|
|
|
570
|
+
|
|
|
571
|
+.gis-poi-marker__dot {
|
|
|
572
|
+ width: 10px;
|
|
|
573
|
+ height: 10px;
|
|
|
574
|
+ border-radius: 50%;
|
|
|
575
|
+ background: #f0b429;
|
|
|
576
|
+ border: 2px solid #fff;
|
|
|
577
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
|
|
|
578
|
+ flex-shrink: 0;
|
|
|
579
|
+}
|
|
|
580
|
+</style>
|