前言
此篇文章在此记录整理,本人Web端地图开发项目的过程中,针对项目中业务需求所遇到的各种难点、API问题、BUG、及教程整理😁
18 19年上家小公司是做美国房源数据平台,有B/C端产品,核心为美国房源列表页及详情页,具体地图的交互可参考老美的Redfin、Zillow、Trulia。
早期地图是Google maps,后期因为Google改版,取消了免费额度,改为了会员制并每个月送100刀(没什么卵用),因为赠送的额度用的API调用数量很快就用完咯,随改为Mapbox GL进行重构替换。
正文
1、关于点聚合数据过多时的页面性能问题解决方案
房源列表页布局是左侧房源列表,右侧是地图Google Map,当时一个很严重性能问题是当点聚合数据过多,Leaflet.markercluster绘制点聚合,比如有一万多个点时,页面性能很卡,例如拖放地图搜索房源时地图会卡死一段时间,用户体验很不好,当时Google Map实在找不到合适的解决方案,恰好那时改为会员制了没有免费API额度了,就放弃Google Map,随即采用Mapbox GL重构,并搭配supercluster插件针对性的解决了这个点聚合的性能问题。
【supercluster的算法插件,结合Google map使用的情况解析:】 介绍:根据git源文件的DEMO模拟,利用work主副线程计算聚合点,达到性能最优。 index.js是主线程,work.js是副线程,使用postMessage在线程之间互相传递数据,onmessage互相接收数据。
第一步、forEach遍历所需计算的经纬度坐标点,组成GeoJSON Feature对象,然后使用supercluster的load方法,参数为GeoJSON Point,并计算好0~17个缩放等级,每个等级要展现的聚合点,计算完成后postMessage告诉主线程,GeoJSON Point坐标点已经ready: true
第二步、index.js主线程新建地图以后,根据getBounds().getNorthEast()和getBounds().getSouthWest(),获取地图边界的4个点, 根据getZoom()获取当前地图的缩放级别。 主线程收到副线程传递过来的ready信息,然后调用update方法,此方法是把上述的数据组成,supercluster的getClusters方法所需的bbox、zoom参数,然后postMessage告诉副线程
第三步、副线程收到主线程传递过来的bbox、zoom参数,然后使用supercluster的getClusters方法,根据options来计算聚合点,如果某个marker点都不在所有的Clusters里面的话,则返回该marker点的信息,而不是Cluster的信息。类型为:GeoJSON Feature规范的数组,包含计算后的Cluster和marker结果。并postMessage告诉主线程计算结果。
第四步、主线程收到计算结果后,(使用Google map的RichMarker插件来自定义marker点的样式及内容),遍历计算结果,如果类型为Cluster的话,则配置好Cluster样式及内容并添加到地图上,如果类型是marker的话,则配置好自定义的marker样式及内容并添加到地图上。
第五步、地图拖动事件时,会重新触发主线程的update方法,重复上述流程。但如果是Cluster的点击事件,先判断当前点击的是否为Cluster,是的话则postMessage传递给副线程getClusterExpansionZoom、center参数,副线程收到数据后,使用supercluster的supercluster的getClusters方法,返回的是点击的Cluster里的子项(下一缩放等级的Cluster和marker);
【supercluster的算法插件,结合Mapbox GL使用的情况解析:】
//--------------------------supercluster点聚合--------------------------
const filterLngLat_GeojsonPoints = [];
this.filterLngLat0.forEach((i) => {
filterLngLat_GeojsonPoints.push({
type: 'Feature',
properties: {
listPrice: i.p,
houseId: i.i,
latitude: i.t,
longitude: i.g,
},
geometry: {
type: 'Point',
coordinates: [i.g, i.t]
}
});
});
this.superclusterIndex = supercluster({
log: true,//是否记录计时信息
radius: 60,//聚类半径(以像素为单位)
extent: 256,//平铺程度(半径相对计算)
minZoom: 5,//允许计算的最小缩放级别
maxZoom: 22,//允许计算的最大缩放级别
nodeSize: 64, //KD树叶节点的大小,影响性能
}).load(filterLngLat_GeojsonPoints);
console.log('所有的points', this.superclusterIndex);
2、关于地图拖拽缩放时
右侧地图拖拽及缩放时,会根据中心坐标,及四个边的坐标,传给接口获取地图视图内的所有房源,服务器查询出结果返回后,前端页面地图要渲染并加载出搜索结果的房源坐标,并且有infoWindow浮窗交互展示房源基本信息。
//在地图完成从一个视图到另一个视图的转换之后触发
let isMoveendNum = 0;
this.myMap.on('moveend', (e) => {
isMoveendNum++;
this.mapLoading = false;
this.maxX = this.myMap.getBounds()._ne.lat;//北
this.maxY = this.myMap.getBounds()._ne.lng;//东
this.minX = this.myMap.getBounds()._sw.lat;//南
this.minY = this.myMap.getBounds()._sw.lng;//西
this.updateSupercluster();
//更新state——房源列表使用——条件筛选
this.$store.commit('set_houseListFilter', {
maxX: this.maxX,
maxY: this.maxY,
minX: this.minX,
minY: this.minY
});
// console.log('保证在地图初始化完成后不重复请求house/search接口,造成性能浪费', isMoveendNum);
//判断房源右侧table和左侧地图是否收起展开,房源table列表收起时,地图拖动不再触发house/search按地图4个角查找房源接口
//保证在地图初始化完成后不重复请求house/search接口,造成性能浪费,下次地图触发moveend事件,isMoveendNum++,>1后再触发
if(!this.houseListIsCollapse && isMoveendNum > 1){
this.tableLoading = true;
//更新state——房源列表使用——是否刷新房源数据
this.$store.commit('set_getHouseData');
}
//判断地拖缩放级别是否 >= 14,值越大地图缩放级别越大
if(Math.round(this.myMap.getZoom()) >= 14){
this.POIselectShow = true;
//更新地图周边数据
this.getPOIData();
}else{
this.POIselectShow = false;
// this.$notify({
// title: '优房数据提示您:',
// message: '地图缩放级别过小,请点击地图左上角的放大按钮控件以放大地图,方便您体验查看周边信息!',
// type: 'warning',
// });
}
//清除掉上一次 房源列表MouseEnter 添加的房源marker点
if(this.houseListMouseEnterAddMarker){
this.houseListMouseEnterAddMarker.remove();
}
});
//在地图完成从一个缩放级别到另一个缩放级别的转换之后触发
this.myMap.on('zoomend', (e) => {
// console.log('zoomend')
});