自己写个dom的聚合插件
有些业务里对聚合点的要求需要强烈的自定义效果(动画,CSS特效,聚合点里包含表格等特殊需求时),这时用webgl或者canvas去绘制聚合点就比较不 方便了,这时我们可以基于 UIMarker
去自定义个聚合插件
WARNING
由于dom渲染的性能问题,所以改方案的性能相对于canvas/webgl等是比较差的,能用canvas/webgl绘制方法解决的最好 使用canvas/webgl方案
设计
抽象个图层MarkerClusterLayer
- 承载点的集合,提供动态设置数据
- 内置聚合库,当地图视角动态变化时,获取当前视野内的聚合点
- 利用UIMarker作为渲染方案,并管理UIMarker的集合
- 开放自定义UIMarker内容给用户,方便用户自定义UIMarker的内容
图层聚合
这里利用 supercluster 来完成点集的聚合
聚合点的展示
因为聚合点需要强烈的自定义效果,dom渲染是最好的方案了,故这里我们使用UIMarker作为图形渲染方案
代码
因为抽象的是个图层,所以在代码层面我们最好保持和maptalks里Layer一致的使用习惯和体验,主要包括:
- addTo方法
- remove方法
- 其他等
WARNING
注意这里只是提供个基础的代码,并没有做到和maptalks Layer一样的功能,业务可以根据自己的需要动态添加你的功能代码, 这里侧重点是提供相关思路和解决方法,比如图层的:
show
hide
zoomFilter等
这些功能我故意的没有做,交给用户自己去加,以此来锻炼用户的动手能力,而不是只会CV
js
const { Eventable, Class } = maptalks;
const OPTIONS = {
radius: 250,
maxZoom: 18,
};
function now() {
return new Date().getTime();
}
class MarkerClusterLayer extends Eventable(Class) {
constructor(options) {
super(options);
this.data = null;
this.map = null;
if (!options.createIcon) {
console.error("not find createIcon params for create uimarker");
return this;
}
const index = new Supercluster(Object.assign({}, OPTIONS, options));
this.index = index;
this.clusterCache = {};
this.time = now();
}
getMap() {
return this.map;
}
getMarkers() {
return Object.values(this.clusterCache);
}
getIndex() {
return this.index;
}
addTo(map) {
if (this.getMap()) {
console.error("it has added to map");
return this;
}
this.map = map;
this.map.on("viewchange", this._viewchange, this);
this._cluster();
return this;
}
remove() {
this.map.on("viewchange", this._viewchange, this);
this.map = null;
return this;
}
_viewchange() {
this._cluster();
}
_cluster() {
const map = this.getMap();
if (!map) {
return this;
}
if (!this.data || this.data.length === 0 || !this.options.createIcon) {
return this;
}
const bound = map.getExtent();
const { xmin, ymin, xmax, ymax } = bound;
const zoom = Math.round(map.getZoom());
const result = this.index.getClusters([xmin, ymin, xmax, ymax], zoom);
const tempCache = {};
result.forEach((feature) => {
const { id } = feature;
tempCache[id] = feature;
});
for (const key in this.clusterCache) {
if (!tempCache[key]) {
const marker = this.clusterCache[key];
marker.remove();
delete marker.feature;
delete this.clusterCache[key];
}
}
// const time = 'time';
// console.time(time);
for (const key in tempCache) {
if (this.clusterCache[key]) {
continue;
}
const feature = tempCache[key];
const marker = this.options.createIcon(feature);
if (!marker) {
continue;
}
marker.addTo(map);
marker.feature = tempCache[key];
this.clusterCache[key] = marker;
}
// console.timeEnd(time);
return this;
}
_checkData(geojson) {
let features;
if (geojson.type === "FeatureCollection") {
features = geojson.features || [];
} else if (Array.isArray(geojson)) {
features = geojson;
}
if (!features) {
console.error("geojson data is error", geojson);
return this;
}
features.forEach((feature) => {
feature.id = feature.id || `f-${maptalks.Util.GUID()}`;
});
this.index.load(features);
this.data = geojson;
return this;
}
setData(geojson) {
this._checkData(geojson);
this._cluster();
return this;
}
clear() {
this.data = [];
this.getMarkers().forEach((marker) => {
marker.remove();
});
this.clusterCache = {};
this._cluster();
return this;
}
}
MarkerClusterLayer.mergeOptions(OPTIONS);
使用
因为设计时参考的是Layer,所以其使用起来和其他的图层是一样的习惯
js
const markerClusterLayer = new MarkerClusterLayer({ createIcon });
markerClusterLayer.addTo(map);
完整的业务代码
js
var map = new maptalks.Map("map", {
center: [121.52413252, 31.14154476],
zoom: 11,
pitch: 0,
zoomControl: true,
baseLayer: new maptalks.TileLayer("base", {
urlTemplate:
"https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ["a", "b", "c", "d"],
maxAvailableZoom: 18,
}),
});
const markerClick = (e) => {
console.log(e.target);
};
const createIcon = (feature) => {
const cluster = feature.properties.cluster;
const coordinate = feature.geometry.coordinates;
let marker;
if (!cluster) {
marker = new maptalks.ui.UIMarker(coordinate, {
content: '<img src="./../assets/image/people.gif"/>',
});
} else {
const count = feature.properties.point_count;
const id = feature.id;
const index = markerClusterLayer.getIndex();
//https://github.com/mapbox/supercluster
const features = index.getLeaves(id, 10);
// console.log(features);
const size = count < 100 ? "small" : count < 1000 ? "medium" : "large";
marker = new maptalks.ui.UIMarker(coordinate, {
content: `<div class="marker-cluster marker-cluster-${size}">${count}</div>`,
});
}
marker.on("click", markerClick);
return marker;
};
const markerClusterLayer = new MarkerClusterLayer({ createIcon });
markerClusterLayer.addTo(map);
fetch("./../assets/data/pois.geojson")
.then((res) => res.json())
.then((geojson) => {
markerClusterLayer.setData(geojson);
});