leaflet和mapboxGL中网格聚类的实现

2023-06-10 11:20:44 浏览数 (2)

概述

前面的文章openlayers中网格聚类的实现发出来后,有好多童鞋问到了其他框架的实现,本文就大家看看在leafletmapboxGL中如何实现。

效果

实现

1. leaflet实现

代码语言:javascript复制
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)
})

2. mapboxGL实现

代码语言:javascript复制
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)
  })
})

0 人点赞