☰
高德地图JS API三件套:标注、定位与路线规划的工程实战
2026/9/28 2:53:22 网站建设 项目流程

简介:面向Android开发者的高德地图集成压缩包,围绕地图标注、路线规划与地图定位三大核心功能,提供可直接运行的Java示例工程。包内共100个文件,仅3.34MB,结构涵盖Java源码、class编译产物、xml布局与地图配置、jar依赖库、png图标素材,并附有AMapDemo.apk安装包及工程配置文件,覆盖编码到验证的完整链路。当前已有304人学习下载,适合希望快速上手高德地图API、减少摸索成本的初中级移动开发者。通过阅读源码与运行App,能够掌握地图初始化、自定义Marker标注、路线规划请求的组装与结果解析,以及驾车、步行等多种出行方式下的路径绘制;同时可学习定位权限配置、坐标转换与地图生命周期管理等细节。借助APK可直观验证标注和路线效果,有助于加速地图功能的集成与调试,是轻量实用的入门参考包。

1. 高德地图标注、路线规划、地图定位在一个包里的背后:一套JS API三件套工程

无论是做车辆监控、外勤巡检还是物流调度,接到地图需求时最先要解决的三件事几乎固定:把坐标渲染成地图上的标注,把终端上报的定位变成图上可见的当前位置,再把用户选的起点终点连成一条可走的路线。市面上流传的《高德地图标注,路线规划_地图定位.zip》这类工程包,本质就是对高德地图JS API这三块能力的封装。它解决的是"会调用接口但不会串成完整业务"的问题——标注怎么和点击弹窗联动、定位坐标为什么偏、路线规划返回了为什么画不出来,这些都是包作者替你踩过的坑,也是这篇要逐层拆给你看的点。适合刚接手LBS页面的前端工程师,或者要用地图做毕业设计/课程项目的学生,按文中步骤半天内能跑出一套可改的工程。

2. 标注、定位、路线规划的数据模型:先把坐标系和对象关系理清

2.1 坐标系决定一切:GPS原始坐标和高德坐标为什么对不上

先说一个最容易让新手翻车的物理事实:手机GPS返回的是WGS84坐标,高德地图用的GCJ-02坐标(俗称火星坐标),两者在绝大多数城市有几十米甚至上百米的偏差。你如果拿着设备上报的GPS原始坐标直接new AMap.Marker,定位点和真实位置会稳定地漂开,不是接口坏了,是坐标系没对齐。常见做法是在数据进入地图层之前统一做一次坐标转换,高德提供了现成方法:

AMap.convertFrom([lng, lat], 'gps', function (status, result) { if (status === 'complete' && result.info === 'ok') { const gcj = result.locations[0]; // 转换完成,gcj.lng / gcj.lat 才是能直接打点的坐标 console.log(gcj.lng, gcj.lat); } });

这里第一个参数可以传单个经纬度数组,也可以传一个二维数组批量转换;第二个参数固定传'gps',表示源坐标系是WGS84。转换是异步的,所以不要在回调外面直接用gcj,否则拿到的是undefined,这是最常见的使用错误。实际项目里我一般会把转换逻辑封装成Promise,在获取定位、接收后端点位上报时统一走这一层,避免后续每一个Marker都重复处理。

如果你的点位数据是后端直接以GCJ-02下发,那这一步可以完全跳过。但调用AMap.convertFrom时要注意额度:免费版本一天有调用上限,如果你有几十万存量点要一次性迁移,建议在后端完成坐标系换算或者分批前端转换,别让浏览器一次性扛全部数据,否则页面会卡到没脾气。

2.2 标注不是"画个点":Marker、LabelMarker、InfoWindow 的层级关系

地图业务里说的"标注",和CV领域用LabelImg、CVAT画框做数据标注完全是两码事,这里的标注是指在地图上标记业务点位。最基本单元是AMap.Marker,它负责把[lng, lat]变成图上可见的图钉;Marker上面的文字或小标签用label属性;点击弹窗用AMap.InfoWindow。一个完整的标注由这三层叠加出来。推荐的数据结构长这样:

const points = [ { id: 'A001', lng: 116.397428, lat: 39.90923, name: '东直门', status: 'online' }, { id: 'A002', lng: 116.327469, lat: 39.989731, name: '奥体中心', status: 'offline' }, { id: 'A003', lng: 116.481488, lat: 39.990556, name: '四惠东', status: 'online' } ];

渲染时把数组map成Marker实例再一次性add到地图上,比循环里逐个marker.setMap(map)要好维护,批量add后可以用map.remove(markers)整体销毁。

const markers = points.map(p => new AMap.Marker({ position: [p.lng, p.lat], title: p.name, zIndex: 10, label: { content: p.name, direction: 'top', offset: new AMap.Pixel(0, -8) } })); map.add(markers);

title是鼠标悬停提示,label的direction控制文字相对图钉的方向,offset微调文字位置。点少这么写没问题;但点位超过500个时,逐个Marker的DOM开销会让缩放拖动变卡,这时候改用LabelMarker或者做聚合。聚合用高德的AMap.MarkerClusterer:

AMap.plugin('AMap.MarkerClusterer', function () { const clusterer = new AMap.MarkerClusterer(map, markers, { gridSize: 60, // 聚合网格像素大小,越小越容易散开 maxZoom: 16 // 超过该缩放级别不再聚合 }); });

gridSize不是越大越好,默认80;如果点位密集且用户经常要精确点选,我会调到50~60,代价是聚合数变多、视觉上稍乱。maxZoom到16以后聚合会全部展开,适合城市级大范围点位在低层级聚成一坨,放大到街道层级再逐个展示。

2.3 路线规划返回的不只是线:Driving 与 Walking 的响应结构

路线规划组件AMap.Driving、AMap.Walking和AMap.Transfer在用法上一致,区别只在支持的交通方式和返回字段。调用search(start, end, callback)之后,真正的路线数据在result.routes里,这是一个数组,因为同一次规划可能返回多套方案。每条route包含distance、time、steps,其中steps是分段的驾驶/步行指引,每一步里有一个path字段,是一串经纬度数组,它就是你在图上看到的那条折线的几何数据。

字段类型含义
result.routesArray路线方案列表,通常至少一条
route.distanceNumber总距离,单位米
route.timeNumber总耗时,单位秒
route.stepsArray分段导航信息,每步含instruction道路名
step.pathArray该段折线的经纬度坐标数组,可直接绘制Polyline
result.origin / result.destinationLngLat规划的起终点,可用来画起点终点Marker

很多新手误以为把map参数传给Driving组件就会自动画线,于是找不到自定义绘制的机会。实际上Driving组件在传入map时会帮你把路线和起终点Marker都画出来,但不传map则只做纯计算,方便你拿到steps[i].path后用AMap.Polyline自定义样式。做“只看距离不开导航”的功能时,我强烈建议用纯计算模式,省去组件自动加的图钉干扰。方向是确定性的:定位给坐标,标注消费坐标,路线规划在坐标之上再画一层数据,这三个模块是层层依赖的。

3. 在本地跑通最小地图工程:初始化、打点、定位三步走

3.1 申请 Key 和安全密钥:JS API 2.0 的白屏第一道关

把zip包里的代码跑起来之前,先去高德开放平台控制台创建一个"Web端(JS API)"类型的Key。这里最容易被忽略的是JS API 2.0需要额外的安全密钥securityJsCode,而且它必须在引入地图脚本之前注入到页面上,顺序错了地图要么白屏要么报INVALID_USER_SCODE。最小可用页面长这样:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>高德地图三件套最小工程</title> <style>#map { width: 100%; height: 500px; }</style> </head> <body> <div id="map"></div> <script> window._AMapSecurityConfig = { securityJsCode: '你的安全密钥' }; </script> <script src="https://webapi.amap.com/maps?v=2.0&key=你的KEY"></script> <script> const map = new AMap.Map('map', { zoom: 12, center: [116.397428, 39.90923], resizeEnable: true }); </script> </body> </html>

resizeEnable: true建议默认开着,否则页面容器尺寸变化时地图不会自动重绘,典型现象是tab切换后地图只剩一半。顺带一提,有些zip包里的请求URL会带一长串统计参数,比如渠道号c04030322001,那是官网推广链接的归因字段,和功能鉴权没有关系,删掉或保留都不影响运行,别在它身上浪费时间排查。

3.2 批量打点:从数组渲染到聚合的完整过渡

业务里地图标注很少只画一个点,通常是接口返回一批设备或站点,前端渲染。上面的points数组示例继续往下走,如果要让标注点能点击、能弹出详情,需要挂事件并配合InfoWindow。这里最容易踩的坑是:Marker的click事件里用m.getPosition()拿到的不是数组,而是LngLat对象,直接塞进InfoWindow.open(map, pos)会报错或弹窗位置不对,要先用toArray()转成[lng, lat]格式。

let infoWindow = null; function openInfo(position, title) { if (infoWindow) infoWindow.close(); infoWindow = new AMap.InfoWindow({ content: `<div><b>${title}</b><p>坐标:${position[0].toFixed(6)}, ${position[1].toFixed(6)}</p></div>`, offset: new AMap.Pixel(0, -30) }); infoWindow.open(map, position); } markers.forEach((m, i) => { m.on('click', () => { const pos = m.getPosition().toArray(); openInfo(pos, points[i].name); }); });

offset: new AMap.Pixel(0, -30)是把弹窗向上偏移30像素,让弹窗尖角正好指向图钉头部,视觉上更贴。业务字段如设备状态、最后上报时间、详情页跳转链接也塞进content字符串里,但注意内容里的特殊字符要转义,否则弹窗HTML会被打断。标注点如果上千,先聚合再挂事件,不然散点事件监听数量过大,页面首次交互会有明显卡顿。

定位和标注经常是同一屏出现:定位结果本身也是一个Marker,只是它的坐标来自Geolocation插件而不是业务数据。所以我们先掌握标注渲染,接下来解决定位来源。

3.3 浏览器定位:Geolocation 插件的参数和回调设计

高德JS API 2.0的定位能力放在插件AMap.Geolocation里,需要先用AMap.plugin显式加载再实例化。定位结果是异步回调,回调签名为(status, result),两个参数必须同时判断,不能只看status。

AMap.plugin('AMap.Geolocation', function () { const geolocation = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 10000, maximumAge: 0, zoomToAccuracy: true, showButton: true }); map.addControl(geolocation); geolocation.getCurrentPosition(function (status, result) { if (status === 'complete') { const pos = [result.position.getLng(), result.position.getLat()]; map.setCenter(pos); new AMap.Marker({ map: map, position: pos, title: '当前位置' }); console.log('精度:', result.accuracy, '地址:', result.formattedAddress); } else { console.error('定位失败:', result.message); } }); });

enableHighAccuracy: true请求GPS级别精度,但首次锁定位置会相对慢;timeout设10000毫秒比较平衡,移动端弱网环境太短会频繁超时。maximumAge: 0表示不接受缓存的旧定位,适合巡检打卡这种对新鲜度敏感的场景。result.accuracy单位是米,它决定了定位圈的大小,调试时可以打印出来看,精度超过50米基本可以判断是室内或信号遮挡。还要注意一个浏览器层面的限制:定位接口要求在HTTPS或localhost环境下才会放行,你拿IP地址访问HTTP页面时大概率拿不到位置,这不是高德的问题,是浏览器策略。

4. 路线规划交互:起终点选择、路线绘制与策略参数调优

4.1 从定位到规划闭环:把"我的位置"设为起点

有了定位能力,路线规划最常见的第一步就是"从当前位置去某地"。把4.1小节其实拆成两半:先定位拿到当前坐标,缓存成起点;再等用户输入终点触发规划。

let startPoint = null; const destPoint = [116.481488, 39.990556]; // 四惠东 function locateAndGo() { AMap.plugin('AMap.Geolocation', function () { const geo = new AMap.Geolocation({ enableHighAccuracy: true, timeout: 8000 }); geo.getCurrentPosition(function (status, result) { if (status !== 'complete') { alert('定位失败,无法设置起点'); return; } startPoint = [result.position.getLng(), result.position.getLat()]; drawRoute(startPoint, destPoint); }); }); } function drawRoute(start, end) { AMap.plugin('AMap.Driving', function () { if (!window.driving) { window.driving = new AMap.Driving({ map: map, policy: AMap.DrivingPolicy.LEAST_TIME, showTraffic: true }); } window.driving.clear(); window.driving.search(start, end); }); }

driving.clear()是必须的,否则第二次规划时旧路线和旧Marker会保留在地图上,出现好几条线叠在一起的情况,这是最影响观感的重复渲染问题。policy的取值有几个:LEAST_TIME最快、LEAST_DISTANCE最短、REAL_TRAFFIC依据实时路况,做物流调度默认LEAST_TIME,做步行导览则用AMap.Walking组件。showTraffic开启后道路会叠加红黄绿路况色带,但也会增加瓦片渲染负担,内网项目建议关掉。

4.2 纯计算模式:不画线拿到几何数据,自由定制路线样式

很多业务页面不需要高德默认的蓝色粗线,而是要把路线画成自己品牌的颜色、宽度甚至虚线。这时就不该把map传给Driving组件,而是用纯计算模式,拿到steps里的path自己拼Polyline。

function calcRoute(start, end) { AMap.plugin('AMap.Driving', function () { const driving = new AMap.Driving({ policy: AMap.DrivingPolicy.LEAST_TIME }); driving.search(start, end, function (status, result) { if (status !== 'complete' || !result.routes || !result.routes.length) { console.warn('无路线结果'); return; } const route = result.routes[0]; const linePath = []; route.steps.forEach(step => { step.path.forEach(p => linePath.push([p.getLng(), p.getLat()])); }); const polyline = new AMap.Polyline({ path: linePath, strokeColor: '#0066FF', strokeWeight: 6, strokeOpacity: 0.8, lineJoin: 'round' }); map.add(polyline); }); }); }

注意step.path里的元素是LngLat对象,Polyline的path虽然兼容LngLat数组,但如果你要自己处理路线上某一点坐标,最好统一转成[lng, lat]数组。strokeWeight是线宽像素,6在普通屏幕上已经比较醒目;lineJoin: 'round'让折线拐角变圆润,视觉上更接近导航App的效果。这种纯计算模式同样适合步行路线,把Driving换成Walking组件即可,返回结构一致。

4.3 途经点与避让区域:多目标路线拆解

一点一线是最简单场景,真实外勤常常要"从A出发,依次经过B、C,最后到D",Driving组件用waypoints参数支持,但有两个边界要知道:最多支持16个途经点(含起终点);途经点顺序默认按地理路径优化,不保证你传入的顺序,除非设置waypointMode: 'fixed'。

const driving = new AMap.Driving({ map: map, policy: AMap.DrivingPolicy.LEAST_TIME, waypoints: [ [116.42, 39.92], [116.44, 39.91] ], waypointMode: 'fixed' });

waypointMode: 'fixed'表示严格按途经点顺序经过,适用于配送多点打卡;不设置时高德会尝试让总路程更短,可能把点顺序打乱。避让区域(avoidpolygons)接受多边形坐标数组,用来绕开施工区或管制区域,但该参数在某些策略组合下会被忽略,官方文档没有明确给出优先级,我的排查经验是:REAL_TRAFFIC策略下避让区域偶尔失效,改用LEAST_TIME就正常了,这属于接口本身的玄学,遇到时换个策略试试。

轨迹相关的进阶需求这里先提一个验证思路:你可以把一段历史的GPS坐标序列依次喂给AMap.Marker的位置更新,用marker.setPosition()按时间间隔移动,就能模拟轨迹回放,而不需要依赖路线规划组件。这个我们也留到第6章展开。

5. 高德地图三件套落地避坑:五个真实翻车场景排查

5.1 地图白屏:安全密钥顺序错,还是Key类型搞错

现象:页面其他元素正常,唯独地图区域一片灰或网格线,控制台报INVALID_USER_SCODE或AMap is not defined。原因基本是两个:window._AMapSecurityConfig定义在了引入地图脚本之后,导致密钥没生效;或者在控制台创建Key时选成了"Web服务",而不是"Web端(JS API)"。解决:把安全密钥配置提到<script src="..."></script>之前;去控制台确认Key类型,生成新Key后同步更新两处。还需要强调,很多zip包里的Key是包作者自己的,有每日配额,跑demo可以,上生产必须换成你自己的,否则某天全公司页面同时转圈就晚了。

5.2 定位坐标漂移:GPS原始坐标直接打点,位置差一条街

现象:手机打开页面,定位点落在地图上偏了几百米,但手机自带地图是准的。原因:高德使用GCJ-02坐标系,而浏览器Geolocation或硬件返回的是WGS84,没有做坐标系转换。解决:在拿到result.position后先用AMap.convertFrom(coord, 'gps', cb)转换再渲染;或者后端在存数据时就统一转成GCJ-02。这里有个实战细节:convertFrom一次建议不超过20个点,批量点很多时拆分循环处理,避免单次请求超时。

5.3 瓦片加载慢或灰块:HTTPS页面混入HTTP资源

现象:地图能初始化,但拖动时大片区域显示灰块,刷新后又恢复;浏览器控制台频繁出现Mixed Content相关报错。原因:页面是HTTPS,但地图脚本或瓦片地址被工程包写成了HTTP,现代浏览器直接拦截了不安全请求。解决:统一使用https://webapi.amap.com/maps?v=2.0&key=...引入脚本,且不手动干预瓦片域名;如果是公司内网出口有限制,需要运维把地图相关域名的HTTPS访问加入白名单。这类问题排查时不要急着改代码,先用浏览器的Network面板过滤是否大量红色请求,定位到域名后再处理。

5.4 路线规划返回空:起终点距离、跨城和坐标格式三座山

现象:search()回调status是complete,但result.routes是空数组;或者只有起终点Marker,没有路线折线。原因大概率是三个之一:起终点传的是WGS84坐标导致定位到海里;起点终点距离超过当前策略支持范围;跨城市规划要求传入city参数否则默认按北京检索。解决:确认起终点都经过GCJ-02转换;查询前检查两点直线距离,超过100公里的驾车规划建议用Driving的extensions: 'all'参数;跨城时给Driving组件初始化参数里手动指定起终点城市编码。排查时打印完整result对象,比盲改参数有效得多。

5.5 别把CV数据标注工具带进来:LabelImg / CVAT / Label Studio 的误用

现象:搜索"地图标注"时出来一堆目标检测标注工具教程,于是有人在工程里尝试引入LabelImg或CVAT的标注结果,费力不讨好。原因:地图工程的"标注"是业务语义,把点位、轨迹、区域画在地图上渲染;深度学习里的"数据标注"是给训练集画边界框,两者除了都叫标注没有任何关系。解决:做地图可视化业务,用高德Marker/InfoWindow/聚合;要训练物体检测模型时,才用LabelImg、CVAT、Label Studio这类的数据标注工具。如果你拿着遥感影像做地块提取,那属于图像语义分割,标注链路和这里的地图三件套是两个技术栈,不要混用方案。

6. 再走深一步:标注点持久化与轨迹回放的最小实现

6.1 用 localStorage 保存标注点,刷新不丢

简单演示项目不想搭后端时,标注点可以序列化后存到localStorage,刷新后读回来再渲染。

function savePoints() { localStorage.setItem('map_points', JSON.stringify(points)); } function loadPoints() { const raw = localStorage.getItem('map_points'); return raw ? JSON.parse(raw) : []; } map.on('click', function (e) { const lnglat = e.lnglat; const p = { id: Date.now(), lng: lnglat.getLng(), lat: lnglat.getLat(), name: '新点' }; points.push(p); savePoints(); new AMap.Marker({ map: map, position: [p.lng, p.lat], title: p.name }); });

map.on('click')在空白处点击能拿到经纬度,这是给不熟悉地图交互的人埋的一个彩蛋:地图不是只能展示点,还能反手生成点。这个方案适合原型验证和课程设计,生产环境还是要把savePoints换成POST到业务后端。

6.2 轨迹回放:定时器驱动 Marker 沿路径移动

把GPS历史坐标按时间顺序回放,是车辆监控最常被问的功能。实现思路简单:一个Marker,一段坐标数组,每秒更新一次setPosition。

const track = [[116.39, 39.91], [116.40, 39.92], [116.42, 39.93]]; let index = 0; const trailMarker = new AMap.Marker({ map: map, position: track[0] }); const timer = setInterval(() => { index += 1; if (index >= track.length) { clearInterval(timer); return; } trailMarker.setPosition(track[index]); map.setCenter(track[index]); }, 1000);

setPosition会直接移动Marker,不需要删除重建,所以GPS点再多也不会累积DOM。回放时配合map.setCenter让视角跟随,体验基本接近导航App的历史轨迹效果。内存和性能上,超过一万个轨迹点建议抽样或分页,不然定时器每帧都要触发重绘,低端设备会有肉眼可见掉帧。生成轨迹数据后,用polyline把原始坐标串起来,就能验证标点、定位、路线三者的坐标基准是否一致。

我做这类地图工程时习惯把坐标系转换放在数据入口统一处理,不在业务组件里到处写convertFrom,产品跑了一段事件后发现线上点位偶尔偏移,定位到最后都是某个新同事绕过封装直接用了原始GPS坐标。地图三件套本身不难,难的是一套规范贯穿所有数据出入口。如果你打算把这套方案接到真实项目里,建议从最小页面起步,先验证Key、定位、画线三件事都通了,再加聚合和轨迹回放;每一步都能在控制台看到明确的打印结果,再往上层堆业务逻辑。希望这篇能帮你少走我当年绕过的弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询