diff --git a/examples/cluster.css b/examples/cluster.css new file mode 100644 index 0000000000..b1f7938dd4 --- /dev/null +++ b/examples/cluster.css @@ -0,0 +1,15 @@ +.info { + min-width: 3em; + text-align: right; +} +form { + display: table; +} +form > div { + display: table-row; +} +form > div > * { + display: table-cell; + white-space: nowrap; + padding-right: 5px; +} diff --git a/examples/cluster.html b/examples/cluster.html index 70c52312a8..9313ece8d3 100644 --- a/examples/cluster.html +++ b/examples/cluster.html @@ -8,6 +8,22 @@ tags: "cluster, vector" ---
diff --git a/examples/cluster.js b/examples/cluster.js index 48e1d0493c..ddc96de897 100644 --- a/examples/cluster.js +++ b/examples/cluster.js @@ -13,7 +13,11 @@ import {Cluster, OSM, Vector as VectorSource} from '../src/ol/source.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {boundingExtent} from '../src/ol/extent.js'; -const distance = document.getElementById('distance'); +const distanceInput = document.getElementById('distance'); +const distanceNode = document.getElementById('distance-info'); +const minDistanceInput = document.getElementById('min-distance'); +const minDistanceNode = document.getElementById('min-distance-info'); +const numClustersNode = document.getElementById('num-clusters'); const count = 20000; const features = new Array(count); @@ -28,9 +32,15 @@ const source = new VectorSource({ }); const clusterSource = new Cluster({ - distance: parseInt(distance.value, 10), + distance: parseInt(distanceInput.value, 10), + minDistance: parseInt(minDistanceInput.value, 10), source: source, }); +clusterSource.on('change', function (evt) { + numClustersNode.innerText = evt.target.features.length; +}); +distanceNode.innerText = clusterSource.getDistance(); +minDistanceNode.innerText = clusterSource.getMinDistance(); const styleCache = {}; const clusters = new VectorLayer({ @@ -75,8 +85,14 @@ const map = new Map({ }), }); -distance.addEventListener('input', function () { - clusterSource.setDistance(parseInt(distance.value, 10)); +distanceInput.addEventListener('input', function () { + distanceNode.innerText = distanceInput.value; + clusterSource.setDistance(parseInt(distanceInput.value, 10)); +}); + +minDistanceInput.addEventListener('input', function () { + minDistanceNode.innerText = minDistanceInput.value; + clusterSource.setMinDistance(parseInt(minDistanceInput.value, 10)); }); map.on('click', (e) => { diff --git a/src/ol/source/Cluster.js b/src/ol/source/Cluster.js index 22050979bd..a8192b9974 100644 --- a/src/ol/source/Cluster.js +++ b/src/ol/source/Cluster.js @@ -9,13 +9,19 @@ import Point from '../geom/Point.js'; import VectorSource from './Vector.js'; import {add as addCoordinate, scale as scaleCoordinate} from '../coordinate.js'; import {assert} from '../asserts.js'; -import {buffer, createEmpty, createOrUpdateFromCoordinate} from '../extent.js'; +import { + buffer, + createEmpty, + createOrUpdateFromCoordinate, + getCenter, +} from '../extent.js'; import {getUid} from '../util.js'; /** * @typedef {Object} Options * @property {import("./Source.js").AttributionLike} [attributions] Attributions. - * @property {number} [distance=20] Minimum distance in pixels between clusters. + * @property {number} [distance=20] Distance within which features will be clustered + * together. * @property {function(Feature):Point} [geometryFunction] * Function that takes an {@link module:ol/Feature} as argument and returns an * {@link module:ol/geom/Point} as cluster calculation point for the feature. When a @@ -29,6 +35,11 @@ import {getUid} from '../util.js'; * ``` * See {@link module:ol/geom/Polygon~Polygon#getInteriorPoint} for a way to get a cluster * calculation point for polygons. + * @property {number} [minDistance=0] Minimum distance between clusters. Will be capped + * at the configured distance. + * By default no minimum distance is guaranteed. This config can be used to avoid + * overlapping icons. As a tradoff, the cluster feature's position will no longer be + * the center of all its features. * @property {VectorSource} [source] Source. * @property {boolean} [wrapX=true] Whether to wrap the world horizontally. */ @@ -66,6 +77,18 @@ class Cluster extends VectorSource { */ this.distance = options.distance !== undefined ? options.distance : 20; + /** + * @type {number} + * @protected + */ + this.minDistance = options.minDistance || 0; + + /** + * @type {number} + * @protected + */ + this.interpolationRatio = 0; + /** * @type {Array