最近的项目中需要将高德地图替换成天地图,由于第一次接触,官方的网站也没有进行详细的介绍,网上对于天地图使用的文章也比较少,分享总结一下vue项目天地图的使用。
lbs.tianditu.gov.cn/home.html 这是天地图的官方地址,我使用的是最新的版本,4.0的api具体的可以进入官网去看看。
前置条件
使用API之前,需要申请应用Key点击该链接注册账号并申请KEY
- 步骤一:注册账号
- 步骤二:点击控制台进入应用管理
- 步骤三:创建新应用,创建后可以看到自己申请的Key

引入天地图
- 方式一:直接在项目的index.html中引入
js
<script src="https://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥" type="text/javascript"></script>
- 方式二:使用动态加载器加载天地图
js
// 天地图配置 config.js
export const tMapVersion = '4.0'
export const tMapKey = 'c0075044cad46971d3270e8db148b491'
export const tMapOrigin = 'https://api.tianditu.gov.cn'
js
import { tMapKey, tMapOrigin, tMapVersion } from './config'
const T_MAP_API_URL = `${tMapOrigin}/api?v=${tMapVersion}&tk=${tMapKey}`
let loadPromise = null
let currentScript = null
/**
* 动态加载天地图 API
* @returns {Promise<TMapAPI>} 天地图 API 对象
*/
export function loadTMapAPI() {
// 如果已经加载完成,直接返回
if (window.T && validateTMapAPI()) {
// console.debug('✅ 天地图 API 已存在,直接返回')
return Promise.resolve(window.T)
}
// 如果正在加载中,返回现有的 Promise
if (loadPromise) {
return loadPromise
}
loadPromise = new Promise((resolve, reject) => {
let retryCount = 0
const maxRetries = 3
function attemptLoad() {
// 创建 script 标签
const script = document.createElement('script')
script.src = T_MAP_API_URL
script.type = 'text/javascript'
script.async = true
script.defer = true
// 保存当前脚本引用
currentScript = script
// 成功加载回调
script.onload = () => {
// 给 API 更多时间完全初始化
setTimeout(() => {
if (window.T && validateTMapAPI()) {
// console.debug('✅ 天地图 API 加载成功')
return resolve(window.T)
}
// 再等待一段时间
console.warn('⚠️ API 对象存在但验证失败,等待更长时间...')
setTimeout(() => {
if (window.T && validateTMapAPI()) {
// console.debug('✅ 天地图 API 延迟验证成功')
return resolve(window.T)
}
const error = new Error('天地图 API 加载失败:API 对象无效或关键方法缺失')
console.error('❌', error.message)
reject(error)
}, 1000)
// 不立即清理脚本,让 API 保持可用
}, 500)
}
// 错误处理
script.onerror = (event) => {
console.error('❌ 脚本加载错误:', event)
cleanupScript(script)
if (retryCount < maxRetries) {
retryCount++
// console.debug(`🔄 重试加载天地图 API (${retryCount}/${maxRetries})...`)
setTimeout(() => attemptLoad(), 1000 * retryCount)
}
else {
const error = new Error(`天地图 API 脚本加载失败:已重试 ${maxRetries} 次,可能是网络问题或 CORS 限制`)
console.error('❌', error.message)
reject(error)
}
}
// 添加到页面头部
document.head.appendChild(script)
// console.debug(`🚀 正在加载天地图 API... (尝试 ${retryCount + 1}/${maxRetries + 1})`)
}
// 开始首次加载尝试
attemptLoad()
// 设置总体超时处理,增加到30秒总超时
setTimeout(() => {
if (!window.T || !validateTMapAPI()) {
const error = new Error('天地图 API 加载总体超时(30秒)')
console.error('❌', error.message)
if (currentScript) {
cleanupScript(currentScript)
}
reject(error)
}
}, 30000)
})
return loadPromise
}
/**
* 清理脚本标签
* @param {HTMLScriptElement} script 要清理的脚本元素
*/
function cleanupScript(script) {
if (script && script.parentNode) {
script.parentNode.removeChild(script)
}
if (currentScript === script) {
currentScript = null
}
}
/**
* 验证天地图 API 是否正确加载
* @returns {boolean}
*/
function validateTMapAPI() {
try {
if (!window.T)
return false
// 检查关键构造函数是否存在
const requiredMethods = ['Map', 'LngLat', 'Marker']
for (const method of requiredMethods) {
if (typeof window.T[method] !== 'function') {
console.warn(`⚠️ 天地图 API 缺少方法: ${method}`)
return false
}
}
return true
}
catch (error) {
console.error('❌ 验证天地图 API 时出错:', error)
return false
}
}
/**
* 检查天地图 API 是否已加载
* @returns {boolean}
*/
export function isTMapLoaded() {
return !!window.T && validateTMapAPI()
}
地图公用方法提取
将使用到的地图方法提取到t-map.js
文件中
js
import { ref, reactive, nextTick } from 'vue'
import { tMapKey, tMapOrigin } from '@/config'
import { loadTMapAPI, isTMapLoaded } from '@/libs/t-map-loader'
export function useTMap(addrInfo) {
const map = ref()
const nearList = ref([])
const currentCity = ref('')
const isMapLoading = ref(false)
const mapLoadError = ref('')
const stateMap = reactive({
center: [114.0579, 22.5431],
marker: [114.0579, 22.5431],
zoom: 11,
})
let T = null
// 地图公用模块抽取
function setMapData() {
if (!T || !map.value)
return
map.value.centerAndZoom(new T.LngLat(stateMap.center[0], stateMap.center[1]), stateMap.zoom) // 中心点坐标和缩放级别
const marker = new T.Marker(new T.LngLat(stateMap.marker[0], stateMap.marker[1]))
map.value.clearOverLays()
map.value.addOverLay(marker)
}
// 初始化地图
async function initMap() {
try {
isMapLoading.value = true
mapLoadError.value = ''
// 动态加载天地图 API
T = await loadTMapAPI()
// 等待 DOM 更新并验证容器存在
await nextTick()
// 验证容器是否存在
const container = document.getElementById('container')
if (!container) {
throw new Error('地图容器 #container 不存在,请确保 DOM 元素已渲染')
}
// 验证天地图 API 是否可用
if (!T || typeof T.Map !== 'function') {
throw new Error('天地图 API 未正确加载或 T.Map 构造函数不可用')
}
// 清空容器内容
container.innerHTML = ''
// 创建地图实例
map.value = new T.Map('container', { projection: 'EPSG:4326' })
// 设置地图数据
const { province, city, longitude, latitude } = addrInfo.value
currentCity.value = province?.includes('市') ? province : city || '深圳市'
if (longitude && latitude) {
stateMap.center = [longitude, latitude]
stateMap.marker = [longitude, latitude]
}
setMapData()
// 向地图上添加标注
map.value.addEventListener('click', onMapClick)
isMapLoading.value = false
}
catch (error) {
console.error('地图初始化失败:', error)
mapLoadError.value = error instanceof Error ? error.message : '地图加载失败,请刷新页面重试'
isMapLoading.value = false
}
}
// 地图选点
async function onMapClick(event) {
if (!T)
return
const { lnglat } = event || {}
const data = await getMapAddress(lnglat.lng, lnglat.lat)
const { location, addressComponent } = data || {}
stateMap.center = [location.lon, location.lat]
stateMap.marker = [location.lon, location.lat]
setMapData()
const value = addressComponent.address
await searchAddressFn(value)
}
async function searchAddressFn(searchValue) {
if (!T || !map.value)
return
const value = searchValue
const bounds = map.value?.getBounds()
const mapBound = `${bounds?.getSouthWest()?.lng},${bounds?.getSouthWest()?.lat},${bounds?.getNorthEast()?.lng},${bounds?.getNorthEast()?.lat}`
const params = {
postStr: JSON.stringify({
keyWord: value,
level: '18', // 搜索等级
mapBound,
queryRadius: 10000,
pointLonlat: `${stateMap.center[0]},${stateMap.center[1]}`,
queryType: '1', // 查询类型 1:普通搜索
start: 0,
count: 100,
show: 2,
}),
tk: tMapKey,
type: 'query',
}
const response = await fetch(`${tMapOrigin}/v2/search?postStr=${encodeURIComponent(params.postStr)}&tk=${tMapKey}&type=${params.type}`)
const data = await response.json()
const { pois } = data || {}
nearList.value = pois || []
}
// 逆地理编码
async function getMapAddress(lng, lat) {
const response = await fetch(`${tMapOrigin}/geocoder?postStr={'lon':${lng},'lat':${lat},'ver':1}&tk=${tMapKey}`)
const data = await response.json()
return data.result
}
return {
map,
nearList,
currentCity,
stateMap,
isMapLoading,
mapLoadError,
setMapData,
getMapAddress,
initMap,
onMapClick,
searchAddressFn,
}
}
具体使用
js
<script setup>
import { ref, computed,nextTick} from 'vue'
import { useTMap } from '@/composables/t-map'
const {
stateMap,
nearList,
initMap,
searchAddressFn,
setMapData,
getMapAddress,
isMapLoading,
mapLoadError
} = useTMap(addrInfo)
// 打开弹框初始化地图
async function showDialog() {
// 延迟初始化地图,确保 DOM 完全渲染
await nextTick()
initMap()
}
html
<template>
<div class="map-container">
<div v-if="dialogVisible" id="container" />
<!-- 地图加载状态覆盖层 -->
<div v-if="isMapLoading" class="map-loading map-overlay">
<i class="el-icon el-icon-loading"></i>
<p>地图加载中...</p>
</div>
<!-- 地图加载错误覆盖层 -->
<div v-else-if="mapLoadError" class="map-error map-overlay">
<i class="el-icon el-icon-warning-outline"></i>
<p>{{ mapLoadError }}</p>
<el-button type="primary" size="mini" @click="initMap">
重新加载
</el-button>
</div>
</div>
</template>