Merge pull request #4917 from ahocevar/flexible-cluster

Make ol.source.Cluster more flexible by adding a geometryFunction option
This commit is contained in:
Andreas Hocevar
2016-02-29 10:40:12 +01:00
3 changed files with 108 additions and 29 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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');