Commit af7e0b18 authored by daniel's avatar daniel

feat(mainsite): 领物导航地图完善 - 多底图/搜索/坐标转换

- 新增高德、腾讯底图切换(卫星+普通,共6种组合)
- WGS-84 ⇔ GCJ-02 坐标自动转换
- 天地图搜索替换 Mapbox Geocoder(支持 POI 和城市统计结果)
- 搜索节流控制(1秒间隔)
- 点位列表点击跳转到对应位置
- 修复 JSP 模板字符串 EL 表达式冲突
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 5c1d9b52
......@@ -31,12 +31,6 @@
<link href="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css" rel="stylesheet">
<!-- 添加Mapbox GL JS -->
<script src="https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"></script>
<!-- 添加Mapbox GL Geocoder JS -->
<script
src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.1-dev/mapbox-gl-geocoder.min.js"></script>
<link rel="stylesheet"
href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v5.0.1-dev/mapbox-gl-geocoder.css"
type="text/css" />
<!-- Favicon-->
<link rel="shortcut icon" href="https://img.geexek.com/files/home/favicon.ico">
<!-- 自定义css -->
......@@ -69,10 +63,36 @@
display: none;
}
/* 下移 50px,右移 10px(可根据需要调整) */
.mapboxgl-ctrl-top-left .mapboxgl-ctrl-geocoder {
margin-top: 90px;
/* margin-left: 10px; */
.search-container {
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 300px;
}
.search-results {
background: white;
border-radius: 4px;
margin-top: 4px;
max-height: 300px;
overflow-y: auto;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.search-result-item {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
font-size: 13px;
}
.search-result-item:hover {
background: #f5f5f5;
}
.hover-underline:hover {
text-decoration: underline;
}
</style>
</head>
......@@ -95,37 +115,44 @@
<div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0;">
<div id="map" style="width: 100%; height: 100%;z-index: 0;"></div>
<div class="fixed left-0 top-0 z-10 flex flex-col justify-center items-start gap-y-2 p-3">
<!-- <input
type="file"
accept=".gpx,.kml"
@change="handleFileChange"
style="display: none"
ref="fileInputRef"
/>
<n-card>
<div class="flex flex-col items-start gap-y-2">
<n-button @click="fileInputRef?.click()" type="primary">
导入轨迹
</n-button>
<span class="text-gray text-sm">
只能上传 GPX 或 KML 文件,且不超过 10M。
</span>
<span class="text-gray text-blue-600" v-if="fileName">
{{ fileName }}
</span>
</div>
</n-card> -->
<n-button @click="onBack" type="primary">
返回
</n-button>
<n-switch v-model:value="isSatelliteLayer" @update:value="toggleMapLayer" >
<template #checked>
卫星地图
</template>
<template #unchecked>
普通地图
</template>
</n-switch>
<n-card size="small" style="min-width:200px;">
<div class="flex flex-col gap-y-2">
<span style="font-size:13px;font-weight:500;">底图</span>
<n-radio-group v-model:value="mapLayer" @update:value="applyMapStyle" size="small">
<n-radio-button v-for="item in MapLayerEnum" :key="item.value" :value="item.value">{{ item.label }}</n-radio-button>
</n-radio-group>
<n-radio-group v-model:value="mapType" @update:value="applyMapStyle" size="small">
<n-radio-button v-for="item in MapTypeEnum" :key="item.value" :value="item.value">{{ item.label }}</n-radio-button>
</n-radio-group>
</div>
</n-card>
<div class="search-container" style="position:relative;">
<n-input-group>
<n-input
v-model:value="searchKeyword"
placeholder="输入地点搜索..."
clearable
@input="onSearchInput"
@clear="searchResults = []; showSearchResults = false;"
@keyup.enter="doSearch"
/>
<n-button type="primary" :loading="searchLoading" @click="doSearch">
搜索
</n-button>
</n-input-group>
<div v-if="showSearchResults && searchResults.length" class="search-results">
<div v-for="(item, index) in searchResults" :key="index"
@click="onSelectSearchResult(item)"
class="search-result-item"
>
<div style="font-weight:500;">{{ item.name }}</div>
<div style="color:#999;font-size:12px;">{{ item.address }}</div>
</div>
</div>
</div>
</div>
<div class="fixed right-3 top-0 z-10 p-3 flex flex-col gap-y-1">
<div class="flex flex-row items-center mb-2 w-full gap-x-1 bg-white p-2 rounded-sm">
......@@ -147,8 +174,8 @@
:key="marker.id"
class="flex flex-row items-center gap-x-2 py-1"
>
<span class="flex flex-1 flex-row items-center gap-x-1 text-xs">
<span class="line-clamp-1 flex-1 font-bold text-md">
<span class="flex flex-1 flex-row items-center gap-x-1 text-xs cursor-pointer" @click="flyToMarker(marker)">
<span class="line-clamp-1 flex-1 font-bold text-md hover-underline">
{{ marker.name }}
</span>
<n-tag size="small" :bordered="false" :type="StatusEnum[marker.status].color">
......@@ -244,7 +271,7 @@
<script>
const cmpt = JSON.parse('${fns:toJson(cmpt)}');
const tk = "16a595e56ea0cec57ad8c07368d7f1ac"
const tk = "1599ebcef853d2efc608e4ef66b46bb3"
// 地图用到的图标
const mapIcons = {
0: 'https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/map-icon/icon-cp.png',
......@@ -374,30 +401,186 @@
},
};
// 底图源枚举
const MapLayerEnum = Object.freeze({
TIANDITU: { value: 'tianditu', label: '天地图', gcj02: false },
GAODE: { value: 'gaode', label: '高德', gcj02: true },
TENCENT: { value: 'tencent', label: '腾讯', gcj02: true },
});
// 底图类型枚举
const MapTypeEnum = Object.freeze({
SATELLITE: { value: 'satellite', label: '卫星' },
NORMAL: { value: 'normal', label: '普通' },
});
// 获取天地图的地址
const getTDTUrls = (type = 'satellite') => {
const getTDTUrls = (type = MapTypeEnum.SATELLITE.value) => {
const services = Array.from({ length: 7 }, (_, i) => i + 1);
const baseUrl = 'https://t%s.tianditu.gov.cn';
const generateUrls = (path) => {
return services.map((v) => baseUrl.replace('%s', v.toString()) + path);
};
// ===== 普通矢量图层(非卫星) =====
const normalPath = "/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" + tk;
const TDTNormalUrls = generateUrls(normalPath);
// ===== 卫星图层 =====
const satellitePath = "/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" + tk;
const TDTSatelliteUrls = generateUrls(satellitePath);
// ===== 注记层(通用) =====
const annotationPath = "/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" + tk;
const TDTAnnotationUrls = generateUrls(annotationPath);
const satelliteAnnotationPath = "/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" + tk;
const TDTSatelliteAnnotationUrls = generateUrls(satelliteAnnotationPath);
const normalAnnotationPath = "/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=" + tk;
const TDTNormalAnnotationUrls = generateUrls(normalAnnotationPath);
return {
TDTNormalUrls,
TDTSatelliteUrls,
TDTAnnotationUrls,
TDTSatelliteAnnotationUrls,
TDTNormalAnnotationUrls,
};
}
// 获取高德地图的瓦片地址
const getGaodeUrls = () => {
const services = [1, 2, 3, 4];
return {
GaodeSatelliteUrls: services.map(v =>
'https://webst0' + v + '.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
),
GaodeNormalUrls: services.map(v =>
'https://webrd0' + v + '.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'
),
GaodeAnnotationUrls: services.map(v =>
'https://webst0' + v + '.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}'
),
};
}
// 腾讯卫星瓦片占位 host
const TENCENT_SATELLITE_HOST = 'tencent-satellite.invalid';
// 获取腾讯地图的瓦片地址
const getTencentUrls = () => {
const services = [0, 1, 2, 3];
return {
TencentSatelliteUrls: ['https://' + TENCENT_SATELLITE_HOST + '/tiles/{z}/{x}/{y}'],
TencentNormalUrls: services.map(v =>
'https://rt' + v + '.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=0&scene=0&version=2213'
),
TencentAnnotationUrls: services.map(v =>
'https://rt' + v + '.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=2&scene=0&version=2213'
),
};
}
// 腾讯卫星瓦片 URL 转换(TMS Y + 分桶目录)
const transformTencentSatelliteUrl = (url) => {
let parsedUrl;
try { parsedUrl = new URL(url); } catch { return url; }
if (parsedUrl.hostname !== TENCENT_SATELLITE_HOST) return url;
const match = parsedUrl.pathname.match(/^\/tiles\/(\d+)\/(\d+)\/(\d+)$/);
if (!match) return url;
const [, zText, xText, xyzYText] = match;
const z = Number(zText);
const x = Number(xText);
const y = Math.pow(2, z) - 1 - Number(xyzYText);
const server = (x + y) % 4;
return 'https://p' + server + '.map.gtimg.com/sateTiles/' + z + '/' + Math.floor(x / 16) + '/' + Math.floor(y / 16) + '/' + x + '_' + y + '.jpg?version=276';
}
// ===== 坐标转换(WGS-84 ⇔ GCJ-02)=====
function outOfChina(lng, lat) {
return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
function _transformLat(x, y) {
let ret = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
ret += ((20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2) / 3;
ret += ((20 * Math.sin(y * Math.PI) + 40 * Math.sin((y / 3) * Math.PI)) * 2) / 3;
ret += ((160 * Math.sin((y / 12) * Math.PI) + 320 * Math.sin((y * Math.PI) / 30)) * 2) / 3;
return ret;
}
function _transformLng(x, y) {
let ret = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
ret += ((20 * Math.sin(6 * x * Math.PI) + 20 * Math.sin(2 * x * Math.PI)) * 2) / 3;
ret += ((20 * Math.sin(x * Math.PI) + 40 * Math.sin((x / 3) * Math.PI)) * 2) / 3;
ret += ((150 * Math.sin((x / 12) * Math.PI) + 300 * Math.sin((x / 30) * Math.PI)) * 2) / 3;
return ret;
}
const EE = 0.00669342162296594323;
const A = 6378245.0;
function wgs84ToGcj02(lng, lat) {
if (outOfChina(lng, lat)) return [lng, lat];
let dLat = _transformLat(lng - 105, lat - 35);
let dLng = _transformLng(lng - 105, lat - 35);
const radLat = (lat / 180) * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180) / (((A * (1 - EE)) / (magic * sqrtMagic)) * Math.PI);
dLng = (dLng * 180) / ((A / sqrtMagic) * Math.cos(radLat) * Math.PI);
return [lng + dLng, lat + dLat];
}
function gcj02ToWgs84(lng, lat) {
if (outOfChina(lng, lat)) return [lng, lat];
let dLat = _transformLat(lng - 105, lat - 35);
let dLng = _transformLng(lng - 105, lat - 35);
const radLat = (lat / 180) * Math.PI;
let magic = Math.sin(radLat);
magic = 1 - EE * magic * magic;
const sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180) / (((A * (1 - EE)) / (magic * sqrtMagic)) * Math.PI);
dLng = (dLng * 180) / ((A / sqrtMagic) * Math.cos(radLat) * Math.PI);
const mgLat = lat + dLat;
const mgLng = lng + dLng;
return [lng * 2 - mgLng, lat * 2 - mgLat];
}
// 判断底图是否使用 GCJ-02 坐标系
function usesGcj02(layer) {
const entry = Object.values(MapLayerEnum).find(e => e.value === layer);
return entry ? entry.gcj02 : false;
}
// 生成 Mapbox style 对象
const getMapStyle = (layer, type) => {
const isSatellite = type === MapTypeEnum.SATELLITE.value;
const { TDTNormalUrls, TDTSatelliteUrls, TDTSatelliteAnnotationUrls, TDTNormalAnnotationUrls } = getTDTUrls();
const { GaodeSatelliteUrls, GaodeNormalUrls, GaodeAnnotationUrls } = getGaodeUrls();
const { TencentSatelliteUrls, TencentNormalUrls, TencentAnnotationUrls } = getTencentUrls();
const tileSets = {
[MapLayerEnum.TIANDITU.value]: {
base: isSatellite ? TDTSatelliteUrls : TDTNormalUrls,
annotation: isSatellite ? TDTSatelliteAnnotationUrls : TDTNormalAnnotationUrls,
},
[MapLayerEnum.GAODE.value]: {
base: isSatellite ? GaodeSatelliteUrls : GaodeNormalUrls,
annotation: isSatellite ? GaodeAnnotationUrls : null,
},
[MapLayerEnum.TENCENT.value]: {
base: isSatellite ? TencentSatelliteUrls : TencentNormalUrls,
baseScheme: isSatellite ? 'xyz' : 'tms',
annotation: isSatellite ? TencentAnnotationUrls : null,
annotationScheme: isSatellite ? 'tms' : undefined,
},
};
const selected = tileSets[layer] || tileSets[MapLayerEnum.TIANDITU.value];
const sources = {
basemap: {
type: 'raster',
tiles: selected.base,
tileSize: 256,
scheme: selected.baseScheme || 'xyz',
},
};
const layers = [
{ id: 'basemap-layer', type: 'raster', source: 'basemap' },
];
if (selected.annotation) {
sources.annotation = {
type: 'raster',
tiles: selected.annotation,
tileSize: 256,
scheme: selected.annotationScheme || 'xyz',
};
layers.push({ id: 'annotation-layer', type: 'raster', source: 'annotation' });
}
return { version: 8, sources, layers, glyphs };
}
// mapbox的字体
const glyphs = 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf';
......@@ -425,48 +608,28 @@
const message = useMessage()
const notification = useNotification()
// 是否是卫星地图图层
const isSatelliteLayer = ref(true)
// 获取地址
const { TDTNormalUrls, TDTSatelliteUrls, TDTAnnotationUrls } = getTDTUrls();
// 构建样式
const mapStyle = computed(() => ({
version: 8,
sources: {
tianmap: {
type: 'raster',
tiles: isSatelliteLayer.value ? TDTSatelliteUrls : TDTNormalUrls,
tileSize: 256,
attribution: isSatelliteLayer.value ? '天地图卫星影像图' : '天地图矢量图',
},
tianmapAnnotation: {
type: 'raster',
tiles: TDTAnnotationUrls,
tileSize: 256,
attribution: '天地图注记图层',
},
},
layers: [
{
id: 'tianmap-layer',
type: 'raster',
source: 'tianmap',
},
{
id: 'tianmap-annotation-layer',
type: 'raster',
source: 'tianmapAnnotation',
},
],
glyphs,
}));
// 添加搜索输入框
const geocoder = new MapboxGeocoder({
accessToken: mapBoxToken,
mapboxgl: mapboxgl,
placeholder: '输入地点搜索...',
marker: false // 不自动加 marker,可自行控制
});
// 底图状态
const mapLayer = ref(MapLayerEnum.TIANDITU.value)
const mapType = ref(MapTypeEnum.SATELLITE.value)
// 坐标转换辅助
const shouldTransformCoord = computed(() => usesGcj02(mapLayer.value))
const transformCoord = (lng, lat) => {
if (!shouldTransformCoord.value) return [lng, lat];
return wgs84ToGcj02(lng, lat);
}
const reverseTransformCoord = (lng, lat) => {
if (!shouldTransformCoord.value) return [lng, lat];
return gcj02ToWgs84(lng, lat);
}
// 搜索相关
const searchKeyword = ref('')
const searchResults = ref([])
const searchLoading = ref(false)
const showSearchResults = ref(false)
let searchTimer = null
let lastSearchTime = 0
onMounted(() => {
getCurrentPosition()
......@@ -522,11 +685,6 @@
/**------------------------ 地图初始化 start ------------------------**/
// 地图对象
let map = ref(null);
// 监听选中事件
geocoder.on('result', (e) => {
const { center, place_name } = e.result;
console.log('选中地点:', place_name, center);
});
// 初始化地图
const initMap = () => {
// 确保容器已完全渲染
......@@ -535,26 +693,29 @@
if (!map.value) {
map.value = new mapboxgl.Map({
container: 'map',
style: mapStyle.value,
container: 'map',
center: GpsConfig.map.center,
style: getMapStyle(mapLayer.value, mapType.value),
center: transformCoord(GpsConfig.map.center[0], GpsConfig.map.center[1]),
zoom: GpsConfig.map.zoom,
accessToken: mapBoxToken,
minZoom: GpsConfig.map.minZoom,
maxZoom: GpsConfig.map.maxZoom,
pitch: GpsConfig.map.defaultPitch, // 初始倾斜角度,0 表示 2D 模式
antialias: true, // 抗锯齿
bearing: 0, // 初始旋转角度
interactive: true, // 可交互
pitch: GpsConfig.map.defaultPitch,
antialias: true,
bearing: 0,
interactive: true,
attributionControl: false,
minTileCacheSize: GpsConfig.map.minTileCacheSize,
maxTileCacheSize: GpsConfig.map.maxTileCacheSize, // 调整瓦片缓存大小
maxTileCacheSize: GpsConfig.map.maxTileCacheSize,
transformRequest: (url, resourceType) => {
if (resourceType === 'Tile') {
return { url: transformTencentSatelliteUrl(url) };
}
return { url };
},
});
// 添加resize事件监听器
window.addEventListener('resize', () => {
map.value.resize();
});
// 监听 pitchChange 事件
map.value.on('load', () => {
loadMapIcons().then(() => {
refreshLiveMap();
......@@ -573,6 +734,18 @@
}, 0);
}
// 切换底图
const applyMapStyle = () => {
if (!map.value) return;
map.value.setStyle(getMapStyle(mapLayer.value, mapType.value));
map.value.once('style.load', () => {
loadMapIcons().then(() => {
refreshLiveMap();
destroyEvent();
initMapEvent();
});
});
}
/**
* 刷新地图
*/
......@@ -608,7 +781,7 @@
* */
const initCenter = () => {
const allCoordinates = trackCoordinates.value.map(
(v) => [v[0], v[1]],
(v) => transformCoord(v[0], v[1]),
);
if (allCoordinates.length > 0) {
const bounds = allCoordinates?.reduce(
......@@ -699,7 +872,7 @@
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [longitude, latitude],
coordinates: transformCoord(longitude, latitude),
},
properties: {
text: name,
......@@ -871,10 +1044,7 @@
*/
const initMapControls = () => {
if (!map.value) return;
// 添加比例尺控件到地图
map.value.addControl(new mapboxgl.ScaleControl(), 'bottom-right');
// 将搜索框添加到地图
map.value.addControl(geocoder, 'top-left');
};
/**
......@@ -891,7 +1061,7 @@
return new Promise((resolve, reject) => {
map.value?.loadImage(icon, (error, image) => {
if (error) {
// console.error(`Failed to load image: ${icon}`, error);
// console.error('Failed to load image: ' + icon, error);
reject(error);
} else {
if (map.value?.hasImage(icon)) {
......@@ -947,10 +1117,11 @@
layers: [MapKeyEnum.CP_ID]
});
if (features.length) return
// 获取点击位置的坐标
// 获取点击位置的坐标(高德/腾讯底图下为 GCJ-02,需反向转换为 WGS-84 存储)
const coordinates = e.lngLat;
markerForm.latitude = coordinates.lat;
markerForm.longitude = coordinates.lng;
const [wgsLng, wgsLat] = reverseTransformCoord(coordinates.lng, coordinates.lat);
markerForm.latitude = wgsLat;
markerForm.longitude = wgsLng;
markerForm.name = '新增标记点'
editCheckPoint(null)
......@@ -1010,12 +1181,12 @@
if (fileType === 'gpx' || fileType === 'kml') {
if (file.size <= 10 * 1024 * 1024) {
// 检查文件大小不超过 10M
message.success(`${file.name} 文件选择成功`);
message.success(file.name + ' 文件选择成功');
} else {
message.error(`${file.name} 文件大小超过 10M,请重新选择`);
message.error(file.name + ' 文件大小超过 10M,请重新选择');
}
} else {
message.error(`只能上传 GPX 或 KML 文件,请重新选择`);
message.error('只能上传 GPX 或 KML 文件,请重新选择');
}
if (file) {
fileName.value = file.name;
......@@ -1276,10 +1447,93 @@
// 回退
window.history.back()
}
// 切换底图
const toggleMapLayer = () => {
map.value.setStyle(mapStyle.value);
};
// 切换底图(已移至 applyMapStyle)
/**------------------------ 搜索 start ------------------------**/
const onSearchInput = () => {
clearTimeout(searchTimer);
if (!searchKeyword.value.trim()) {
searchResults.value = [];
showSearchResults.value = false;
return;
}
var now = Date.now();
var elapsed = now - lastSearchTime;
var THROTTLE_MS = 1000;
if (elapsed >= THROTTLE_MS) {
lastSearchTime = now;
doSearch();
} else {
searchTimer = setTimeout(function() {
lastSearchTime = Date.now();
doSearch();
}, THROTTLE_MS - elapsed);
}
}
const doSearch = async () => {
const keyword = searchKeyword.value.trim();
if (!keyword) return;
searchLoading.value = true;
try {
const bounds = map.value.getBounds();
const mapBound = bounds.getWest() + ',' + bounds.getSouth() + ',' + bounds.getEast() + ',' + bounds.getNorth();
const level = String(Math.round(map.value.getZoom()));
const postStr = JSON.stringify({
keyWord: keyword,
level,
mapBound,
queryType: '1',
start: '0',
count: '10',
});
const url = 'https://api.tianditu.gov.cn/v2/search?postStr=' + encodeURIComponent(postStr) + '&type=query&tk=' + tk;
const res = await fetch(url).then(r => r.json());
var results = [];
if (res.pois && res.pois.length) {
results = res.pois.map(function(poi) {
var parts = poi.lonlat.split(/[, ]+/).map(Number);
return { name: poi.name, address: poi.address || '', lng: parts[0], lat: parts[1] };
});
} else if (res.statistics && res.statistics.priorityCitys && res.statistics.priorityCitys.length) {
results = res.statistics.priorityCitys.map(function(city) {
var parts = city.lonlat.split(',').map(Number);
return { name: city.adminName, address: '共' + city.count + '条结果', lng: parts[0], lat: parts[1] };
});
}
if (results.length) {
searchResults.value = results;
showSearchResults.value = true;
} else {
searchResults.value = [];
showSearchResults.value = false;
message.warning('未找到相关地点');
}
} catch (err) {
console.error('搜索失败:', err);
message.error('搜索失败,请重试');
} finally {
searchLoading.value = false;
}
}
const onSelectSearchResult = (item) => {
showSearchResults.value = false;
searchKeyword.value = item.name;
const [flyLng, flyLat] = transformCoord(item.lng, item.lat);
map.value.flyTo({
center: [flyLng, flyLat],
zoom: 15,
duration: 1000,
});
}
const flyToMarker = (marker) => {
if (!map.value || !marker.longitude || !marker.latitude) return;
var coords = transformCoord(Number(marker.longitude), Number(marker.latitude));
map.value.flyTo({
center: coords,
zoom: 16,
duration: 800,
});
}
/**------------------------ 搜索 end ------------------------**/
/**------------------------ marker end ------------------------**/
// 对外返回的信息
return {
......@@ -1290,6 +1544,8 @@
// 公共
CheckPointTypeEnum,
StatusEnum,
MapLayerEnum,
MapTypeEnum,
// 点位编辑模块
markers,
markerForm,
......@@ -1304,8 +1560,17 @@
isAddMode,
fileList,
onBack,
isSatelliteLayer,
toggleMapLayer,
mapLayer,
mapType,
applyMapStyle,
searchKeyword,
searchResults,
searchLoading,
showSearchResults,
onSearchInput,
doSearch,
onSelectSearchResult,
flyToMarker,
uploadUrl,
uploadFinish
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment