前面的文章openlayers中网格聚类的实现
发出来后,有好多童鞋问到了其他框架的实现,本文就大家看看在leaflet
和mapboxGL
中如何实现。
class Geojson {
constructor(features = [], metaData = {}) {
this.type = 'FeatureCollection'
this.metadata = metaData
this.features = features
}
}
class Geometry {
constructor(type, coordinates) {
this.type = type
this.coordinates = coordinates
}
}
class Feature {
constructor(geomType, properties, coordinates) {
this.type = 'Feature'
this.properties = properties
this.geometry = new Geometry(geomType, coordinates)
}
}
class GridCluster {
constructor(map, data, size = 64, showPoint = true) {
const that = this
this.map = map
this.data = data.map(d => {
d.coords = that.toWeb([d.lon, d.lat])
return d
})
this.size = size
this.val = 20037508.34
this.showPoint = showPoint
this.polygonLayer = null
this.polygonLabel = null
this.pointLayer = null
this.map.on('moveend', () => {
that.polygonLayer.remove()
that.polygonLabel.remove()
that.pointLayer.remove()
that.addCluster()
})
that.addCluster()
}
toLonLat(coords) {
const {x, y} = proj4.transform(proj4.Proj("EPSG:3857"), proj4.Proj("EPSG:4326"), coords)
return [x, y]
}
toWeb(lonlat) {
const {x, y} = proj4.transform(proj4.Proj("EPSG:4326"), proj4.Proj("EPSG:3857"), lonlat)
return [x, y]
}
addCluster() {
const that = this
const z = Math.ceil(that.map.getZoom())
const res = (that.val * 2 / Math.pow(2, z)) / 256 * that.size
let [_x1, _y1, _x2, _y2] = that.map.getBounds().toBBoxString().split(',').map(Number)
const [x1, y1] = that.toWeb([_x1, _y1])
const [x2, y2] = that.toWeb([_x2, _y2])
const count = Math.ceil(that.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++) {
let xmin = i * res - that.val
let xmax = (i+1) * res - that.val
for(let j = 0; j < count; j++) {
let ymax = that.val - j * res
let 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) {
const [_xmin, _ymin] = that.toLonLat([xmin, ymin])
const [_xmax, _ymax] = that.toLonLat([xmax, ymax])
const coords = [[[_xmin, _ymin], [_xmax, _ymin], [_xmax, _ymax], [_xmin, _ymax], [_xmin, _ymin]]]
features.push(new Feature('Polygon', {count, extent: [_xmin, _ymin, _xmax, _ymax]}, coords))
}
}
}
}
that.polygonLayer = L.geoJSON(new Geojson(features), {
style: (feature) => {
const count = feature.properties.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'
}
color = `rgb(${color})`
return {
color: color,
weight: 1,
opacity: 1,
fillColor: color,
fillOpacity: 0.5
}
}
}).addTo(map);
that.polygonLabel = L.layerGroup().addTo(map);
features.forEach(feature => {
const {count, extent} = feature.properties
const [xmin, ymin, xmax, ymax] = extent
const [x, y] = [(xmin + xmax) / 2, (ymin + ymax) / 2]
const myIcon = L.divIcon({
html: count,
className: 'cluster-count'
});
L.marker([y, x], { icon: myIcon }).addTo(that.polygonLabel);
})
if(that.showPoint) {
const features = data.map(d => {
return new Feature('Point', d, [d.lon, d.lat])
})
that.pointLayer = L.geoJSON(new Geojson(features), {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 2,
fillColor: "#f00",
color: "#f00",
weight: 1,
opacity: 1,
fillOpacity: 0.6
});
}
}).addTo(map);
}
}
}
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}
}).filter(({lon, lat}) => {
return Boolean(lon) && Boolean(lat)
})
new GridCluster(map, airports)
})
class GridCluster {
constructor(map, data, size = 64, showPoint = true) {
const that = this
this.map = map
this.data = data.map(d => {
d.coords = that.toWeb([d.lon, d.lat])
return d
})
this.size = size
this.val = 20037508.34
this.showPoint = showPoint
that.map.addSource('grid-cluster', {
type: 'geojson',
data: new Geojson()
})
that.map.addSource('grid-cluster-point', {
type: 'geojson',
data: new Geojson()
})
const color = {
"property": "count",
"stops": [
[5, 'rgb(68, 149, 247)'],
[10, 'rgb(243,199,96)'],
[10, 'rgb(255,138,5)'],
[15, 'rgb(255,10,10)'],
[20, 'rgb(165,0,179)'],
]
}
that.map.addLayer({
id: 'grid-cluster-fill',
type: 'fill',
source: 'grid-cluster',
paint: {
'fill-color': color,
'fill-opacity': 0.5
}
})
that.map.addLayer({
id: 'grid-cluster-line',
type: 'line',
source: 'grid-cluster',
paint: {
'line-color': color,
'line-width': 1
}
})
that.map.addLayer({
id: 'grid-cluster-point',
type: 'circle',
source: 'grid-cluster-point',
paint: {
'circle-color': '#f00',
'circle-radius': 5,
'circle-blur': 2
}
})
that.map.addLayer({
id: 'grid-cluster-text',
type: 'symbol',
source: 'grid-cluster',
layout: {
'text-anchor': "center",
'text-field': "{count}",
'text-size': 14
},
paint: {
"text-color": '#000' ,
"text-halo-color": '#fff',
"text-halo-width": 1,
}
})
this.map.on('moveend', () => {
that.addCluster()
})
that.addCluster()
}
toLonLat(coords) {
const {x, y} = proj4.transform(proj4.Proj("EPSG:3857"), proj4.Proj("EPSG:4326"), coords)
return [x, y]
}
toWeb(lonlat) {
const {x, y} = proj4.transform(proj4.Proj("EPSG:4326"), proj4.Proj("EPSG:3857"), lonlat)
return [x, y]
}
addCluster() {
const that = this
const z = Math.ceil(that.map.getZoom())
const res = (that.val * 2 / Math.pow(2, z)) / 256 * that.size
const [min, max] = that.map.getBounds().toArray()
let [x1, y1] = that.toWeb(min)
let [x2, y2] = that.toWeb(max)
const count = Math.ceil(that.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++) {
let xmin = i * res - that.val
let xmax = (i+1) * res - that.val
for(let j = 0; j < count; j++) {
let ymax = that.val - j * res
let 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) {
const [_xmin, _ymin] = that.toLonLat([xmin, ymin])
const [_xmax, _ymax] = that.toLonLat([xmax, ymax])
const coords = [[[_xmin, _ymin], [_xmax, _ymin], [_xmax, _ymax], [_xmin, _ymax], [_xmin, _ymin]]]
features.push(new Feature('Polygon', {count}, coords))
}
}
}
}
that.map.getSource('grid-cluster').setData(new Geojson(features))
if(that.showPoint) {
const features = data.map(d => {
return new Feature('Point', d, [d.lon, d.lat])
})
that.map.getSource('grid-cluster-point').setData(new Geojson(features))
}
}
}
map.on('load', () => {
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}
}).filter(({lon, lat}) => {
return Boolean(lon) && Boolean(lat)
})
new GridCluster(map, airports)
})
})