第 10 节:地图点击逆地理编码(点击地图获取经纬度 + 地址),
<template> <div class="map-page"> <h2>第10节:地图点击逆地理编码</h2> <div style="margin-bottom:8px;"> <span>操作:点击地图任意位置,获取经纬度和详细地址</span> <button style="margin-left:20px;" @click="clearMarker">清除标记</button> </div> <div id="map" class="map-container"></div> </div> </template> <script setup> import { onMounted, onUnmounted } from 'vue' const TDT_KEY = '替换成你的tk' let map = null let clickMarker = null let infoWindow = null function loadTdtScript() { return new Promise((resolve, reject) => { if (window.T) { return resolve(window.T) } const script = document.createElement('script') script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${TDT_KEY}` script.onload = () => resolve(window.T) script.onerror = err => reject(err) document.body.appendChild(script) }) } async function initMap() { const T = await loadTdtScript() const mapDom = document.getElementById('map') if (mapDom.children.length > 0) return map = new T.Map('map') const center = new T.LngLat(116.404, 39.915) map.centerAndZoom(center, 10) map.enableScrollWheelZoom(true) // 缩放控件 const zoomCtrl = new T.Control.Zoom({ position: 'topright' }) map.addControl(zoomCtrl) // 比例尺控件 const scaleCtrl = new T.Control.Scale({ position: 'topleft' }) map.addControl(scaleCtrl) // 地图单击事件 map.on('click', async (e) => { if (!e || !e.lnglat) return const lnglat = e.lnglat const lng = lnglat.lng.toFixed(6) const lat = lnglat.lat.toFixed(6) // 清理上次标记和弹窗 if(clickMarker) map.removeOverLay(clickMarker) if(infoWindow) map.closeInfoWindow() // 添加点位Marker clickMarker = new T.Marker(lnglat) map.addOverLay(clickMarker) // 天地图逆地理编码服务 const url = `https://api.tianditu.gov.cn/geocoder?ds={"lon":${lng},"lat":${lat}}&tk=${TDT_KEY}` const res = await fetch(url) const data = await res.json() let address = '未获取到地址' if(data?.result?.formatted_address) { address = data.result.formatted_address } const winContent = ` <div style="padding:6px;"> <div><b>点击位置信息</b></div> <div>经度:${lng}</div> <div>纬度:${lat}</div> <div>地址:${address}</div> </div> ` infoWindow = new T.InfoWindow(winContent) map.openInfoWindow(infoWindow, lnglat) }) } // 清除标记与弹窗 const clearMarker = () => { if(clickMarker){ map.removeOverLay(clickMarker) clickMarker = null } if(infoWindow){ map.closeInfoWindow() infoWindow = null } } onMounted(() => { initMap() }) onUnmounted(() => { if(map){ clearMarker() const dom = document.getElementById('map') dom.innerHTML = '' map = null } }) </script> <style scoped> .map-page { padding: 20px; } .map-container { width: 100%; height: 600px; border:1px solid #ccc; } </style>功能说明
- 点击地图,放置 Marker,弹出信息窗口,显示经纬度 + 逆地理地址
- 【清除标记】按钮删除点位与弹窗
- 全部使用 V4 原生 API,没有引入 V3 废弃接口
问题定位
400 (Bad Request) 根源:ds 里的 JSON 没有做 URL 编码,浏览器自动把{:转义,服务端解析 JSON 失败。
同时还有一个关键点:逆地理编码需要申请【Web 服务许可 key】,不是地图 JS 的 tk!
地图 JS 的 tk(网页地图加载) 和 Web 服务 API 的 appkey(逆地理 / 搜索)是两套不同权限,只用地图 tk 会直接 400。
方案
- 用
encodeURIComponent对 ds 的 JSON 字符串编码,修复 url 参数格式问题 - 增加状态判断,区分:请求失败 / 无地址 / 成功
- 预留注释,提示你替换成Web 服务 appkey(天地图控制台单独申请,Web 服务许可)
第 10 节【修复完整版 逆地理编码】
<template> <div class="map-page"> <h2>第10节:地图点击逆地理编码【V4修复】</h2> <div style="margin-bottom:8px;"> <span>操作:点击地图任意位置,获取经纬度和详细地址</span> <button style="margin-left:20px;" @click="clearMarker">清除标记</button> </div> <div id="map" class="map-container"></div> </div> </template> <script setup> import { onMounted, onUnmounted } from 'vue' const TDT_JS_TK = '替换为你的地图JS TK' // ⚠️重要:逆地理编码需要单独申请【Web服务许可key】,和上面地图tk不是同一个! const TDT_WEB_APPKEY = '替换为Web服务appkey' let map = null let clickMarker = null let infoWindow = null function loadTdtScript() { return new Promise((resolve, reject) => { if (window.T) { return resolve(window.T) } const script = document.createElement('script') script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${TDT_JS_TK}` script.onload = () => resolve(window.T) script.onerror = err => reject(err) document.body.appendChild(script) }) } async function initMap() { const T = await loadTdtScript() const mapDom = document.getElementById('map') if (mapDom.children.length > 0) return map = new T.Map('map') const center = new T.LngLat(116.404, 39.915) map.centerAndZoom(center, 10) map.enableScrollWheelZoom(true) // 缩放控件 const zoomCtrl = new T.Control.Zoom({ position: 'topright' }) map.addControl(zoomCtrl) // 比例尺控件 const scaleCtrl = new T.Control.Scale({ position: 'topleft' }) map.addControl(scaleCtrl) // 地图单击事件 map.on('click', async (e) => { if (!e || !e.lnglat) return const lnglat = e.lnglat const lng = lnglat.lng.toFixed(6) const lat = lnglat.lat.toFixed(6) // 清理上次标记和弹窗 if(clickMarker) map.removeOverLay(clickMarker) if(infoWindow) map.closeInfoWindow() // 添加点位Marker clickMarker = new T.Marker(lnglat) map.addOverLay(clickMarker) // ✅ 构造逆地理参数,encodeURIComponent编码JSON,解决400报错 const dsObj = { lon: lng, lat: lat } const dsStr = encodeURIComponent(JSON.stringify(dsObj)) const url = `https://api.tianditu.gov.cn/geocoder?ds=${dsStr}&tk=${TDT_WEB_APPKEY}` let address = '未获取到地址' try { const res = await fetch(url) const data = await res.json() if (data.status === '0' && data.result?.formatted_address) { address = data.result.formatted_address } else { address = data.msg || '该位置无地址信息' } } catch (err) { address = '接口请求失败,请检查Web服务appkey权限' console.error('逆地理请求异常:', err) } const winContent = ` <div style="padding:6px;"> <div><b>点击位置信息</b></div> <div>经度:${lng}</div> <div>纬度:${lat}</div> <div>地址:${address}</div> </div> ` infoWindow = new T.InfoWindow(winContent) map.openInfoWindow(infoWindow, lnglat) }) } // 清除标记与弹窗 const clearMarker = () => { if(clickMarker){ map.removeOverLay(clickMarker) clickMarker = null } if(infoWindow){ map.closeInfoWindow() infoWindow = null } } onMounted(() => { initMap() }) onUnmounted(() => { if(map){ clearMarker() const dom = document.getElementById('map') dom.innerHTML = '' map = null } }) </script> <style scoped> .map-page { padding: 20px; } .map-container { width: 100%; height: 600px; border:1px solid #ccc; } </style>关键说明
- 两套 key 区分
- JS 地图 tk:用于加载天地图底图
- Web 服务 appkey:用于逆地理编码、搜索等 http 接口,需要在天地图控制台单独申请【Web 服务许可】,不申请直接报 400
- 增加
encodeURIComponent(JSON.stringify(dsObj)),对 ds 参数做 URL 编码,修复 400 请求参数错误 - try-catch 捕获网络异常,友好提示
操作:替换
TDT_JS_TK和TDT_WEB_APPKEY,刷新测试。
今天 16:56