概述
聚类是根据一定的规则将数据进行分类统计,常见的聚类方式有:1、基于行政区划;2、基于空间距离;3、基于业务字段。本文实现了基于固定大小的网格的聚类。
效果
实现
1. 数据源
本文的数据源为cesium
示例中的全球的机场数据。
2. 实现代码
- 网格大小可配置,默认为64;
- 根据聚类的数量进行了分级颜色渲染;
class GridCluster {
constructor(map, data, size = 64, showPoint = true) {
this.map = map
this.data = data.map(d => {
d.coords = ol.proj.fromLonLat([d.lon, d.lat])
return d
})
this.size = size
this.val = 20037508.34
this.showPoint = showPoint
const source = new ol.source.Vector({
features: []
});
const vector = new ol.layer.Vector({
source: source,
style: (feat) => {
const count = feat.get('count')
let color = `68, 149, 247`
if(count > 20) {
color = '165,0,179'
} else if(count <= 20 && count > 15) {
color = '255,10,10'
} else if(count <= 15 &&count > 10) {
color = '255,138,5'
}
else if(count <= 10 &&count > 5) {
color = '247,176,76'
}
const colorPoint = '255,0,0'
const stroke = new ol.style.Stroke({
color: `rgba(${color}, 1)`,
width: 1
})
const style = count && count > 0 ? {
fill: new ol.style.Fill({
color: `rgba(${color}, 0.35)`
}),
stroke: stroke,
text: new ol.style.Text({
text: count.toString(),
font: 'bold 16px sans-serif',
fill: new ol.style.Fill({
color: '#000'
}),
stroke: new ol.style.Stroke({
color: `rgba(255,255,255, 1)`,
width: 1
})
})
} : {
image: new ol.style.Circle({
fill: new ol.style.Fill({
color: `rgba(${colorPoint}, 0.7)`
}),
stroke: new ol.style.Stroke({
color: `rgba(${colorPoint}, 1)`,
width: 1
}),
radius: 2
}),
}
return new ol.style.Style(style)
}
});
map.addLayer(vector)
this.source = source
const that = this
this.map.on('movestart', () => {
this.source.clear()
})
this.map.on('moveend', () => {
that.addCluster()
})
that.addCluster()
}
addCluster() {
const that = this
const zoom = that.map.getView().getZoom()
const res = that.map.getView().getResolutionForZoom(zoom) * that.size
const [x1, y1, x2, y2] = that.map.getView().calculateExtent()
const count = Math.ceil(this.val * 2 / res)
let features = []
const data = that.data.filter(({coords}) => {
const [x, y] = coords
return x >= x1 && x <= x2 && y >= y1 && y <= y2
})
for(let i = 0; i < count; i ) {
const xmin = i * res - that.val
const xmax = (i 1) * res - that.val
for(let j = 0; j < count; j ) {
const ymax = that.val - j * res
const ymin = that.val - (j 1) * res
const isInExtent = xmin >= x1 && xmin <= x2 && ymin >= y1 && ymin <= y2
|| xmax >= x1 && xmax <= x2 && ymin >= y1 && ymin <= y2
|| xmin >= x1 && xmin <= x2 && ymax >= y1 && ymax <= y2
|| xmax >= x1 && xmax <= x2 && ymax >= y1 && ymax <= y2
if(isInExtent) {
const dataFilter = [...data].filter(({coords}) => {
const [x, y] = coords
return x >= xmin && x <= xmax && y >= ymin && y <= ymax
})
const count = dataFilter.length
if(count > 1) {
features.push(new ol.Feature({
geometry: ol.geom.Polygon.fromExtent([xmin, ymin, xmax, ymax]),
count: count,
data: dataFilter
}))
}
}
}
}
that.source.addFeatures(features)
if(that.showPoint) {
const features = data.map(d => {
return new ol.Feature({
geometry: new ol.geom.Point(d.coords),
data: d
})
})
that.source.addFeatures(features)
}
}
}
fetch('./flights.json').then(res => res.json()).then(res => {
let {airports} = res
airports = airports.map(([name, city, country, lon, lat]) => {
return {name, city, country, lon, lat}
})
new GridCluster(map, airports)
})