简介面向地理信息系统与移动应用开发者的高德地图API实战教程聚焦利用Web端JavaScript库完成驾车、步行等多模式线路的规划与可视化绘制适合具备一定前端基础、希望快速上手地图功能的初中级前端开发者。从高德开放平台注册与API密钥获取到引入JS库、初始化地图实例再到调用AMap.Driving与AMap.Walking接口完成路线规划整个实现链路从发起请求到渲染均有清晰示例可循。在此基础上结合Polyline绘制路径线段、Marker标注起点与终点覆盖最短时间策略、多途经点设置、动态调整路线、点击获取坐标与显示实时路况等扩展场景可加深对高德地图API数据结构与渲染逻辑的理解。资源打包为zip格式整体约12.01MB文件结构清晰便于下载后本地对照练习。目前已有856人学习/下载适合作为地图导航类功能开发的技术参考。1. 高德地图绘制本质是在 GCJ-02 坐标系里“动笔”做地图可视化的工程师十有八九会撞上“高德地图绘制”这个需求。它听起来就是“在地图上画个点、连条线”真正落到业务里却是标点、画围栏、框选区域、圈定配送范围这一整套交互逻辑。高德地图绘制不等于写死一个坐标而是通过高德 JS API 或 Android SDK 让用户在底图上拖动、描点、生成可编辑的覆盖物最后把结果存进业务系统。这篇文章适合正在做轨迹可视化、店址标注、电子围栏、地块圈选的开发者也想帮新手避开那些只有真正跑一遍才会发现的坑。下面不聊概念从坐标选型、覆盖物 API、交互绘制、序列化到避坑和进阶验证把这条链路完整拆开。2. 绘制前必须想清楚的事坐标系与覆盖物模型2.1 高德地图绘制绕不开的坐标系GCJ-02 与坐标转换高德地图所有产品用的底图坐标系是 GCJ-02俗称火星坐标。而手机 GPS 芯片直接输出的原始坐标是 WGS84。把 WGS84 的经纬度当成高德的经纬度直接绘制点位会偏出几百米。这不是地图没加载完也不是 Marker 位置算错了是坐标系根本不对。实际做项目时基本有三种处理方式。第一种是 Android 端直接使用高德定位 SDK定位结果默认就是 GCJ-02拿回来就能画。第二种是 Web 端用官方转换接口AMap.convertFrom做转换一次支持批量转换但要注意它有数量上限单次以 40 个坐标为界。第三种是前后端约定好入库统一存 WGS84后端做地图服务时统一转成 GCJ-02 再吐给前端这样数据不绑定任何一家地图厂商。// 官方 convertFrom 用法把 WGS84 坐标批量转成 GCJ-02 AMap.convertFrom( [116.45, 39.95], // 传入 [lng, lat] 或二维数组 gps, // 源坐标系gps 表示 WGS84 (status, result) { if (status complete result.info OK) { const marker new AMap.Marker({ position: result.locations[0] }) map.add(marker) } else { console.error(坐标转换失败, status, result) } } )convertFrom是异步回调不是同步返回所以转换后要立刻做 UI 更新的话记得把绘制逻辑写在回调里。还有一个容易忽略的点这个接口只能转国内区域的坐标海外坐标会fail。我一般在封装里先判断经纬度范围超出直接抛错走 WGS84 原值绘制。不要在需要稳定的生产环境里手写火星坐标转换公式。网上流传的 JavaScript 转换代码虽然可以跑但不同版本精度差几米到几十米而且依赖固定的偏移参数万一哪天高德调整加密参数手写版本就全部失真。我见过一个团队在围栏模块里手写了转换算法上线后部分地点偏移了 30 多米查了两天才定位到是转换常数抄错。坐标转换这种事能用官方接口就别自己造轮子。2.2 覆盖物模型Marker、Polyline、Polygon 与绘制工具的选型高德地图绘制的基础是覆盖物。JS API 里覆盖物分几类点用AMap.Marker线用AMap.Polyline面用AMap.Polygon再加上AMap.Circle和AMap.Rectangle。它们都通过setMap(map)或map.add(overlay)挂到地图上地图内部会把覆盖物按类型和zIndex排好层级。Mark 一下Polygon 在结构上就是一个闭合的 Polyline但 Polygon 自带填充样式、挖洞能力和命中检测所以圈范围、画围栏时要用 Polygon 而不是 Polyline 首尾相连。Polyline 就算把首尾点重合也不会做填充事件拾取也弱于 Polygon。绘制矩形这件事常被拿来和 Cesium 做对比。在 Cesium 里画矩形用entity.rectangle它和高德一样矩形边必须平行于经纬线方向没法做旋转矩形。Cesium 要实现旋转矩形通常用 Polygon 去拼四个角点高德里也是一样的套路。如果你的业务需要画带角度的矩形比如倾斜的停车场、旋转后的地块不要指望 Rectangle 覆盖物老老实实按中心点加旋转角算出四个顶点再交给 Polygon 绘制。我一般这样安排绘制功能标记点、普通标注用 Marker轨迹路径、路线规划预览用 Polyline围栏、地块、配送范围用 Polygon小区半径圈选用 Circle直角框选用 Rectangle。这样选不是因为 API 限制而是后续做样式统一和编辑时每种覆盖物都有对应的 Editor混着用会给自己找麻烦。2.3 为什么用官方覆盖物而不是自绘 Canvas拾取、编辑、吸附都要自己造很多人觉得画个圆、画个矩形而已直接用 Canvas 在图层上画不是更轻确实轻但高德地图绘制一旦进入生产你要面对的是编辑、拖拽、属性回调、删除重绘这些官方覆盖物都内置了。自绘 Canvas 覆盖物主要亏在三点拾取要自己算命中鼠标悬停和点击要自己维护状态编辑节点要自己写拖拽逻辑。而官方 PolygonEditor 只要一行open()就能让用户拖拽多边形顶点。地图底图缩放时官方覆盖物跟着地图重新投影自绘 Canvas 如果没监听 zoomchange图形就会和底图错位。除非你要画的是几千上万个动态热力点否则我不建议绕开官方覆盖物去做高德地图绘制。3. 用高德 JS API 绘制点线面一套可直接拷贝的最小实现3.1 初始化地图JS API 2.0 的 script 引入与安全密钥高德地图绘制第一步是把地图跑起来。现在申请的高德 JS API key 必须配合安全密钥使用如果不设置控制台会报INVALID_USER_SCODE地图一片空白。安全密钥要在引入 JS 文件之前声明写成全局变量。!DOCTYPE html html head meta charsetutf-8 / style #container { width: 100%; height: 600px; } /style /head body div idcontainer/div script // 安全密钥必须在引入 JS API 之前定义 window._AMapSecurityConfig { securityJsCode: 你的安全密钥 } /script script srchttps://webapi.amap.com/maps?v2.0key你的KeypluginAMap.MouseTool,AMap.PolygonEditor/script script const map new AMap.Map(container, { zoom: 13, center: [116.397428, 39.90923], // [经度, 纬度] viewMode: 2D }) /script /body /html注意center参数是[lng, lat]不是[lat, lng]写反了地图会定位到偏移很大的位置而且不报错。plugin参数里声明了AMap.MouseTool和AMap.PolygonEditor不先声明直接 new 会报AMap.MouseTool is not a constructor。如果你只用点线面展示不涉及交互式绘制plugin 可以不写。但既然做高德地图绘制后面大概率要画围栏建议一开始就带上。3.2 绘制点标记Marker 的 position、anchor 与自定义 content点标记是最简单的覆盖物。创建 Marker 之后setMap(map)就能显示。但实际项目里我们很少用默认的红色大头针更多是用自定义 DOM 加业务字段。const marker new AMap.Marker({ position: [116.397428, 39.90923], anchor: bottom-center, // 锚点图标底部中心对准经纬度点 content: div classshop-marker stylebackground:#fff;border:2px solid #1791fc;border-radius:4px;padding:2px 8px;24h便利店/div }) marker.setMap(map)anchor决定图标哪个位置对准坐标点。默认是中心点但自定义弹窗式标记我希望底部尖角对准坐标就设成bottom-center。如果设了contentDOM 的尺寸和定位完全由自己控制锚点偏移量也要自己微调多试几次就能对齐。另一个容易忽略的是事件穿透自定义 content 里有按钮、链接时点击这些元素通常会触发 DOM 事件也可能冒泡到底图触发地图 click。解决方式是给不想要的元素加pointer-events: none或者监听事件后用event.stopPropagation()阻断冒泡。这个坑在第五章再具体展开。3.3 绘制折线与多边形path、stroke 与 fill 的完整参数折线和多边形的代码结构很像核心是path数组和描边样式。const path [ [116.40, 39.90], [116.42, 39.92], [116.44, 39.91] ] // 折线 const polyline new AMap.Polyline({ path, strokeColor: #FF7F24, strokeWeight: 4, strokeOpacity: 0.8, strokeStyle: solid // 或 dashed }) map.add(polyline) // 多边形路径首尾相连 const polygon new AMap.Polygon({ path: [...path, [116.40, 39.90]], fillColor: #1791fc, fillOpacity: 0.3, strokeColor: #1791fc, strokeWeight: 2, strokeOpacity: 0.8 }) map.add(polygon)Polygon 的path自动闭合最后一个点和第一个点重复与否不影响结果。样式参数里fillOpacity建议不要超过 0.4否则会把底图压得太死用户看不到下面的道路和楼栋。参数速查参数折线多边形说明path是是经纬度数组多边形支持多子路径strokeColor是是描边颜色strokeWeight是是描边宽度单位像素strokeOpacity是是0-1描边透明度strokeStyle是是solid / dashedfillColor否是填充颜色fillOpacity否是0-1填充透明度多边形还支持挖洞。path传入二维数组第一层是外边界后面的子数组是洞。比如画一个环形区域外圈是业务范围内圈是排除区域直接在一个 Polygon 上配置就行不需要两个覆盖物叠加。const donutPolygon new AMap.Polygon({ path: [ [[116.40, 39.90], [116.44, 39.90], [116.44, 39.94], [116.40, 39.94]], // 外边界 [[116.41, 39.91], [116.43, 39.91], [116.43, 39.93], [116.41, 39.93]] // 内洞 ], fillColor: #1791fc, fillOpacity: 0.4 }) map.add(donutPolygon)这个数据结构容易写错。外层数组的每个元素本身是一个坐标数组两个子路径不会自动识别谁是外谁是内高德内部按面积判断面积大的当成外边界。如果你把内外反过来画渲染出来就是一个抠反了的图形不报错但视觉错。遇到这种情况检查子路径的方向和面积。3.4 圆形与矩形半径单位是米bounds 方向别反圆形和矩形是高德地图绘制里被误解最多的两个覆盖物。// 圆形radius 单位是米 const circle new AMap.Circle({ center: [116.40, 39.90], radius: 1000, // 1 公里范围 fillColor: #1791fc, fillOpacity: 0.3, strokeColor: #1791fc, strokeWeight: 2 }) map.add(circle) // 矩形bounds 用西南角 东北角 const rectangle new AMap.Rectangle({ bounds: new AMap.Bounds([116.40, 39.90], [116.42, 39.92]), fillColor: #FF7F24, fillOpacity: 0.3, strokeColor: #FF7F24, strokeWeight: 2 }) map.add(rectangle)Circle的radius单位是米不是经纬度。你要画 5 公里范围就传5000。Rectangle的bounds要求第一个点是西南角第二个点是东北角如果传反了绘制出的矩形会越过中心点跑到对角方向。而且高德不校验这一点图形照样画出来只是位置完全不对。这是我在 code review 时一眼就能看出的经典问题。另外这两个覆盖物都不支持旋转。想让圆形变成椭圆或让矩形倾斜某个角度官方覆盖物做不到只能改用 Polygon 手算顶点。前面说过Cesium 里画矩形也受经纬线方向限制所以这不是高德一家的问题是经纬度这种坐标系统的天然约束。4. 交互式绘制与二次编辑AMap.MouseTool 的生产级用法4.1 用 MouseTool 让用户手动画多边形样式定制与回调处理地图绘制功能做出来不是让程序员自己在控制台画而是要交付给运营、销售让他们在地图上鼠标拖拽画围栏。这里必须上AMap.MouseTool。先声明 plugin然后实例化 MouseTool调用drawPolygon监听drawComplete拿到绘制结果。const mouseTool new AMap.MouseTool(map) // 打开多边形绘制 mouseTool.drawPolygon({ strokeColor: #1791fc, strokeWeight: 2, fillColor: #1791fc, fillOpacity: 0.25 }) mouseTool.on(drawComplete, (event) { const polygon event.obj // 绘制结束的 Polygon 实例 resultLayers.push(polygon) // 挂进自己的覆盖物列表 mouseTool.close() // 立即关闭画笔防止连续误画 })event.obj返回的是一个完整的AMap.Polygon实例不是坐标数组。也就是说你可以在回调里直接调polygon.getPath()、polygon.setMap()、甚至直接new AMap.PolygonEditor(map, polygon)。这个设计让绘制和编辑无缝衔接。实际使用中有几个细节要注意。第一个是切换绘制模式前必须先close()旧画笔否则再调用drawCircle时地图上会出现两支画笔同时工作的现象。第二个是 drawComplete 后如果你不把 polygon 保存起来下一次绘制开始前记得map.remove(previousPolygon)不然画一个留一个图上越来越乱。第三个是 MouseTool 的样式参数和直接 new Polygon 的样式参数完全一致建议把样式抽成全局常量保证手绘围栏和接口返回的围栏长得一样。4.2 二次编辑PolygonEditor 的打开、事件与提交绘制完的围栏几乎必然需要修改边界。业务人员画完一圈发现北边少框了一栋楼这时候不能让用户删掉重画要提供顶点拖拽编辑。const editor new AMap.PolygonEditor(map, polygon) editor.open() // 开启编辑多边形顶点变成可拖拽的小圆点 editor.on(end, () { const newPath polygon.getPath() const geojson polygonToGeoJSON(polygon) console.log(编辑后的边界, geojson) }) // 需要主动结束时调用 editor.close()Editor 加载方式和 MouseTool 一样需要写在 script 的 plugin 参数里。end事件在每次拖拽完成时触发包含整个多边形的新路径。监听addnode和removenode可以控制加顶点和删顶点的行为比如限制围栏最少 3 个点、最多 200 个点。我踩过一个大坑editor.open() 开启后如果业务代码在这个间隙里去修改 polygon 的 fillColor 或 setPath编辑器会进入半死状态顶点拖不动Close 后多边形路径还停留在旧值。所以编辑期间除了 Editor 自己不要通过任何其他 API 去操作这个 polygon等 end 事件或手动 close() 之后再更新样式和提交数据。4.3 把绘制结果序列化GeoJSON 输出与后端存储绘制完成只会存在于内存里刷个页面就丢了。必须序列化后提交到后端。高德的覆盖物类型各不相同我习惯统一转成 GeoJSON后端一套存储逻辑就能接住点线面。function polygonToGeoJSON(polygon) { const coordinates polygon.getPath().map(p [p.getLng(), p.getLat()]) return { type: Feature, properties: {}, geometry: { type: Polygon, coordinates: [coordinates] // GeoJSON 的 coordinates 需要一层数组包裹 } } }GeoJSON 的coordinates结构是多层嵌套Polygon类型要求外层是一个数组里面才是坐标点数组不包一层后端解析会直接报错。序列化时经纬度默认保留 6 位小数大概十米量级。如果你的围栏边界精度要求到米以下可以把 LngLat 的 lng()、lat() 返回值用 toFixed(7) 处理约到米级。存储方面我的习惯是后端数据库里直接存 GeoJSON 字段坐标保留 GCJ-02 原样。因为高德地图绘制出来的数据再回到高德地图上展示没必要二次转换。只有当业务要和 WGS84 坐标系的其他系统互通时才在后端入口做一次转换前端不掺和。这个约定必须写进接口文档否则后端用 WGS84 算面积前端用 GCJ-02 画围栏两头差着几百米线上事故就是这么出的。5. 高德地图绘制避坑指南五个必踩的坑与排查路径5.1 绘制完保存再次加载整个围栏错位几百米现象现场用 GPS 打点画了一个配电房围栏保存后第二天打开围栏整体跑到马路对面。原因数据录入时直接用 WGS84 坐标入库渲染时没转换。这是最经典的坐标问题每周都能在技术群里看到一次。解决确认整个链路里只有一个坐标系。Android 端用高德定位 SDK 拿到的就是 GCJ-02直接入库Web 端如果外部设备传的是 WGS84入库前调AMap.convertFrom转换转换后的结果再存。不放心的话写个巡检脚本定时对比最近新增围栏的中心点和现场设备上报点偏移超过 50 米的自动告警。5.2 高德地图瓦片地址放到 QGIS 里和已有图层始终对不齐现象在 QGIS 里用 XYZ Tiles 添加高德瓦片想和 WGS84 的矢量边界叠在一起出图结果路网和边界图错开几百米。原因高德瓦片是按 GCJ-02 切片发布的QGIS 默认工程坐标系是 WGS84两层数据基准不同当然对不齐。解决要么把 QGIS 工程的 CRS 改成 GCJ-02 对应的自定义坐标系要么先把矢量边界转换成 GCJ-02 再叠加。直接在图层属性里输入高德瓦片地址并不能自动纠偏。这个问题常和“高德地图瓦片地址”一起被搜索但本质不是地址问题是坐标系问题。5.3 渠道号配置错误导致地图白屏绘制功能完全不可用现象应用集成高德 SDK 后同一套代码在 A 手机上能绘制在 B 手机上底图加载不出来只有网格背景。原因这类问题在 Android 端最常见SDK 上报的渠道号比如渠道号 c04030322001 这类字符串与你在高德开放平台配置的 Key、包名不一致服务端对客户端鉴权失败瓦片请求返回异常。解决先核对包名、Key、渠道号三者是否匹配。渠道号不是随便填的要和发版渠道对应。把 SDK 日志打开看瓦片请求 URL 的返回码如果集中在 403 或 401基本就是鉴权问题。这种问题最玄学的地方在于部分机型不报错只白屏不要一上来怀疑地图版本先查配置。5.4 覆盖物一多拖拽地图掉帧严重现象页面一次性渲染 800 个 Marker 加 200 个 Polygon初始化要两三秒拖拽时帧率明显下降。原因每个 Marker 是一个 DOM 元素浏览器要同步维护几百个 DOM 节点的位置每个 Polygon 又有一组顶点在地图缩放时参与投影计算CPU 撑不住。解决海量点标记不要用 AMap.Marker改用 AMap.MassMarks它内部是 Canvas 绘制几百上千个点都没问题。海量面如果需要动态更新抽出边界画到自绘 Canvas 图层上而不是直接堆 Polygon。还要定期检查有没有覆盖物只是 remove 了地图引用但没销毁对象已经绘制完成的覆盖物如果确认没用调overlay.setMap(null)把对象释放防止map.add越加越多。5.5 绘制好的 Polygon 点击没有反应或用 Marker 挡住了地图点击现象运营反馈鼠标点围栏想弹详情点了没反应换个场景点击自定义 Marker底图的 click 事件却被一起触发。原因高德 Polygon 的clickable属性默认是false不显式开启click 事件永远不触发。自定义 Marker 的 content 是 DOM事件冒泡到底图容器导致地图 click 也被触发。解决创建 Polygon 时显式加上clickable: trueMarker 的自定义 content 里不需要交互的子元素加pointer-events: none。如果你监听了地图 click 又要监听覆盖物 click事件回调里判断event.target是不是覆盖物实例分别处理。6. 绘制结果更专业瓦片认知、分层组织与性能验证6.1 高德地图瓦片地址解析看懂 URL 模板才能做自定义叠加进阶玩法是把高德瓦片叠加进自己的可视化系统。高德在线瓦片地址有一套固定模板格式大致是https://webrd0{1-4}.is.autonavi.com/appmaptile?langzh_cnsize1style7x{x}y{y}z{z}其中style7是矢量街道图style8是卫星影像。我一般写个小的瓦片图层函数验证对齐情况。const customTileLayer new AMap.TileLayer({ getTileUrl(x, y, z) { // 子域名轮询避免单域名请求过载 const sub ((x y) % 4) 1 return https://webrd0${sub}.is.autonavi.com/appmaptile?langzh_cnsize1style7x${x}y${y}z${z} }, zIndex: 3 }) customTileLayer.setMap(map)这里有个原则自绘瓦片必须用 GCJ-02 切片否则 x、y、z 都对不上底图。我用这个模板主要是排查瓦片错位、调试影像偏移生产环境不会直接拼这个地址去绕过高德的服务。如果你需要稳定的自定义地图样式高德官方提供了自定义地图服务直接在控制台配好发布再用地图实例的 mapStyle 加载比手拼瓦片地址可靠得多。6.2 分层组织与性能验证把绘制数据分到独立的覆盖物集合里绘制功能做复杂之后我习惯把覆盖物分成三层基础展示层放接口返回的围栏、轨迹绘制工作层放 MouseTool 正在画的临时图形结果提交层放已经保存的数据。每层用单独的数组管理。const layer { base: [], editing: [], committed: [] } function commitDrawing(polygon) { layer.committed.push(polygon) layer.editing.forEach(o map.remove(o)) layer.editing [] }这个分层的好处是清理临时绘制时不会误删已保存围栏撤销上一步时只要操作 editing 数组即可。性能验证也有一个可量化的办法按 100、500、1000 个覆盖物分档每档map.add(overlays)后记录耗时。function perfTest(overlays) { const t0 performance.now() map.add(overlays) const t1 performance.now() console.log(add ${overlays.length} overlays: ${(t1 - t0).toFixed(2)} ms) }map.add耗时只是第一层指标更重要的是拖拽地图时 DevTools Performance 面板的帧率和调用栈。如果帧率掉到 20 以下优先检查是不是 Marker 数量过多或 Polygon 顶点数过大。我现在接到任何高德地图绘制需求都会先问三件事坐标系谁给、结果存哪里、覆盖物量级多大。这三句话能挡掉后面 80% 的返工。希望帮到你。本文还有配套的精品资源点击获取