---
title: "天地图 Web 服务"
description: "createTianditu 统一封装天地图地名搜索、地理编码、行政区划与路线规划，tk 一次注入，坐标统一 WGS84。"
seo_title: "Tianditu Web Services"
seo_description: "createTianditu wraps Tianditu place search, geocoding, administrative divisions and route planning behind one client; bind tk once, all results in WGS84."
canonical_url: "https://mapbox.mhaibaraai.cn/docs/utils/tianditu"
---
# 天地图 Web 服务

> createTianditu 统一封装天地图地名搜索、地理编码、行政区划与路线规划，tk 一次注入，坐标统一 WGS84。

## 简介

`createTianditu` 把天地图多个 Web 服务收敛为一个客户端：`tk` 在创建时注入一次，之后各方法无需重复传递。天地图坐标体系是 WGS84（CGCS2000），客户端不做坐标转换，入参与返回值均原样透传。

```ts
import { createTianditu } from '@movk/mapbox/utils/tianditu-client'

const td = createTianditu({ tk: tiandituApiToken })

await td.locate('上海南浦大桥') // 地名→最佳匹配点（自动收窄 + 精确匹配优先）
await td.search({ type: 'nearby', keyword: '公园', center: [116.48, 39.93], radius: 5000 })
await td.geocode('北京市海淀区莲花池西路28号') // 地址→精确坐标
await td.reverseGeocode([116.37304, 39.92594]) // 坐标→结构化地址
await td.administrative('北京', { childLevel: 1 })
await td.route([116.35506, 39.92277], [116.39751, 39.90854], { mode: 'fastest' })
```

> \[\!WARNING\]
> 
> 天地图 Web 服务只能在服务端调用
> 
> ，不要在浏览器代码里直接 import。检索/地理编码/行政区划/路线接口都要求
> 
> 服务端类型
> 
>  key（按调用方 IP 白名单校验），与 
> 
> MapboxTiandituLayer
> 
> /
> 
> tiandituToken
> 
>  用的
> 
> 浏览器端类型
> 
>  key（按 Referer 校验）是两种不同的 key，不能混用——用浏览器端 key 调这些接口会收到 
> 
> {"code":301012,"msg":"权限类型错误"}
> 
> 。
> 
> tk
> 
>  必须由调用方从私有配置（例如 Nuxt 的 
> 
> runtimeConfig
> 
> ，不是 
> 
> runtimeConfig.public
> 
> ）读取后显式传入。

在 Nuxt 里典型用法是包一层 `server/api` 路由：

```ts [server/api/geocode.get.ts]
import { createTianditu } from '@movk/mapbox/utils/tianditu-client'

export default defineEventHandler(async (event) => {
  const { keyword } = getQuery(event)
  const tk = useRuntimeConfig().tiandituApiToken // 私有字段，不放 public
  return await createTianditu({ tk }).locate(String(keyword))
})
```

Vue + Vite 项目没有 Nitro，可以在 `vite.config.ts` 里用 `configureServer` 加一段开发期中间件，效果等价：

```ts [vite.config.ts]
import { defineConfig, loadEnv } from 'vite'
import { createTianditu } from '@movk/mapbox/utils/tianditu-client'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '') // 第三个参数传 '' 才能读到未加 VITE_ 前缀的变量

  return {
    plugins: [
      {
        name: 'tianditu-geocode',
        configureServer(server) {
          server.middlewares.use('/api/geocode', async (req, res) => {
            const { keyword } = Object.fromEntries(new URL(req.url!, 'http://localhost').searchParams)
            const result = await createTianditu({ tk: env.TIANDITU_API_TOKEN }).locate(String(keyword))
            res.setHeader('Content-Type', 'application/json')
            res.end(JSON.stringify(result))
          })
        }
      }
    ]
  }
})
```

> \[\!NOTE\]
> 
> tk
> 
>  只能用不带 
> 
> VITE\_
> 
>  前缀的环境变量（如 
> 
> TIANDITU\_API\_TOKEN
> 
> ）读取——
> 
> VITE\_
> 
>  前缀的变量会被 Vite 原样内联进浏览器 bundle，放服务端专用 key 会导致泄漏。生产环境需要一个真实的 Node 服务器（该中间件写法只在 
> 
> vite dev
> 
>  生效，
> 
> vite build
> 
>  产物是纯静态文件，不含这段服务端逻辑）。

> \[\!NOTE\]
> See: /docs/extensions/tianditu
> 
> 天地图底图 / 注记的组件用法见 
> 
> MapboxTiandituLayer
> 
> ；本页是纯服务端的 Web 服务能力，不涉及地图组件。

## API

### `createTianditu()`

创建客户端，绑定 `tk` 后返回下列各方法。

**options.tk** (`string`) *required*: 天地图 Web 服务 token（服务端类型 key）。

### `search()`

地名搜索：一个入口覆盖天地图 7 种 `queryType`，按 `type` 判别入参、按 `resultType` 判别归一化输出。所有坐标入参均为 WGS84。

```ts
type SearchParams =
  | { type: 'normal', keyword: string, bounds: Bounds, level: number, specify?: string }
  | { type: 'inView', keyword: string, bounds: Bounds, level: number }
  | { type: 'nearby', keyword: string, center: [number, number], radius: number }
  | { type: 'polygon', keyword: string, polygon: [number, number][] }
  | { type: 'district', specify: string, keyword?: string }
  | { type: 'category', specify: string, bounds: Bounds, dataTypes: string }
  | { type: 'statistics', specify: string, keyword?: string }
// 公共可选：start / count / dataTypes / show

type SearchResult =
  | { kind: 'poi', count: number, pois: Poi[], suggestedDistrict?: { name: string, code: string } }
  | { kind: 'categories', categories: { name: string, count: number, pois: Poi[] }[] }
  | { kind: 'statistics', statistics: Statistics }
  | { kind: 'area', area: Area }
  | { kind: 'suggestion', suggestion: Suggestion }
  | { kind: 'line', lines: LineResult[] }
  | { kind: 'empty' }
```

调用方按 `result.kind` 分支消费；`empty` 表示无数据（非错误）。接口返回异常状态码时抛 `TiandituError`（含天地图 `infocode`）。POI 结果伴随的 `suggestedDistrict` 是天地图从关键词里识别出的行政区建议（`locate()` 据此自动收窄二次检索）。

> \[\!NOTE\]
> 
> category
> 
>  类型传
> 
> 单个
> 
> dataTypes
> 
>  时命中 
> 
> kind: 'poi'
> 
> ；传
> 
> 多个
> 
> （逗号分隔）时天地图按分类名分组返回，命中 
> 
> kind: 'categories'
> 
> ，每组 
> 
> name
> 
>  即查询分类名、
> 
> count
> 
> /
> 
> pois
> 
>  独立对应。

### `locate()`

地名精确定位：把地标/地名解析为最佳匹配点，比直接 `search({ type: 'normal' })` 更准——未传 `bounds` 时自动用行政区建议收窄检索范围，并优先返回同名精确匹配。

**keyword** (`string`) *required*: 地名 / 地标关键词，如“上海南浦大桥”。

**options.bounds** (`[number, number, number, number]`): 自定义检索范围 \[minx, miny, maxx, maxy\]；传入时不再自动收窄。缺省用全国范围。

**options.level** (`number`): 查询级别 1-18。
@defaultValue 10

**options.count** (`number`): 返回结果数量上限。

返回 `Promise<SearchResult>`（结构同 `search()`，典型为 `kind: 'poi'`）。

### `searchNearby()`

`search({ type: 'nearby' })` 的薄封装，直接返回 POI 列表。

**keyword** (`string`) *required*: 搜索关键词，如"银行""地铁站"。

**center** (`[number, number]`) *required*: 中心点坐标 \[lng, lat\]。

**options.radius** (`number`): 搜索半径（米）。
@defaultValue 5000

**options.count** (`number`): 返回结果数量上限。

返回 `Promise<Poi[]>`，每个 `Poi` 含 `name`/`address?`/`location`（`[lng, lat]`）等字段。

### `geocode()`

正地理编码：把结构化地址解析为精确坐标点；无结果或低置信度（`score < 60`）返回 `undefined`。适合“带门牌号的完整地址”；纯地标/地名请用 `locate()`。

**address** (`string`) *required*: 结构化地址，如"北京市海淀区莲花池西路28号"。

返回 `Promise<GeocodePoint | undefined>`：`location`（`[lng, lat]`）、`level?`（匹配级别）、`score?`（匹配得分）。

### `reverseGeocode()`

逆地理编码：把一个坐标点反查为结构化地址；无结果返回 `undefined`。

**point** (`[number, number]`) *required*: 坐标 \[lng, lat\]。

返回 `Promise<ReverseGeocodeResult | undefined>`：`formattedAddress` 及 `province?`/`city?`/`county?`/`road?`/`poi?`。

### `administrative()`

行政区划查询：按名称（或国标码）查中心点、边界轮廓、下级行政区。天地图返回的 WKT `MULTIPOLYGON`（真实省市常含飞地多环）已解析为 GeoJSON `MultiPolygon` 并转为 WGS84，可直接喂给 `MapboxLayer`。

**keyword** (`string`) *required*: 行政区名称或 9 位国标码，如"北京"或"156110000"。

**options.childLevel** (`0 | 1 | 2 | 3`): 下级行政区级数：0 不返回、1 下一级、2 下两级、3 下三级。
@defaultValue 0

**options.boundary** (`boolean`): 是否返回边界轮廓。
@defaultValue true

返回 `Promise<AdministrativeDivision[]>`：`name`/`code`/`level`/`center`、`boundary?`（GeoJSON `MultiPolygon`）、`children?`。

### `route()`

驾车/步行路线规划：返回沿真实路网的路径折线与真实距离/时长；未找到路线抛 `TiandituError`。

**origin** (`[number, number]`) *required*: 起点坐标 \[lng, lat\]。

**destination** (`[number, number]`) *required*: 终点坐标 \[lng, lat\]。

**options.mode** (`'fastest' | 'shortest' | 'avoid-highway' | 'walking'`): 路线类型：最快 / 最短 / 避开高速 / 步行。
@defaultValue 'fastest'

**options.waypoints** (`[number, number][]`): 途经点坐标数组。

返回 `Promise<RouteResult>`：`distanceKm`/`durationMinutes`、`path`（GeoJSON `LineString`）、`summary?`（转向摘要）、`center?`/`scale?`（适宜展示整条路线的相机参数）。

## Changelog

See commit history for [src/runtime/utils/tianditu-request.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-request.ts)、[src/runtime/utils/tianditu-client.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-client.ts)、[src/runtime/utils/tianditu-geocoder.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-geocoder.ts)、[src/runtime/utils/tianditu-administrative.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-administrative.ts)、[src/runtime/utils/tianditu-route.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-route.ts)、[src/runtime/utils/tianditu-search.ts](https://github.com/mhaibaraai/movk-mapbox/commits/main/src/runtime/utils/tianditu-search.ts).


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
