前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >leaflet和mapboxGL中网格聚类的实现

leaflet和mapboxGL中网格聚类的实现

作者头像
牛老师讲GIS
发布2023-06-10 11:20:44
4060
发布2023-06-10 11:20:44
举报
文章被收录于专栏:跟牛老师一起学WEBGIS

概述

前面的文章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)
  })
})
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 效果
  • 实现
    • 1. leaflet实现
      • 2. mapboxGL实现
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档