第 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