事情是这样的,有一天产品找到我说我们系统的地图绘制板块的时候有些卡,是否可以优化一下。
于是我持怀疑态度去系统查看,大部分的地图操作都很流畅,莫非产品在耍我???
直到我点开了一个导入的板块数据(如下图),厚礼蟹!板块的电子围栏创建 Polygon 编辑器 的同时生成了几千个可操作点,而这些可操作的点,就是影响地图流畅度和渲染速度的罪魁祸首!
然鹅我们的代码全都是按照官方文档去实现的,为何性能如此之差?真相只有一个,就是我们的地图版本过于陈旧了。在阅读了 高德地图 v2.0 的版本介绍 后,安抚好产品小伙伴的情绪 并提出了地图优化升级的方案!
第 4 代 Web 地图渲染引擎
伴随开发者要求的不断提高,以及我们对质量与技术的无尽追求,地图 JSAPI 持续进行着技术革新与升级换代,前后经历了 4 代地图渲染引擎:
- 第 1 代 Web 地图渲染引擎以栅格瓦片拼接为主要的地图绘图手段;
- 第 2 代 Web 地图渲染引擎引入 Canvas 2D 绘图,实现了矢量地图绘制;
- 第 3 代 Web 地图渲染引擎初步引入 WebGL 渲染,实现了部分图层的3D渲染,并使渲染效率得到提升;
- 第 4 代 Web 地图渲染引擎——地图JSAPI 2.0 Beta,广泛采用各种前沿技术,不论是交互体验、视觉体验,还是接口能力都有大幅提升。
交互体验提升
地图 JSAPI 2.0 是我们基于 WebGL 渲染技术打造的高德第四代 WEB 地图渲染引擎,所有图层与地图要素均使用 WebGL 绘制,充分利用 GPU 运算。除此之外,我们从世界模型构建、矢量数据请求、数据加载传输、前端数据处理、地理要素构建、图形绘制显示、实时事件交互、惯性缓动效果等各个节点进行了深度的技术、逻辑与算法优化,使得地图的交互体验更加平顺自然。
视觉体验升级
为了图面信息传递更加高效,我们对 2000+ 种类的地图要素进行了系统化的层次和优先级精细梳理;同时我们对包括图标、文字、道路、路名、区域面、楼块等全部地图元素的视觉样式进行了优化升级,一定会让您一目了然。
功能强化升级
为了让开发体验更便捷,接口功能更加贴近现实需求,新版本突破了旧版本的部分功能设定或性能局限,如:
- 地图缩放等级 (zoom
) 放开至[2, 20]
,大到全球七大洲,小到街道或室内,满足更多业务场景需求;
- 老版本中 Marker
点的添加、信息窗体的打开时的异步处理过程彻底消除,再也不用为 JQuery 等选择器选查找不到对应 Dom 元素而苦恼;
- 折线 Polyline
、多边形 Polygon
、点标记 Marker
等覆盖物的创建效率大幅提升,实测提升 10 倍以上;
- LngLat
/Pixel
/Size
支持二元数组形式;
- 合理化部分接口的设定。
- PolygonEditor
新增吸附能力
JSAPI 2.0对所有常用覆盖物、图层的创建和绘制进行了深入的性能优化,各项性能指标均取得了大幅提升:
500 个 普通Marker 创建耗时 | 1.01s | 60ms | 1580% |
普通 Marker 流畅绘制数量上限 | 500 | 1000 | 100% |
文字标注 流畅绘制数量上限 | 500 | 30000 | 5400% |
图标标注 流畅绘制数量上限 | 3000 | 30000 | 900% |
5000个 Polyline/Polygon 创建耗时 | 19s | 915ms | 1970% |
5000个 Polyline/Polygon 绘制帧数 | 10 FPS | 30FPS | 200% |
30万 点聚合计算耗时 | 3.9s | 950ms | 310% |
确定下升级方案后,我 “仔细” 阅读了高德地图 v2.0 的升级指南。按照官方的说法,我们只需要把 JSAPI 引用中的版本号修改为2.0,大功告成!我成功的解决了地图卡顿的问题,与此同时我也解决掉了系统中所有的地图....😅
事情果然没有那么简单,于是我开始重新阅读 升级指南-地图 JS API v2.0 | 高德地图API
发现高德地图中很多 api 的名称已经发生了变更,所以我“码”不停蹄的开始对 v2.0 变更api进行升级(具体变更内容请查阅官方升级指南,此文不再赘述)。
此外, JSAPI 2.0 提供了跨版本适配器——Adaptor
插件,对于一些希望快速升级 API 版本的应用可以使用Adaptor
插件来避免大部分兼容问题,但是同时也将无法享受到新用法的全新特性,所以还是建议按照正常的步骤进行升级。
地图终于成功渲染,看起来还不错。但事情还没有结束。。。
实际开发中有很多覆盖物的特性已经发生了变化,此时我们也需要根据我们的业务逻辑去做相应的升级。以下为我升级过程中遇到的坑点记录,以便于大家在升级时可以节约时间!
-
获取额外属性 polygon.getExtData() 老版本的 polygon.w.extData 已经无法获取
-
辅助点拖拽:adjust => addnode
polyEditor.on('adjust', (event) => { //v2.0 无法监听中间辅助点拖拽事件 辅助点拖拽需使用 addnode }) polyEditor.on('addnode', (event) => { //todo: })
-
AMap.Autocomplete => AMap.AutoComplete
-
AMap.service 已失效全部改为 AMap.plugin
// 弃用 AMap.service(['AMap.PlaceSearch'],function () { var placeSearch=new AMap.PlaceSearch({ map: map, ...otherProps }); }); // 弃用 AMap.plugin('AMap.Autocomplete',function () { var placeSearch=new AMap.PlaceSearch({ map: map, ...otherProps }); }); //推荐 AMap.plugin(['AMap.PlaceSearch','AMap.AutoComplete'], function() { const myPlaceSearch = new AMap.PlaceSearch({ map: map, ...otherProps }) const myAutoComplete = new window.AMap.AutoComplete({ input: inputEl }) })
至此地图升级已经完成,不管是渲染速度还是操作流畅度都得到了显著的提升!
最后,给大家分享些实用干货
Turf.js - 地理空间分析库,处理各种地图算法
可以处理一些高德地图无法处理的运算能力,比如判断一个多边形是否合规。
校验不合规多边形(多边形的边有交点)可通过 turf.lineString() 对多边形进行拆分线段,将线段两两之间通过 lineIntersect() 判断是否存在交点,若存在,具体代码如下:
import * as turf from '@turf/helpers'
import lineIntersect from '@turf/line-intersect'
/**
* 坐标转线段
* @param {*} path
* @returns {arr}
*/
export function pathToLines(path) {
const lines = []
path.forEach((p, pi) => {
let line
if (pi == path.length - 1) {
line = turf.lineString([path[pi], path[0]])
lines.push(line)
return
}
line = turf.lineString([path[pi], path[pi + 1]])
lines.push(line)
})
return lines
}
/**
* 判断坐标组成的单个多边形是否合法
* @param {*} path
* @description 请传入[[1,2],[2,2],[3,3]] 类似的二维数组
* @returns {boolean}
*/
export function isTruePolygon(path) {
// 判断数组且数组的长度小于3不构成满足一个面的必要条件终止
if (!Array.isArray(path) || path.length < 3) return false
// 具体坐标也需是一个一维数组,并且数组的长度等于2
if (!path.every(item => Array.isArray(item) && item.length == 2)) return false
// 将坐标转成线段
const lines = pathToLines(path)
// 是否合法标志
let isTrue = true
// 验证函数
function check() {
// 倒序循环
for (let i = lines.length - 1; i >= 0; i--) {
// 基准线段
const line = lines[i]
const lineNextIndex = i == 0 ? lines.length - 1 : i - 1
const lineLastIndex = i == lines.length - 1 ? 0 : i + 1
const lineNext = lines[lineNextIndex]
const lineLast = lines[lineLastIndex]
// 相邻二根线段必须要有交点
if (
!isIntersect(line, lineNext) ||
!isIntersect(line, lineLast)
) {
console.log('相邻二根线段必须要有交点', line, lineNext, lineLast, isIntersect(line, lineNext), isIntersect(line, lineLast))
isTrue = false
return
}
// 非相邻的线段必须无交点
const noNearLines = lines.filter((item, i) => i !== lineNextIndex && i !== lineLastIndex)
noNearLines.forEach(le => {
if (isIntersect(line, le)) {
console.log('非相邻的线段必须无交点')
isTrue = false
return
}
})
}
}
check()
isTrue ? console.info('多边形合法') : console.log('多边形不合法')
return isTrue
}
function isIntersect(line1, line2) {
return lineIntersect(line1, line2).features.length > 0
}
export default {
pathToLines,
isTruePolygon
}