十五、openlayers官方示例Clustered Features——使用聚合加载大量点位数据

官网demo地址:

 Clustered Features

这篇讲的是如何使用聚合处理大量点数据。

为什么会用聚合呢?当我们在地图上加载了大量矢量点位数据之后,地图就会卡顿,而在地图层级缩放层级较小的时候根本也看不见点位的具体信息。这时候,使用聚合就是一个比较好的解决方案。地图缩小的时候它会将紧挨着的许多点位合并成一个矢量点,放大到一定程度才会显示具体点位信息。

首先,来生成一些点位坐标数组

 const count = 20000;
      const features = new Array(count);
      const e = 4500000;
      for (let i = 0; i < count; ++i) {
        const coordinates = [
          2 * e * Math.random() - e,
          2 * e * Math.random() - e,
        ];
        features[i] = new Feature(new Point(coordinates));
      }

此时,features里面就是两万个点位的feature。然后把它们加载到矢量数据源上

const source = new VectorSource({
        features: features,
      });

 接着创建聚合类,Cluster的source是一个矢量数据源。distance表示的是feature与feature之间的距离,因为这个示例中有一个滑块动态设置间距,所以这里要设置一下minDistance最小距离。

 const clusterSource = new Cluster({
        distance: this.distanceInput,
        minDistance: this.minDistanceInput,
        source: source,
      });

然后把source加载图层上。

    this.clusterLayer = new VectorLayer({
        source: clusterSource,
      });

来看下效果:

点位都出来了,接下来加点样式配置。在实例化VectorLayer时加一个style配置,函数里会返回每一个feature,每一个feature上都有一个features属性,记录着它的下级features数组,聚合的原理就是把几个相邻的点通过计算合成一个feature,我猜features里面记录的就是合成这个feature所用的数组。style后面的这个函数是个高频触发的函数,地图稍微移动一下都会触发,所以这里的使用styleCache对象做了缓存效果,以每个feature的features数组的长度作为key,这样就不用每次都重新new Style了。

const styleCache = {};
      this.clusterLayer = new VectorLayer({
        source: clusterSource,
        style: function (feature) {
          const size = feature.get("features").length;
          let style = styleCache[size];
          if (!style) {
            style = new Style({
              image: new CircleStyle({
                radius: 10,
                stroke: new Stroke({
                  color: "#fff",
                }),
                fill: new Fill({
                  color: "#3399CC",
                }),
              }),
              text: new Text({
                text: size.toString(),
                fill: new Fill({
                  color: "#fff",
                }),
              }),
            });
            styleCache[size] = style;
          }
          return style;
        },
      });

注册地图点击事件,点击每一个feature就会把地图视角定位到点击的feature处。

this.map.on("click", (e) => {
      this.clusterLayer.getFeatures(e.pixel).then((clickedFeatures) => {
        if (clickedFeatures.length) {
          // Get clustered Coordinates
          console.log("clickedFeatures[0]", clickedFeatures);
          const features = clickedFeatures[0].get("features");
          //   console.log('features',features);
          const polygon = clickedFeatures[0].getGeometry();
          if (features.length > 1) {
            console.log(
              "features.map((r) => r.getGeometry().getCoordinates())",
              features.map((r) => r.getGeometry().getCoordinates())
            );
            const extent = boundingExtent(
              features.map((r) => r.getGeometry().getCoordinates())
            );
            this.map
              .getView()
              .fit(extent, { duration: 1000, padding: [50, 50, 50, 50] });
          }
        }
      });
    });

features.map((r) => r.getGeometry().getCoordinates()) 这段获取了组成这个feature用到的所有坐标数组。

使用boundingExtent(坐标数组)方法,可以计算出extent边界值。

最后再使用fit方法进行定位,duration表示动画时间。

this.map.getView().fit(extent, { duration: 1000, padding: [50, 50, 50, 50] });

小细节:

fit方法的第一个参数可以接受extent(边界值)或者geometry(几何形状)。

而这里为什么要用extent而不是geometry呢?使用geometry定位看看。

 const polygon = clickedFeatures[0].getGeometry();
 this.map.getView().fit(extent, { duration: 1000, padding: [50, 50, 50, 50] });

 额。。。定位到了一个不知道的地方。

这是因为聚合里面,图层上的feature本来就是根据缩放层级变化的,定位过程中刚刚点击的那个feature已经不见了。而且这里我们希望地图视角移动到一个刚好能显示所有下级features的位置。所以这里使用extent更为合适。

完整代码:

<template>
  <div class="box">
    <h1>ClusteredFeatures 使用聚合加载大量数据点</h1>
    <div id="map"></div>
    <form>
      <div class="form-group">
        <label for="distance" class="col-form-label pb-0"
          >Cluster distance</label
        >
        <input
          id="distance"
          class="form-range"
          type="range"
          min="0"
          max="200"
          step="1"
          @change="distanceInputFun"
          v-model.number="distanceInput"
        />
        <small class="form-text text-muted"> features聚集在一起的距离 </small>
      </div>
      <div class="form-group">
        <label for="min-distance" class="col-form-label pb-0"
          >Minimum distance</label
        >
        <input
          id="min-distance"
          class="form-range"
          type="range"
          min="0"
          max="200"
          step="1"
          @change="minDistanceInputFun"
          v-model.number="minDistanceInput"
        />
        <small class="form-text text-muted">
          features之间的最小距离。不能大于配置的距离。
        </small>
      </div>
    </form>
  </div>
</template>

<script>
import Feature from "ol/Feature.js";
import Map from "ol/Map.js";
import Point from "ol/geom/Point.js";
import View from "ol/View.js";
import { Circle as CircleStyle, Fill, Stroke, Style, Text } from "ol/style.js";
import { Cluster, OSM, Vector as VectorSource } from "ol/source.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { boundingExtent } from "ol/extent.js";

export default {
  name: "",
  components: {},
  data() {
    return {
      map: null,
      clusterLayer: null,
      distanceInput: 40,
      minDistanceInput: 20,
    };
  },
  computed: {},
  created() {},
  mounted() {
    this.initMap();
    this.addUnderLayer();
    this.addClusterLayer();
    this.map.on("click", (e) => {
      this.clusterLayer.getFeatures(e.pixel).then((clickedFeatures) => {
        if (clickedFeatures.length) {
          // Get clustered Coordinates
          console.log("clickedFeatures[0]", clickedFeatures);
          const features = clickedFeatures[0].get("features");
          //   console.log('features',features);
          
          if (features.length > 1) {
            console.log(
              "features.map((r) => r.getGeometry().getCoordinates())",
              features.map((r) => r.getGeometry().getCoordinates())
            );
            const extent = boundingExtent(
              features.map((r) => r.getGeometry().getCoordinates())
            );
            const polygon = clickedFeatures[0].getGeometry();
            this.map.getView().fit(extent, { duration: 0, padding: [50, 50, 50, 50] });
          }
        }
      });
    });
  },
  methods: {
    distanceInputFun() {
      this.clusterLayer
        .getSource()
        .setDistance(parseInt(this.distanceInput, 10));
    },
    minDistanceInputFun() {
      this.clusterLayer
        .getSource()
        .setDistance(parseInt(this.minDistanceInput, 10));
    },
    addClusterLayer() {
      const count = 20000;
      const features = new Array(count);
      const e = 4500000;
      for (let i = 0; i < count; ++i) {
        const coordinates = [
          2 * e * Math.random() - e,
          2 * e * Math.random() - e,
        ];
        features[i] = new Feature(new Point(coordinates));
      }
      const source = new VectorSource({
        features: features,
      });

      const clusterSource = new Cluster({
        distance: this.distanceInput,
        minDistance: this.minDistanceInput,
        source: source,
      });

      const styleCache = {};
      this.clusterLayer = new VectorLayer({
        source: clusterSource,
        style: function (feature) {
          const size = feature.get("features").length;
          let style = styleCache[size];
          if (!style) {
            style = new Style({
              image: new CircleStyle({
                radius: 10,
                stroke: new Stroke({
                  color: "#fff",
                }),
                fill: new Fill({
                  color: "#3399CC",
                }),
              }),
              text: new Text({
                text: size.toString(),
                fill: new Fill({
                  color: "#fff",
                }),
              }),
            });
            styleCache[size] = style;
          }
          return style;
        },
      });
      this.map.addLayer(this.clusterLayer);
    },
    addUnderLayer() {
      const raster = new TileLayer({
        source: new OSM(),
      });
      this.map.addLayer(raster);
    },
    initMap() {
      this.map = new Map({
        layers: [],
        target: "map",
        view: new View({
          center: [0, 0],
          zoom: 2,
        }),
      });
    },
  },
};
</script>

<style lang="scss" scoped>
#map {
  width: 100%;
  height: 500px;
}
.box {
  height: 100%;
}
</style>

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值