Make ol.source.Cluster more flexible by adding a geometryFunction option

This commit is contained in:
Andreas Hocevar
2016-02-25 14:58:09 +01:00
parent fa180cfa13
commit 0407e779f9
3 changed files with 108 additions and 29 deletions
+20
View File
@@ -3984,6 +3984,7 @@ olx.source.BingMapsOptions.prototype.wrapX;
* distance: (number|undefined), * distance: (number|undefined),
* extent: (ol.Extent|undefined), * extent: (ol.Extent|undefined),
* format: (ol.format.Feature|undefined), * format: (ol.format.Feature|undefined),
* geometryFunction: (undefined|function(ol.Feature):ol.geom.Point),
* logo: (string|undefined), * logo: (string|undefined),
* projection: ol.proj.ProjectionLike, * projection: ol.proj.ProjectionLike,
* source: ol.source.Vector, * source: ol.source.Vector,
@@ -4017,6 +4018,25 @@ olx.source.ClusterOptions.prototype.distance;
olx.source.ClusterOptions.prototype.extent; 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. * Format.
* @type {ol.format.Feature|undefined} * @type {ol.format.Feature|undefined}
+40 -27
View File
@@ -14,7 +14,9 @@ goog.require('ol.source.Vector');
/** /**
* @classdesc * @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 * @constructor
* @param {olx.source.ClusterOptions} options Constructor options. * @param {olx.source.ClusterOptions} options Constructor options.
@@ -48,6 +50,17 @@ ol.source.Cluster = function(options) {
*/ */
this.features_ = []; 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} * @type {ol.source.Vector}
* @private * @private
@@ -117,25 +130,25 @@ ol.source.Cluster.prototype.cluster_ = function() {
for (var i = 0, ii = features.length; i < ii; i++) { for (var i = 0, ii = features.length; i < ii; i++) {
var feature = features[i]; var feature = features[i];
if (!(goog.getUid(feature).toString() in clustered)) { if (!(goog.getUid(feature).toString() in clustered)) {
var geometry = feature.getGeometry(); var geometry = this.geometryFunction_(feature);
goog.asserts.assert(geometry instanceof ol.geom.Point, if (geometry) {
'feature geometry is a ol.geom.Point instance'); var coordinates = geometry.getCoordinates();
var coordinates = geometry.getCoordinates(); ol.extent.createOrUpdateFromCoordinate(coordinates, extent);
ol.extent.createOrUpdateFromCoordinate(coordinates, extent); ol.extent.buffer(extent, mapDistance, extent);
ol.extent.buffer(extent, mapDistance, extent);
var neighbors = this.source_.getFeaturesInExtent(extent); var neighbors = this.source_.getFeaturesInExtent(extent);
goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found'); goog.asserts.assert(neighbors.length >= 1, 'at least one neighbor found');
neighbors = neighbors.filter(function(neighbor) { neighbors = neighbors.filter(function(neighbor) {
var uid = goog.getUid(neighbor).toString(); var uid = goog.getUid(neighbor).toString();
if (!(uid in clustered)) { if (!(uid in clustered)) {
clustered[uid] = true; clustered[uid] = true;
return true; return true;
} else { } else {
return false; return false;
} }
}); });
this.features_.push(this.createCluster_(neighbors)); this.features_.push(this.createCluster_(neighbors));
}
} }
} }
goog.asserts.assert( goog.asserts.assert(
@@ -150,16 +163,16 @@ ol.source.Cluster.prototype.cluster_ = function() {
* @private * @private
*/ */
ol.source.Cluster.prototype.createCluster_ = function(features) { ol.source.Cluster.prototype.createCluster_ = function(features) {
var length = features.length;
var centroid = [0, 0]; var centroid = [0, 0];
for (var i = 0; i < length; i++) { for (var i = features.length - 1; i >= 0; --i) {
var geometry = features[i].getGeometry(); var geometry = this.geometryFunction_(features[i]);
goog.asserts.assert(geometry instanceof ol.geom.Point, if (geometry) {
'feature geometry is a ol.geom.Point instance'); ol.coordinate.add(centroid, geometry.getCoordinates());
var coordinates = geometry.getCoordinates(); } else {
ol.coordinate.add(centroid, coordinates); 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)); var cluster = new ol.Feature(new ol.geom.Point(centroid));
cluster.set('features', features); cluster.set('features', features);
+46
View File
@@ -14,8 +14,54 @@ describe('ol.source.Cluster', function() {
expect(source).to.be.a(ol.source.Cluster); 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.proj');
goog.require('ol.source.Cluster'); goog.require('ol.source.Cluster');
goog.require('ol.source.Source'); goog.require('ol.source.Source');