简介:一套面向Web开发者与GIS初学者的百度地图地名定位源码包,演示如何输入地名调用百度地图地名库获取经纬度坐标,并在地图上生成Marker、拖动Marker及重复定位更新坐标。压缩包共8个文件,体积仅5KB,包含1个可直接运行的index.html页面、3个txt功能说明/代码片段,以及4个补充参考用url链接(涵盖localSearch检索结果转换、Ajax保存坐标到数据库、覆盖物拖放获取坐标等),结构精简,适合快速上手与二次开发。目前已吸引888人学习浏览。通过学习可掌握地理编码、localSearch结果转点、Marker创建与setPosition更新、Ajax持久化坐标等完整思路,并能在导航、本地服务查找等场景中复用。
1. 百度地图地名定位:从地址到坐标,为什么不是一次搜出来就完事
做Web地图应用时,最常见的需求是用户在文本框里敲一个地名,地图立刻移过去并打点。看起来只是“搜索+定位”两步,实际开发里却常常卡在交互细节上:连续输入要不要清除旧点?检索结果里的POI怎么取坐标?保存到后台后再回显为什么偏了几百米?这套源码就是用百度地图JavaScript API把地名输入、本地检索、POI坐标提取、可拖动Marker、Ajax保存串成一条完整闭环,解决的不光是“能不能出来点”,而是“多次操作和落库回显还准不准”。适合正在搭建工单定位、人员位置管理或本地服务查找页面的开发者。从地图定位这个核心需求出发,后续所有代码都围绕一个目标:让地名到坐标的转换可交互、可重复、可保存。
2. 原理与选型:地理编码、本地搜索和 getPoi 的边界到底在哪
2.1 地理编码和本地搜索的分工
先理清楚一个概念:地图上一切打点、轨迹、测距都建立在经纬度上。用户输入的是文字地名,要变成坐标,就只能靠地图服务商提供的编码能力。百度地图JavaScript API里有两套方案:Geocoder地理编码服务和LocalSearch本地检索服务。Geocoder吃的是门牌号级别的完整地址,比如“某市高新区科技路1号”,它能给出相对平滑的坐标结果;LocalSearch吃的是关键词,比如“某某大厦”“某某科技园”,它会按POI匹配返回一组候选点。两者的返回结构完全不同:Geocoder回调里是GeocoderResult,LocalSearch回调里是LocalResult,后者才带getPoi方法。
选型时要看入口。如果你的页面只有一个必须精确到门牌的地址输入框,优先用Geocoder,它更适合标准地址的批量转换。但是像“输入地名,地图上定位”这种场景,用户往往只记得一个地名或楼宇名,这时Geocoder经常因为地址不标准而返回偏差,LocalSearch反而能给出多个候选,方便用户确认。这个资源走的是LocalSearch + getPoi路线,原因是它更贴近真实输入习惯,同时getPoi返回的point对象能直接用在地图绘制上。没有哪个绝对好,只有适不适合你的输入形态。
还要注意一个隐藏问题:Geocoder对模糊地名有时会返回“行政区中心点”,比如搜一个园区名,结果给你降到区政府的中心坐标,这在需要精确定位的业务里是不能接受的。而LocalSearch的POI数据通常来自地图标注和商户数据,只要地名在库里有标注,返回的点就相对靠谱。这就是为什么我一般把Geocoder当成“兜底”,把LocalSearch当成“主查询”,两个方法各司其职。
2.2 localResult.getPoi 返回了什么:POI 对象里的关键属性
本地搜索在执行后,回调参数是localResult。常见的错误是把这个对象当成数组,直接localResult[0],结果拿到undefined。正确做法是调用localResult.getPoi(index)返回第 index 个 POI 对象。POI 对象上有title、address、point、phone等字段,其中point就是百度坐标系下的BMap.Point,可以直接传给Marker。
这里有一个大家容易忽略的分页:默认每页10条结果,如果搜索结果很多,localResult.getPageCount()会大于 1。你要做的不是只取getPoi(0),而是先判断getCurrentNumPois(),再按需遍历当前页。把结果转成坐标的核心动作非常简单,难点在于什么时候取、取到第几个,以及有没有把点丢进地图坐标系。看这段典型的回调处理:
// 假设已经在页面里创建了map,并成功添加了百度地图API脚本 let localSearch = new BMap.LocalSearch(map, { pageCapacity: 10, // 每页显示结果数量 onSearchComplete: function(localResult) { // 先检查状态,再检查结果数量,避免取到undefined if (localSearch.getStatus() !== BMAP_STATUS_SUCCESS) { console.warn('检索失败,状态码:', localSearch.getStatus()); return; } if (!localResult || localResult.getCurrentNumPois() === 0) { console.warn('没有匹配的POI'); return; } // 取第一个POI作为默认落点 let poi = localResult.getPoi(0); console.log('POI名称:', poi.title); console.log('POI坐标:', poi.point.lng, poi.point.lat); // 这里可以直接把一个Marker挂到地图上,后面会讲 // map.addOverlay(new BMap.Marker(poi.point)); } });逻辑说明:onSearchComplete是搜索完成后的回调,里面先通过localSearch.getStatus()判断网络和检索状态,再通过getCurrentNumPois()获取当前页实际返回的POI数量。最后用getPoi(0)取出第一个POI并打印它的坐标。参数说明:pageCapacity控制每页返回条数,最小是1,最大是20,默认10;如果你只想拿第一个候选,可以把它调小,减少不必要的流量。这里把打印放进去,是为了让你先确认数据长什么样,再继续写后续逻辑。
2.3 为什么不直接用 Geocoder:LocalSearch 的取舍
当用户输入“人民广场”这种泛地名时,Geocoder大概率返回一个行政层面或默认中心点,甚至失败;而LocalSearch会列出多个同名POI,让用户选择。这就是资源选择LocalSearch路线的核心原因。不过也要看到它的代价:第一,本地搜索依赖POI库,一些新小区或村庄可能搜不到;第二,LocalSearch返回的是兴趣点坐标,和门牌坐标可能有细微偏差;第三,回调是异步的,如果你在后面代码里立即读取一个全局变量,会踩到“变量还没赋值”的坑。
常见的做法是:先Geocoder解析门牌号,如果状态码异常或者坐标落在某个荒谬位置,再降级到LocalSearch。这个资源没有做降级,但你在真实项目里应该加。理解了这个选型逻辑,再往下看页面落地版本,就会知道为什么回调里一堆代码看起来“绕”——不是绕,而是异步回调本来就需要把后续动作放到回调内部执行,而不是靠外部变量顺延。
3. 落地第一个版本:在 index.html 里完成地名输入、检索、坐标转换
3.1 初始化地图:HTML 骨架和 API 引入
先搭一个最简页面。地图容器必须给高度,否则初始化后是零高度,看不到地图。API 的引入放在app.js之前,确保全局BMap对象存在。代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>地图地名定位Demo</title> <style> #map-container { width: 100%; height: 500px; } #keyword, #search-btn { padding: 8px; font-size: 14px; } #candidate-box { margin-top: 8px; } #candidate-box button { display: block; margin: 4px 0; padding: 6px; cursor: pointer; } </style> </head> <body> <div style="padding: 10px;"> <input id="keyword" type="text" placeholder="输入地名,然后回车或点击检索" /> <button id="search-btn">检索并定位</button> </div> <div id="map-container"></div> <div id="candidate-box"></div> <!-- 替换为你在百度地图开放平台申请到的ak --> <script src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=YOUR_AK"></script> <script src="app.js"></script> </body> </html>逻辑说明:页面里放了一个输入框、一个按钮、一个地图容器、一个候选列表容器。地图容器高度写死 500px,这是地图能正常渲染的前提。ak必须替换成自己应用的密钥,不能用别人的,否则会有域名白名单校验或者调用失败。参数说明:type=webgl是 WebGL 版本,渲染性能更好,适合矢量地图;如果你维护的是老项目,也可以用v=2.0的方式,但交互事件和坐标系是同一套,下面代码基本通用。
初始化地图的代码写在app.js:
// app.js // 初始化地图,中心点先用业务区域附近的占位坐标 const map = new BMap.Map('map-container', { enableMapClick: true }); map.centerAndZoom(new BMap.Point(120.1, 30.2), 14); map.enableScrollWheelZoom(true);逻辑说明:new BMap.Map('map-container')是百度地图标准的初始化方式,第一个参数是容器元素的 id。centerAndZoom设置初始中心和缩放级别,这里用了占位坐标,实际项目中你应该把它改成业务覆盖区域的中心,否则用户打开页面要拖半天才能找到目标。enableScrollWheelZoom(true)打开鼠标滚轮缩放,这里对应一个布尔参数,不传默认也是关闭。注意:如果地图容器初始是隐藏的,比如在 Tab 页里,渲染高度可能为 0,这里建议等 Tab 切换后再初始化地图,或者调用map.invalidateSize()。
3.2 文本框事件:防抖与回车触发
不要每一次input都发检索,那样会频繁触发接口,也容易造成请求顺序乱掉。用一个定时器做防抖,用户停顿 300ms 后才检索,回车则立即检索。代码如下:
const input = document.getElementById('keyword'); const btn = document.getElementById('search-btn'); let timer = null; function handleSearch() { const keyword = input.value.trim(); if (!keyword) { console.warn('地名不能为空'); return; } doLocalSearch(keyword); } input.addEventListener('keyup', function (event) { // 回车直接搜 if (event.key === 'Enter') { clearTimeout(timer); handleSearch(); return; } // 连续输入时只响应最后一次,防抖300ms clearTimeout(timer); timer = setTimeout(handleSearch, 300); }); btn.addEventListener('click', handleSearch);逻辑说明:这里用keyup监听回车和普通输入,event.key === 'Enter'时立即清除之前的定时器并执行搜索,避免回车前刚触发过一次防抖。普通输入时,每次都清掉上一次定时器,只有停下来 300ms 后才执行。参数说明:300ms 是一个比较稳的防抖阈值,如果你的地名选项来自后端联想接口,建议加长到 400~500ms;如果只是本地检索,300ms 正好。这个防抖还兼了“避免短时间重复请求”的作用,比单纯在接口层做限制体验好。
3.3 用 LocalSearch 搜索并取出候选坐标
接下来是核心的检索函数。LocalSearch实例只需要创建一次,因为它内部会保存地图实例和回调,重复调用search会复用。每次搜索后,回调里通过getPoi(i)取出 POI 的坐标,再传给后续定位函数。
let localSearch = null; function doLocalSearch(keyword) { if (!localSearch) { localSearch = new BMap.LocalSearch(map, { pageCapacity: 10, onSearchComplete: function (result) { if (localSearch.getStatus() !== BMAP_STATUS_SUCCESS) { alert('未找到该地名,请换个关键词'); return; } const count = result.getCurrentNumPois(); if (count === 0) { alert('该地名暂没有POI数据'); return; } // 先取第一个POI,后续可以换成用户手动选择 const poi = result.getPoi(0); const point = poi.point; console.log('检索到坐标:', point.lng, point.lat); locateToPoint(point, poi.title, poi.address); } }); } localSearch.search(keyword); }逻辑说明:这个版本先硬取第一个候选。getStatus()返回的是数字状态码,BMAP_STATUS_SUCCESS是 API 内置常量,等于 0。如果成功但没有 POI,会提示用户换关键词。取出的poi.point就是百度坐标系下的坐标,可以直接传给locateToPoint。参数说明:pageCapacity这里设为 10,意思是每页最多返回 10 个候选;如果你只需要第一个,可以改成 1,接口响应会更快,但这会牺牲候选列表。
3.4 检索结果为空或候选过多:展示列表而不是硬取第一个
用户输入“某某大厦”可能有四五个同名地点,硬取第一个很容易翻车。我一般会把候选全部渲染出来,让用户点选,同时默认落第一个点。这样兼顾了自动定位和用户纠错。代码:
function doLocalSearch(keyword) { if (!localSearch) { localSearch = new BMap.LocalSearch(map, { pageCapacity: 10, onSearchComplete: function (result) { const status = localSearch.getStatus(); if (status !== BMAP_STATUS_SUCCESS) { renderCandidateList([]); return; } const total = result.getCurrentNumPois(); const candidates = []; for (let i = 0; i < total; i++) { const poi = result.getPoi(i); candidates.push({ name: poi.title, address: poi.address, point: poi.point }); } renderCandidateList(candidates); if (candidates.length > 0) { locateToPoint(candidates[0].point, candidates[0].name, candidates[0].address); } } }); } localSearch.search(keyword); } function renderCandidateList(list) { const box = document.getElementById('candidate-box'); if (!box) return; box.innerHTML = ''; if (list.length === 0) { box.textContent = '暂无匹配结果'; return; } list.forEach(function (item, index) { const btn = document.createElement('button'); btn.textContent = (index + 1) + '. ' + item.name + ' ' + (item.address || ''); btn.addEventListener('click', function () { locateToPoint(item.point, item.name, item.address); }); box.appendChild(btn); }); }逻辑说明:回调里遍历当前页所有 POI,把它们转成一个数组,再渲染到#candidate-box。默认情况下仍然用第一个候选自动定位,用户如果不满意,可以点候选列表里的其他按钮,然后重新定位。参数说明:这里没有处理多页情况,如果result.getPageCount()大于 1,后续查询需要调用localSearch.pageNext()或gotoPage()。对一般交互来说,第一页 10 个候选已经足够。
4. Marker 生成、拖动与二次定位:让坐标点能改能移
4.1 创建可拖动的 Marker
定位到坐标后,需要在地图上落一个点。这个点要能显示名称,还要允许用户手动拖动调整。百度地图里对应对象是BMap.Marker,准确定位后通过map.addOverlay挂到地图上。
let currentMarker = null; function locateToPoint(point, name, address) { clearCurrentMarker(); currentMarker = new BMap.Marker(point, { enableDragging: true, // 允许用户拖动 title: name || '定位点' }); map.addOverlay(currentMarker); map.panTo(point); map.setZoom(16); const info = new BMap.InfoWindow('<p>' + (name || '') + '</p><p>' + (address || '') + '</p>', { width: 200, title: '定位结果' }); map.openInfoWindow(info, point); }逻辑说明:每次定位前先清掉旧 Marker,避免重复叠加。enableDragging: true是关键参数,如果不加这个配置,Marker 看着能碰,但拖不走。panTo让地图平滑移动到点所在位置,setZoom(16)把缩放级别放到街区级,城市级地图通常 10~12,街道建筑级 16~18。信息窗口是可选的,但建议保留,因为用户需要确认自己选的是不是这个地方。
Marker 配置项可以单独抽一张表,方便你按场景调整:
| 参数 | 可选值 | 作用 |
|---|---|---|
| enableDragging | true / false | 是否允许拖拽 |
| title | 字符串 | 鼠标悬停时的提示文字 |
| icon | BMap.Icon 实例 | 自定义 Marker 图标 |
| rotation | 数字角度 | 旋转角度,箭头类图标常用 |
| top | 布尔值 | 是否覆盖其他覆盖物(不常用) |
参数说明:多数业务只用enableDragging和title,自定义图标需要额外创建BMap.Icon,并传入大小和中心点,不在这个源码的核心路径里。
4.2 拖动结束事件:读取最新坐标
Marker 拖完以后,要拿到新坐标。百度地图提供了dragend事件,事件对象里的point就是拖动结束后的落点。
currentMarker.addEventListener('dragend', function (event) { const newPoint = event.point; // 拖动后最新坐标 console.log('用户把点拖到了:', newPoint.lng, newPoint.lat); updateCoordDisplay(newPoint.lng, newPoint.lat); }); function updateCoordDisplay(lng, lat) { const el = document.getElementById('coord-info'); if (el) { el.textContent = '经度:' + lng.toFixed(6) + ',纬度:' + lat.toFixed(6); } }逻辑说明:dragend事件的event.point是百度提供的坐标点。toFixed(6)可以保留 6 位小数,经纬度 6 位小数大约对应 0.1 米精度,足够落库。实际项目中,我会同时更新一个历史记录数组,记录这个点从自动定位到手动拖动的变化轨迹,方便后台审核坐标是否被人工改过。参数说明:如果你用的是dragstart事件,只能拿到事件但拿不到最终坐标,别在dragstart里读点。
4.3 重新定位:清掉旧 Marker 再画新点
重复输入地名时,如果不清理旧 Marker,地图上会堆出十几个点,用户根本分不清哪个是最新结果。清理有两种方式:map.clearOverlays()会清掉所有覆盖物,适合一次性重置的场景;map.removeOverlay(marker)只清除指定 Marker,更精准。这个项目用后者,因为地图上可能还有线路、热力图等其他覆盖物。
function clearCurrentMarker() { if (currentMarker) { map.removeOverlay(currentMarker); currentMarker = null; } }逻辑说明:移除 Marker 后要把引用置为null,否则下一次removeOverlay可能会对已经不在地图上的对象重复执行。注意,这里只清当前 Marker,不会动其他覆盖物,所以比clearOverlays()安全。使用clearOverlays()的典型场景是“重新搜索时把之前的候选点、路线全清掉”,代价是自定义覆盖物也没了,需要业务自行权衡。
4.4 地图视野跟随:panTo 与 setZoom 的搭配
定位不只是打点,还要把地图视野粘过去。panTo是平滑移动,setZoom是设置缩放级别。两者搭配时有一个细节:如果新点离当前视野很近,panTo可能感觉不到变化;如果很远,直接panTo又会让用户看不清路径。常见做法是先setZoom再panTo,减少视野跳变的突兀感。
function relocate(point) { // 当前缩放级别低于16时,先放大再移动 if (map.getZoom() < 16) { map.setZoom(16); } map.panTo(point); }逻辑说明:这个写法只在缩放级别不足 16 时放大,已经够大就不重复设置。参数说明:getZoom()返回当前缩放级别;setZoom接受 3~19 之间的整数,16 适合看一个街区或园区,如果只定位一个楼,可以设 17~18。注意panTo的动画时间默认是 100ms 量级,不需要额外配置。
5. 保存到数据库与避坑指南:Ajax 回传、异步陷阱、坐标偏移
5.1 用 Ajax 把坐标和地名保存到后台
前端拿到坐标后,最终要存库。这里用fetch发送 POST JSON,不依赖 jQuery。重点是要把坐标系类型一起提交,很多项目最后坐标偏了几百米,就是因为不知道库里存的是哪种坐标。
async function savePointToServer(name, point) { const payload = { name: name, lng: point.lng, lat: point.lat, coordType: 'BD-09' // 明确记录坐标系,避免后续混用 }; const response = await fetch('/api/point/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { throw new Error('保存失败,HTTP状态码:' + response.status); } const data = await response.json(); console.log('后台返回:', data); return data; }逻辑说明:这个接口路径是示例,真实后台根据自己框架去写。前端提交的lng和lat是 Number 类型,coordType固定写BD-09,意思是百度坐标。参数说明:为什么不用字符串拼接?因为POST用 JSON 最稳,后端解析不需要处理 URL 编码;response.ok在 HTTP 200~299 之间才为true,配合throw可以在调用方捕获异常。
5.2 后台存储:字段类型与坐标系要一起考虑
后台对应的表结构里,经纬度字段不要用FLOAT,精度不够,高精度定位时会丢位。建议用DECIMAL(10,7),整数部分三位给经度,小数部分七位保证亚米级精度。同时要有coord_type字段,记录坐标来自百度、高德还是原始 GPS。
CREATE TABLE location_point ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, point_name VARCHAR(100) NOT NULL, lng DECIMAL(10, 7) NOT NULL, lat DECIMAL(10, 7) NOT NULL, coord_type VARCHAR(20) DEFAULT 'BD-09', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );逻辑说明:DECIMAL(10,7)的整数位是10-7=3,经度最大 180,三位够用,纬度最大 90,更没问题。coord_type默认BD-09,如果接入其他地图数据,可以改成GCJ-02或WGS-84。参数说明:point_name长度 100 足够,不要用TEXT,索引和排序都不友好。
这里要说清楚坐标系偏移问题:百度地图用的是 BD-09 坐标系,它不是纯粹的 GPS 原始坐标。如果一个坐标是从百度地图拿来的,存库时标记成BD-09,之后放到使用 GCJ-02 的高德地图上,就会偏几百米。反之亦然。你需要做的不是把所有坐标强制转成 WGS-84,而是保持一个统一的坐标标准,并在入库时留好标记。常见做法是:百度地图体系内直接存 BD-09,高德体系存 GCJ-02,外部 GPS 设备数据先转成目标坐标系再入业务库。
5.3 常见问题排查:地图不显示、定位错位、Marker 拖不动
这一部分都是真实开发里踩过的坑,每条按现象到原因到解决整理,可以直接当排查手册用。
地图区域空白或提示“APP Referer 校验失败”。现象:页面其他元素正常,地图块空白,控制台输出校验失败信息。原因:申请的
ak没有启用,或者开放平台里的 Referer 白名单没把当前域名加进去。解决:去百度地图开放平台检查应用配置,把本地调试的localhost和线上域名都加进白名单,ak换掉重新加载页面。注意本地用 IP 访问时,也要把 IP 加入白名单。检索回调不执行。现象:输入地名后没有任何反应,控制台也不打印。原因:
LocalSearch构造时没传onSearchComplete回调,或者keyword是空字符串,search直接没发起。解决:确认回调写在构造参数里,在search前打印 keyword,排除输入问题。另一个常见原因是地图初始化失败,LocalSearch构造时依赖一个有效map实例。调用
localResult.getPoi(0)报 “getPoi is not a function”。现象:console 提示这个函数找不到。原因:有人把onSearchComplete的形参直接当成数组,写成result[0],或者把BMap.LocalSearch的实例当成结果对象。解决:回调的第一个形参就是localResult,必须使用getPoi方法。这是源码资源里最容易被误解的地方。Marker 拖不动。现象:鼠标按住 Marker 后出现移动光标,但点不跟着走。原因:创建 Marker 时没有设置
enableDragging: true,或者 Marker 被其他覆盖物遮挡了点击事件。解决:在new BMap.Marker(point, { enableDragging: true })里加上这个配置,检查是否有透明覆盖物悬浮在 Marker 上层。坐标保存后回显偏了几百米。现象:从数据库取出坐标,重新放到地图上,点和真实位置偏一条街以上。原因:入库时没有记录坐标系,或者前端用了百度坐标,后端却按高德坐标处理。解决:统一在
coord_type字段标记坐标系,展示前或入库前做一次坐标转换。我一般会在保存接口里固定coordType: 'BD-09',后端再校验这个字段,避免别的模块乱写。
6. 进阶:批量地名定位、坐标纠偏与结果验证
6.1 批量地名定位:控制并发而不是一次性丢一堆
如果你有几百个地名要定位,千万不要for循环同步发请求,浏览器有并发上限,百度侧也有配额限制。常见做法是用一个固定并发的任务队列,每次只跑 3 个请求,完成一个补一个。
async function batchLocate(list, concurrency = 3) { const results = []; const queue = list.map((item, index) => ({ item, index })); const workers = Array.from({ length: concurrency }, async () => { while (queue.length > 0) { const task = queue.shift(); const point = await searchOneKeyword(task.item); results[task.index] = point; } }); await Promise.all(workers); return results; }逻辑说明:queue保存带原始索引的任务,避免并发完成后结果顺序错乱;concurrency默认 3,是比较保守的参数,实际可按账号配额调整。searchOneKeyword需要自己封装,把LocalSearch的回调包成 Promise,这一步不复杂,但不包会很难控制并发。
6.2 坐标纠偏:BD09 转 GCJ02 的一个实用转换函数
如果你的项目还要和高德或者第三方平台交换数据,就有必要准备一个坐标转换函数。社区里常见的转换算法如下,误差在 1~2 米,适合展示层,不适合高精度测绘。
function bd09ToGcj02(lng, lat) { const x = lng - 0.0065; const y = lat - 0.006; const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * 0.017453292519943); const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * 0.017453292519943); return { lng: z * Math.cos(theta), lat: z * Math.sin(theta) }; }逻辑说明:这组公式是社区整理出来的近似转换,核心思路是做偏移还原和旋转系数调整。参数说明:0.017453292519943是角度转弧度的系数,固定常数;0.0065和0.006是经纬度方向上的初始偏移量,因为坐标系间存在系统性的平移偏差。真实项目里如果对接需求多,建议直接用地图服务商提供的转换接口,自己维护公式会面临版本更新风险。
6.3 定位结果的验证方法
定位做完了,怎么判断偏不偏?一个直接的办法:把坐标生成一个 KML 文件,丢到卫星图工具里叠看。下面这段代码可以从当前 Marker 坐标生成 KML:
function toKml(name, point) { return `<?xml version="1.0" encoding="UTF-8"?> <kml xmlns="http://www.opengis.net/kml/2.2"> <Placemark> <name>${name}</name> <Point><coordinates>${point.lng},${point.lat},0</coordinates></Point> </Placemark> </kml>`; }逻辑说明:KML 的坐标顺序是经度、纬度、高度,和常见习惯相反,这里不要写反。参数说明:name是定位点名称,方便在卫星图工具里识别。生成后保存成.kml文件,用桌面端 GIS 工具打开,对比卫星影像里的实际建筑,就能看出定位是否准确。
以前我总是一口气把几百个地名丢进接口,结果限流加数据错乱,后来我给自己定了个规矩:批量任务必须加并发控制,所有入库坐标必须带坐标系标记,每次上线前抽几个坐标点导出 KML 做卫星图抽检。这套流程看着啰嗦,但能拦住大量“坐标玄学”问题,从那以后我每次做地图定位功能,都会强制走一遍这三个检查项。希望帮到你。
本文还有配套的精品资源,点击获取