第 10 节:地图点击逆地理编码(点击地图获取经纬度 + 地址)

第 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>

功能说明

  1. 点击地图,放置 Marker,弹出信息窗口,显示经纬度 + 逆地理地址
  2. 【清除标记】按钮删除点位与弹窗
  3. 全部使用 V4 原生 API,没有引入 V3 废弃接口

问题定位

400 (Bad Request) 根源:ds 里的 JSON 没有做 URL 编码 ,浏览器自动把{ :转义,服务端解析 JSON 失败。

同时还有一个关键点:逆地理编码需要申请【Web 服务许可 key】,不是地图 JS 的 tk!

地图 JS 的 tk(网页地图加载) 和 Web 服务 API 的 appkey(逆地理 / 搜索)是两套不同权限,只用地图 tk 会直接 400。

方案

  1. 用 encodeURIComponent 对 ds 的 JSON 字符串编码,修复 url 参数格式问题
  2. 增加状态判断,区分:请求失败 / 无地址 / 成功
  3. 预留注释,提示你替换成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>

关键说明

  1. 两套 key 区分
    • JS 地图 tk:用于加载天地图底图
    • Web 服务 appkey:用于逆地理编码、搜索等 http 接口,需要在天地图控制台单独申请【Web 服务许可】,不申请直接报 400
  2. 增加 encodeURIComponent(JSON.stringify(dsObj)),对 ds 参数做 URL 编码,修复 400 请求参数错误
  3. try-catch 捕获网络异常,友好提示

操作:替换TDT_JS_TK和TDT_WEB_APPKEY,刷新测试。

今天 16:56

相关推荐
斯内普吖1 小时前
(开源)线上花店系统实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
溪语流沙3 小时前
【Web全栈进阶】Vue Router + Pinia:SPA路由与全局状态
前端·javascript·vue.js
u0111026754 小时前
WebP 转成 PNG 后为什么更大?图片格式转换不等于压缩
前端·javascript·vue.js
seconp4 小时前
基于Spring Boot和Vue 3的智慧旅游网站:住宿预订、行程规划与千问接入
vue.js·spring boot·毕业设计·软件工程·课程设计·旅游·毕设
daols885 小时前
vue 甘特图组件 vxe-gantt 修改滚动条样式-让滚动条自动隐藏
javascript·vue.js·甘特图
特立独行的猫A6 小时前
仓颉UI开发实战:我用仓颉写了个桌面版力扣:从判题机到 100 道题
vue.js
用户1733598075376 小时前
纯前端 PDF 双面打印避坑指南:翻页顺序错了整个白印?
前端·javascript·vue.js
码艺-Alimjan18 小时前
Tauri 源码备份 · 打包 & 解压 总结,源码收集之路
前端·javascript·vue.js
顽疲21 小时前
Java vue 养老系统源码详解:月账单 care_month_bill 与 Quartz 扣费 Job 实践
java·开发语言·vue.js·vue养老·springboot养老·java养老