tdt-map-sdk
tdt-map-sdk
免费开源的天地图 JS API 4.0 轻量封装 —— 核心层(纯地图能力)+ 组件层(开箱即用的搜索面板/工具栏),PC / H5 通用,样式已内联无需单独引 CSS。
💯 完全免费:MIT 协议开源,无任何收费点、无功能阉割,商用无需向本 SDK 付费。地图底图由国家地理信息公共服务平台(天地图)提供,注册即可获取免费 key。
⚠️ 天地图官方对免费 key 设有调用额度,个人与企业的授权范围不同,商用上线前请以天地图官方条款为准。

✨ 特性
- 💯 免费开源:MIT 协议,无收费点、无需向本 SDK 付费,个人与商用项目均可自由使用
- 🎯 开箱即用:一行代码获得完整地图组件(搜索面板 + 工具栏 + H5 半屏)
- 📦 双层设计:核心层 12.5KB / 组件层 31.5KB(gzip 后更小),按需选择
- 🌐 框架无关:React / Vue / Angular / 原生 HTML 通用
- 📱 多端适配:PC 浮动卡片、H5 可拖拽半屏自动切换
- 🔧 零依赖:运行时不依赖任何第三方库
- 💪 TypeScript:完整类型声明
- 🇨🇳 国内合规:底图来自天地图,坐标符合国测标准,无境外服务依赖,国内访问速度快
🚀 快速开始
第一步:获取免费的天地图 key
本 SDK 免费,但需要一枚天地图开发者密钥(key)才能加载底图。key 免费申请,注册即可获得:
- 打开 天地图开发者平台,注册并登录
- 进入「控制台 → 应用管理 → 创建应用」,应用类型选 浏览器端
- 在应用里点击「添加 key」,
授权域名填你的站点域名(本地开发可填localhost) - 复制生成的 key 填入下面的代码
本地调试如果不想填域名限制,可先留空或用
localhost;上线前记得补上正式域名,否则浏览器控制台会报 403。 天地图的免费 key 有调用额度限制,个人与企业的授权范围不同,商用项目请以官方条款为准。
安装
npm install tdt-map-sdk
组件层:开箱即用(推荐)
一行代码,自带搜索面板、工具栏、H5 半屏:
import TdtMapWidget from 'tdt-map-sdk/widget';
new TdtMapWidget({
el: 'app',
key: '你的天地图key'
});
无需引入 CSS(样式已内联进 JS)
React 示例
import { useEffect, useRef } from 'react';
import TdtMapWidget from 'tdt-map-sdk/widget';
export default function MapPage() {
const containerRef = useRef(null);
const widgetRef = useRef(null);
useEffect(() => {
widgetRef.current = new TdtMapWidget({
el: containerRef.current,
key: '你的天地图key'
});
return () => widgetRef.current?.destroy();
}, []);
return <div ref={containerRef} style={{ width: '100vw', height: '100vh' }} />;
}
Vue 示例
<template>
<div ref="mapContainer" style="width: 100%; height: 100vh;"></div>
</template>
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue';
import TdtMapWidget from 'tdt-map-sdk/widget';
const mapContainer = ref(null);
const widget = ref(null);
onMounted(() => {
widget.value = new TdtMapWidget({
el: mapContainer.value,
key: '你的天地图key'
});
});
onBeforeUnmount(() => widget.value?.destroy());
</script>
CDN 直接引入
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script src="https://unpkg.com/tdt-map-sdk@latest/dist/tdt-widget.min.js"></script>
</head>
<body>
<div id="app"></div>
<script>
new TdtMapWidget({ el: 'app', key: '你的key' });
</script>
</body>
</html>
核心层:纯地图能力
只需要地图 API,不需要面板 UI:
import TdtMap from 'tdt-map-sdk';
const map = new TdtMap({
el: 'mapDiv',
key: '你的天地图key',
center: [116.40769, 39.89945],
zoom: 12
});
map.search('北京大学', { count: 10 }, (err, res) => {
if (!err) map.plotResults(res.pois);
});
📚 双层架构
tdt-map-sdk
│
├─ 核心层(TdtMap) 12.5 KB(含样式)
│ ├─ 地图加载、定位、缩放
│ ├─ 搜索 API、结果打点
│ ├─ 标记点、信息窗
│ └─ 全屏/小窗模式
│
└─ 组件层(TdtMapWidget) 31.5 KB(含样式)
├─ 包含核心层全部能力
├─ 搜索面板(地名搜索 + 坐标拾取)
├─ 右下工具栏(图层切换 + 定位按钮)
└─ H5 可拖拽半屏(三档吸附、惯性滑动)
选择指南
| 需求 | 推荐方案 | 引入路径 |
|---|---|---|
| 快速集成,不想写 UI | 组件层 | tdt-map-sdk/widget |
| React / Vue 项目 | 组件层 | tdt-map-sdk/widget |
| 需要深度定制搜索面板 | 核心层 | tdt-map-sdk |
| 只需要地图能力 | 核心层 | tdt-map-sdk |
| 想要免费替代高德 / 百度 / 腾讯地图 | 本 SDK | tdt-map-sdk/widget |
为什么选天地图而不是高德 / 百度 / 腾讯?
| 对比项 | 本 SDK(天地图) | 高德 / 百度 / 腾讯 JS API |
|---|---|---|
| SDK 授权费用 | 免费,MIT 协议 | 部分厂商 SDK 附带使用条款 |
| 底图服务费用 | 有免费额度,商用请查官方条款 | 基础量免费,超量/商用需付费 |
| 是否需实名认证 | 需注册天地图账号 | 多数需实名认证 |
| 坐标系 | 国测标准(CGCS2000) | 各家自有偏移坐标(GCJ-02 / BD-09) |
| 数据来源 | 国家地理信息公共服务平台 | 商业地图厂商 |
| 打包体积 | 核心 12.5KB / 组件 31.5KB | 通常数百 KB |
| 引入方式 | npm / CDN,样式内联 | npm / CDN,通常需额外引 CSS |
如果你的项目追求无 SDK 授权费、体积极小、坐标系合规,本 SDK 是值得评估的免费替代方案。
🎯 组件层:TdtMapWidget
自带功能
PC 端
- ✅ 左上浮动搜索面板(地名搜索 + 坐标拾取,双页签)
- ✅ 右下工具栏(图层切换:矢量/影像/混合 + 定位按钮)
- ✅ 搜索结果列表(可点击跳转)
H5 端
- ✅ 底部可拖拽半屏(三档:收起 / 半屏 / 全屏)
- ✅ 惯性滑动 + 吸附动画
- ✅ 轻点把手切换档位
半屏档位由面板高度(
clamp(420px, 55vh, 84vh))与固定露出高度计算,与屏幕高矮解耦,三档顺序不会因屏幕不同而翻转。搜索结果渲染导致面板高度变化时会自动重算档位。
API
class TdtMapWidget {
constructor(options: {
el: string | HTMLElement; // 容器(必填)
key: string; // 天地图 key(必填)
center?: [number, number]; // 初始中心 [lng, lat]
zoom?: number; // 初始缩放级别
search?: boolean; // 显示搜索页签(默认 true)
coord?: boolean; // 显示坐标页签(默认 true)
tools?: boolean; // 显示工具栏(默认 true)
miniable?: boolean; // 是否允许小窗模式(默认 false,显示小窗内的"展开"按钮)
mode?: 'full' | 'mini'; // 初始模式(默认 'full')
onReady?: (widget) => void;
onError?: (error) => void;
mapOptions?: {}; // 额外透传给 TdtMap 的选项
});
// 获取底层 TdtMap 实例
getSDK(): TdtMap;
// 获取组件根元素(挂载自定义节点 / 打样式补丁)
getElement(): HTMLElement;
// 地名搜索(结果自动渲染到面板)
search(keyword: string, opts?: {
count?: number; // 默认 10
plot?: boolean; // 是否打点,默认 true
}): this;
// 周边搜索(默认以当前地图中心为圆心)
searchNearby(keyword: string, radius?: number, opts?: {
count?: number; // 默认 10
lng?: number; // 指定圆心经度(不传则取地图中心)
lat?: number; // 指定圆心纬度
plot?: boolean; // 是否打点,默认 true
}): this;
// 定位到指定坐标
locate(lng: number, lat: number, opts?: {}): boolean;
// 切换图层:'vec' | 'sat' | 'hyb'
switchLayer(type: string): this;
// 切换页签:'search' | 'coord'
switchTab(tab: string): this;
// 切换模式:'full' | 'mini'
setMode(mode: 'full' | 'mini'): this;
// 读取当前模式
getMode(): 'full' | 'mini';
// H5:把底部半屏展开到全屏档(PC 端无半屏时无操作)
openFull(): this;
// 监听事件:'ready' | 'error' | 'search' | 'expand' | 'collapse'
on(event: string, handler: Function): () => void;
// 显示提示条
toast(msg: string, duration?: number): this;
// 销毁组件
destroy(): void;
}
小窗模式(mini)
组件支持在「铺满容器的全屏模式」和「340×240 小窗模式」之间切换,用于地图作为辅助工具而非主界面的场景(后台管理页角落挂一张图、客服工作台侧边预览等)。
const widget = new TdtMapWidget({
el: 'app',
key: '你的天地图key',
mode: 'mini', // 初始就渲染为小窗
miniable: true // 显示小窗右上角的"展开"按钮
});
// 代码控制切换
widget.setMode('full'); // 展开为全屏
widget.setMode('mini'); // 收回小窗
widget.getMode(); // 读取当前模式:'full' | 'mini'
// 监听切换(可用于同步宿主页面自身的布局)
widget.on('expand', () => console.log('已展开到全屏'));
widget.on('collapse', () => console.log('已收起到小窗'));
行为说明:
setMode('mini')时,卡片缩为居中 340×240 小窗(可通过 CSS 覆盖),搜索面板与右下工具栏自动隐藏,miniable: true时显示展开按钮,点击即回到全屏。- 模式切换后组件会自动调用底层
checkResize(),并在 320ms 后补一次,避免地图在小窗中拉伸变形。 mode: 'mini'与miniable相互独立:只设mode: 'mini'而不设miniable时不会出现展开按钮,适合由宿主自己的按钮来控制模式。- 小窗尺寸改由 CSS 决定,宿主可覆盖:
.tdt-widget.tdt-mode-mini .tdt-widget-card {
width: 400px;
height: 300px;
}
完整示例见 examples/mini-mode.html。
事件
const off = widget.on('search', (res) => {
console.log('搜索到', res.pois.length, '条结果');
});
off(); // 调用返回值即可取消该监听
| 事件 | 触发时机 | 回调参数 |
|---|---|---|
ready |
地图初始化完成 | TdtMap 实例 |
error |
初始化失败(key 错误、脚本加载失败等) | Error |
search |
每次搜索返回(含无结果 / 行政区定位) | { pois, area, resultType } |
expand |
由 mini 切回 full | 无 |
collapse |
由 full 切到 mini | 无 |
调用底层 API
const widget = new TdtMapWidget({ el: 'app', key: 'xxx' });
const map = widget.getSDK(); // 获取底层 TdtMap 实例
// 调用核心层的任何方法
map.on('click', (e) => console.log(e.lnglat));
map.addMarker(116.4, 39.9, { title: '自定义点' });
样式定制
/* 修改主题色 */
:root {
--tdt-accent: #ff6b6b;
--tdt-accent-light: #ffe0e0;
}
/* 修改搜索面板宽度 */
.tdt-widget-panel { width: 400px; }
恢复原生滚动条
SDK 默认隐藏所有 .tdt-* 元素的滚动条(含搜索结果列表),如需恢复:
.tdt-widget-results {
scrollbar-width: auto;
-ms-overflow-style: auto;
}
.tdt-widget-results::-webkit-scrollbar {
width: 6px;
height: 6px;
display: block;
}
🔧 核心层:TdtMap
API
class TdtMap {
constructor(options: {
el: string | HTMLElement;
key: string;
center?: [number, number];
zoom?: number;
mapType?: 'vec' | 'sat' | 'hyb';
onReady?: (instance) => void;
onError?: (error) => void;
});
// 定位到指定坐标
locate(lng: number, lat: number, opts?: {
zoom?: number;
title?: string;
content?: string;
}): boolean;
// 添加标记点
addMarker(lng: number, lat: number, opts?: {
title?: string;
content?: string;
icon?: string;
}): Marker | null;
// 地名搜索
search(keyword: string, opts: {
count?: number; // 返回条数,默认 10
start?: number; // 起始序号(分页)
level?: number; // 结果层级
mapBound?: string; // 限定搜索范围
}, callback: (err, result) => void): void;
// 周边搜索
searchNearby(keyword: string, lng: number, lat: number,
radius: number, opts: {}, callback: Function): void;
// 搜索结果打点
plotResults(pois: Array): void;
// 清除自定义标记点
clearMarkers(): this;
// 清除搜索结果标记点
clearResultMarkers(): this;
// 切换底图
switchMap(type: 'vec' | 'sat' | 'hyb'): this;
// 地理定位
geolocate(opts?: {
onError?: (error) => void;
}): void;
// 获取当前中心点
getCenter(): [number, number] | null;
// 获取当前缩放级别
getZoom(): number | null;
// 容器尺寸变化后手动触发地图重绘(宿主布局变动时调用)
resize(): void;
// 监听事件
on(event: 'ready' | 'load' | 'click', handler: Function): void;
// 切换模式
setMode(mode: 'full' | 'mini'): this;
// 读取当前模式
getMode(): 'full' | 'mini';
// 销毁地图
destroy(): void;
}
// 静态成员
TdtMap.version; // 版本号
TdtMap.isValidMapType('sat'); // 校验底图类型:'vec' | 'sat' | 'hyb'
📦 包导出
{
"exports": {
".": "./dist/tdt-map.esm.js", // 核心层
"./widget": "./dist/tdt-widget.esm.js", // 组件层
"./core": "./dist/tdt-map.esm.js" // 核心层别名
}
}
📄 许可证
MIT
🙋 FAQ
Q: 真的完全免费吗?会不会有隐藏收费?
本 SDK 免费。以 MIT 协议开源,源码可自由使用、修改、商用,没有任何收费点或功能锁,也不需要向本 SDK 支付任何费用。
唯一的外部依赖是天地图 key,激活也是免费的,但天地图官方对免费 key 设有调用额度,且个人与企业账号的授权范围不同。个人学习、开源项目、中小流量站点通常够用;商用或无授权顾虑的场景,建议发布前查阅天地图官方最新条款,必要时申请更高配额。这部分属于天地图平台的政策,SDK 侧不做任何限制。
Q: 和直接调用天地图官方 JS API 有什么区别?
官方 API 只提供地图能力,搜索框、坐标拾取、图层按钮、H5 半屏这些 UI 都需要自己写。本 SDK 把这些封装好了,组件层一行代码即可获得完整交互界面;如果只想用纯地图能力,用核心层即可,体积仅 12.5KB。此外还补齐了官方 API 中较繁琐的搜索请求签名、结果解析与打点逻辑。
Q: 支持哪些框架?能在小程序里用吗?
支持所有能运行 JS 的环境:React、Vue、Angular、Svelte、原生 HTML 均可直接使用。微信小程序不支持,因为小程序没有 DOM 且禁止动态加载外部脚本,请改用天地图的小程序 SDK。
Q: 组件层是否包含核心层?
是的。TdtMapWidget 内部封装了 TdtMap,无需同时引入两者。
Q: 坐标怎么和高德 / 百度互转?
天地图使用国测标准坐标(CGCS2000,与 WGS84 在民用精度下可视为一致),高德/腾讯使用 GCJ-02,百度使用 BD-09。跨平台数据混用前需做坐标转换,否则会有几十到几百米的偏移。本 SDK 不做隐式转换,避免引入误差。
Q: 可以隐藏组件的某些部分吗?
可以。通过初始化选项控制:
new TdtMapWidget({
el: 'app',
key: 'xxx',
search: false, // 隐藏搜索页签
coord: false, // 隐藏坐标页签
tools: false // 隐藏右下工具栏
});
Q: 小窗模式(mini)怎么用?
初始化传 mode: 'mini' 即可渲染为小窗;miniable: true 会额外显示小窗内的展开按钮,点它回到全屏。也可随时用 widget.setMode('mini' | 'full') 切换,并用 widget.on('expand' | 'collapse', fn) 同步宿主页面布局。详见小窗模式(mini)。
Q: CSS 会污染全局吗?
不会。所有样式类都带 .tdt- 或 .tdt-widget- 前缀。
🔗 相关链接
v1.3.0 · Made with ❤️
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 1.3.2 | MIT | |
| 1.3.1 | MIT | |
| 1.3.0 | MIT |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |