Merge pull request #4917 from ahocevar/flexible-cluster
Make ol.source.Cluster more flexible by adding a geometryFunction option
This commit is contained in:
@@ -3984,6 +3984,7 @@ olx.source.BingMapsOptions.prototype.wrapX;
|
||||
* distance: (number|undefined),
|
||||
* extent: (ol.Extent|undefined),
|
||||
* format: (ol.format.Feature|undefined),
|
||||
* geometryFunction: (undefined|function(ol.Feature):ol.geom.Point),
|
||||
* logo: (string|undefined),
|
||||
* projection: ol.proj.ProjectionLike,
|
||||
* source: ol.source.Vector,
|
||||
@@ -4017,6 +4018,25 @@ olx.source.ClusterOptions.prototype.distance;
|
||||
olx.source.ClusterOptions.prototype.extent;
|
||||
|
||||
|
||||
/**
|
||||
* Function that takes an {@link ol.Feature} as argument and returns an
|
||||
* {@link ol.geom.Point} as cluster calculation point for the feature. When a
|
||||
* feature should not be considered for clustering, the function should return
|
||||
* `null`. The default, which works when the underyling source contains point
|
||||
* features only, is
|
||||
* ```js
|
||||
* function(feature) {
|
||||
* return feature.getGeometry();
|
||||
* }
|
||||
* ```
|
||||
* See {@link ol.geom.Polygon#getInteriorPoint} for a way to get a cluster
|
||||
* calculation point for polygons.
|
||||
* @type {undefined|function(ol.Feature):ol.geom.Point}
|
||||
* @api
|
||||
*/
|
||||
olx.source.ClusterOptions.prototype.geometryFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Format.
|
||||
* @type {ol.format.Feature|undefined}
|
||||
@@ -6174,8 +6194,8 @@ olx.style.FillOptions;
|
||||
|
||||
|
||||
/**
|
||||
* A color, gradient or pattern. See {@link ol.color}
|
||||
* and {@link ol.colorlike} for possible formats. Default null;
|
||||
* A color, gradient or pattern. See {@link ol.color}
|
||||
* and {@link ol.colorlike} for possible formats. Default null;
|
||||
* if null, the Canvas/renderer default black will be used.
|
||||
* @type {ol.Color|ol.ColorLike|undefined}
|
||||
* @api
|
||||
|
||||
@@ -14,7 +14,9 @@ goog.require('ol.source.Vector');
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Layer source to cluster vector data.
|
||||
* Layer source to cluster vector data. Works out of the box with point
|
||||
* geometries. For other geometry types, or if not all geometries should be
|
||||
* considered for clustering, a custom `geometryFunction` can be defined.
|
||||
*
|
||||
* @constructor
|
||||
* @param {olx.source.ClusterOptions} options Constructor options.
|
||||
@@ -48,6 +50,17 @@ ol.source.Cluster = function(options) {
|
||||
*/
|
||||
this.features_ = [];
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature.
|
||||
* @return {ol.geom.Point} Cluster calculation point.
|
||||
*/
|
||||
this.geometryFunction_ = options.geometryFunction || function(feature) {
|
||||
var geometry = feature.getGeometry();
|
||||
goog.asserts.assert(geometry instanceof ol.geom.Point,
|
||||
'feature geometry is a ol.geom.Point instance');
|
||||
return geometry;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {ol.source.Vector}
|
||||
* @private
|
||||
@@ -117,25 +130,25 @@ ol.source.Cluster.prototype.cluster_ = function() {
|
||||
for (var i = 0, ii = features.length; i < ii; i++) {
|
||||
var feature = features[i];
|
||||
if (!(goog.getUid(feature).toString() in clustered)) {
|
||||
var geometry = feature.getGeometry();
|
||||
goog.asserts.assert(geometry instanceof ol.geom.Point,
|
||||
'feature geometry is a ol.geom.Point instance');
|
||||
var coordinates = geometry.getCoordinates();
|
||||
ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
|
||||
ol.extent.buffer(extent, mapDistance, extent);
|
||||
var geometry = this.geometryFunction_(feature);
|
||||
if (geometry) {
|
||||
var coordinates = geometry.getCoordinates();
|
||||
ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
|
||||
ol.extent.buffer(extent, mapDistance, extent);
|
||||
|
||||
var neighbors = this.source_.getFeaturesInExtent(extent);
|
||||
goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found');
|
||||
neighbors = neighbors.filter(function(neighbor) {
|
||||
var uid = goog.getUid(neighbor).toString();
|
||||
if (!(uid in clustered)) {
|
||||
clustered[uid] = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
this.features_.push(this.createCluster_(neighbors));
|
||||
var neighbors = this.source_.getFeaturesInExtent(extent);
|
||||
goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found');
|
||||
neighbors = neighbors.filter(function(neighbor) {
|
||||
var uid = goog.getUid(neighbor).toString();
|
||||
if (!(uid in clustered)) {
|
||||
clustered[uid] = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
this.features_.push(this.createCluster_(neighbors));
|
||||
}
|
||||
}
|
||||
}
|
||||
goog.asserts.assert(
|
||||
@@ -150,16 +163,16 @@ ol.source.Cluster.prototype.cluster_ = function() {
|
||||
* @private
|
||||
*/
|
||||
ol.source.Cluster.prototype.createCluster_ = function(features) {
|
||||
var length = features.length;
|
||||
var centroid = [0, 0];
|
||||
for (var i = 0; i < length; i++) {
|
||||
var geometry = features[i].getGeometry();
|
||||
goog.asserts.assert(geometry instanceof ol.geom.Point,
|
||||
'feature geometry is a ol.geom.Point instance');
|
||||
var coordinates = geometry.getCoordinates();
|
||||
ol.coordinate.add(centroid, coordinates);
|
||||
for (var i = features.length - 1; i >= 0; --i) {
|
||||
var geometry = this.geometryFunction_(features[i]);
|
||||
if (geometry) {
|
||||
ol.coordinate.add(centroid, geometry.getCoordinates());
|
||||
} else {
|
||||
features.splice(i, 1);
|
||||
}
|
||||
}
|
||||
ol.coordinate.scale(centroid, 1 / length);
|
||||
ol.coordinate.scale(centroid, 1 / features.length);
|
||||
|
||||
var cluster = new ol.Feature(new ol.geom.Point(centroid));
|
||||
cluster.set('features', features);
|
||||
|
||||
@@ -14,8 +14,54 @@ describe('ol.source.Cluster', function() {
|
||||
expect(source).to.be.a(ol.source.Cluster);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#loadFeatures', function() {
|
||||
var extent = [-1, -1, 1, 1];
|
||||
var projection = ol.proj.get('EPSG:3857');
|
||||
it('clusters a source with point features', function() {
|
||||
var source = new ol.source.Cluster({
|
||||
source: new ol.source.Vector({
|
||||
features: [
|
||||
new ol.Feature(new ol.geom.Point([0, 0])),
|
||||
new ol.Feature(new ol.geom.Point([0, 0]))
|
||||
]
|
||||
})
|
||||
});
|
||||
source.loadFeatures(extent, 1, projection);
|
||||
expect(source.getFeatures().length).to.be(1);
|
||||
expect(source.getFeatures()[0].get('features').length).to.be(2);
|
||||
});
|
||||
it('clusters with a custom geometryFunction', function() {
|
||||
var source = new ol.source.Cluster({
|
||||
geometryFunction: function(feature) {
|
||||
var geom = feature.getGeometry();
|
||||
if (geom.getType() == 'Point') {
|
||||
return geom;
|
||||
} else if (geom.getType() == 'Polygon') {
|
||||
return geom.getInteriorPoint();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
source: new ol.source.Vector({
|
||||
features: [
|
||||
new ol.Feature(new ol.geom.Point([0, 0])),
|
||||
new ol.Feature(new ol.geom.LineString([[0, 0], [1, 1]])),
|
||||
new ol.Feature(new ol.geom.Polygon(
|
||||
[[[-1, -1], [-1, 1], [1, 1], [1, -1], [-1, -1]]]))
|
||||
]
|
||||
})
|
||||
});
|
||||
source.loadFeatures(extent, 1, projection);
|
||||
expect(source.getFeatures().length).to.be(1);
|
||||
expect(source.getFeatures()[0].get('features').length).to.be(2);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
goog.require('ol.Feature');
|
||||
goog.require('ol.geom.LineString');
|
||||
goog.require('ol.geom.Point');
|
||||
goog.require('ol.geom.Polygon');
|
||||
goog.require('ol.proj');
|
||||
goog.require('ol.source.Cluster');
|
||||
goog.require('ol.source.Source');
|
||||
|
||||
Reference in New Issue
Block a user