diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js
index 76ede57540..ca079dd89c 100644
--- a/src/ol/geom/geometry.js
+++ b/src/ol/geom/geometry.js
@@ -1,5 +1,3 @@
-// FIXME add GeometryCollection
-
goog.provide('ol.geom.Geometry');
goog.require('goog.asserts');
@@ -18,7 +16,8 @@ ol.geom.GeometryType = {
POLYGON: 'Polygon',
MULTI_POINT: 'MultiPoint',
MULTI_LINE_STRING: 'MultiLineString',
- MULTI_POLYGON: 'MultiPolygon'
+ MULTI_POLYGON: 'MultiPolygon',
+ GEOMETRY_COLLECTION: 'GeometryCollection'
};
diff --git a/src/ol/geom/geometrycollection.exports b/src/ol/geom/geometrycollection.exports
new file mode 100644
index 0000000000..103ce2c40f
--- /dev/null
+++ b/src/ol/geom/geometrycollection.exports
@@ -0,0 +1,7 @@
+@exportSymbol ol.geom.GeometryCollection
+@exportProperty ol.geom.GeometryCollection.prototype.clone
+@exportProperty ol.geom.GeometryCollection.prototype.getExtent
+@exportProperty ol.geom.GeometryCollection.prototype.getGeometries
+@exportProperty ol.geom.GeometryCollection.prototype.getSimplifiedGeometry
+@exportProperty ol.geom.GeometryCollection.prototype.getType
+@exportProperty ol.geom.GeometryCollection.prototype.setGeometries
diff --git a/src/ol/geom/geometrycollection.js b/src/ol/geom/geometrycollection.js
new file mode 100644
index 0000000000..7765699b0c
--- /dev/null
+++ b/src/ol/geom/geometrycollection.js
@@ -0,0 +1,204 @@
+goog.provide('ol.geom.GeometryCollection');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.object');
+goog.require('ol.extent');
+goog.require('ol.geom.Geometry');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.geom.Geometry}
+ * @param {Array.
=} opt_geometries Geometries.
+ */
+ol.geom.GeometryCollection = function(opt_geometries) {
+
+ goog.base(this);
+
+ /**
+ * @private
+ * @type {Array.}
+ */
+ this.geometries_ = goog.isDef(opt_geometries) ? opt_geometries : null;
+
+};
+goog.inherits(ol.geom.GeometryCollection, ol.geom.Geometry);
+
+
+/**
+ * @param {Array.} geometries Geometries.
+ * @private
+ * @return {Array.} Cloned geometries.
+ */
+ol.geom.GeometryCollection.cloneGeometries_ = function(geometries) {
+ var clonedGeometries = [];
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ clonedGeometries.push(geometries[i].clone());
+ }
+ return clonedGeometries;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.clone = function() {
+ var geometryCollection = new ol.geom.GeometryCollection(null);
+ geometryCollection.setGeometries(this.geometries_);
+ return geometryCollection;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.closestPointXY =
+ function(x, y, closestPoint, minSquaredDistance) {
+ if (minSquaredDistance <
+ ol.extent.closestSquaredDistanceXY(this.getExtent(), x, y)) {
+ return minSquaredDistance;
+ }
+ var geometries = this.geometries_;
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ minSquaredDistance = geometries[i].closestPointXY(
+ x, y, closestPoint, minSquaredDistance);
+ }
+ return minSquaredDistance;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.containsXY = function(x, y) {
+ var geometries = this.geometries_;
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ if (geometries[i].containsXY(x, y)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.getExtent = function(opt_extent) {
+ if (this.extentRevision != this.revision) {
+ var extent = ol.extent.createOrUpdateEmpty(this.extent);
+ var geometries = this.geometries_;
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ ol.extent.extend(extent, geometries[i].getExtent());
+ }
+ this.extent = extent;
+ this.extentRevision = this.revision;
+ }
+ goog.asserts.assert(goog.isDef(this.extent));
+ return ol.extent.returnOrUpdate(this.extent, opt_extent);
+};
+
+
+/**
+ * @return {Array.} Geometries.
+ */
+ol.geom.GeometryCollection.prototype.getGeometries = function() {
+ return ol.geom.GeometryCollection.cloneGeometries_(this.geometries_);
+};
+
+
+/**
+ * @return {Array.} Geometries.
+ */
+ol.geom.GeometryCollection.prototype.getGeometriesArray = function() {
+ return this.geometries_;
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.getSimplifiedGeometry =
+ function(squaredTolerance) {
+ if (this.simplifiedGeometryRevision != this.revision) {
+ goog.object.clear(this.simplifiedGeometryCache);
+ this.simplifiedGeometryMaxMinSquaredTolerance = 0;
+ this.simplifiedGeometryRevision = this.revision;
+ }
+ if (squaredTolerance < 0 ||
+ (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 &&
+ squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance)) {
+ return this;
+ }
+ var key = squaredTolerance.toString();
+ if (this.simplifiedGeometryCache.hasOwnProperty(key)) {
+ return this.simplifiedGeometryCache[key];
+ } else {
+ var simplifiedGeometries = [];
+ var geometries = this.geometries_;
+ var simplified = false;
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ var geometry = geometries[i];
+ var simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance);
+ simplifiedGeometries.push(simplifiedGeometry);
+ if (simplifiedGeometry !== geometry) {
+ simplified = true;
+ }
+ }
+ if (simplified) {
+ var simplifiedGeometryCollection = new ol.geom.GeometryCollection(null);
+ simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries);
+ this.simplifiedGeometryCache[key] = simplifiedGeometryCollection;
+ return simplifiedGeometryCollection;
+ } else {
+ this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance;
+ return this;
+ }
+ }
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.getType = function() {
+ return ol.geom.GeometryType.GEOMETRY_COLLECTION;
+};
+
+
+/**
+ * @param {Array.} geometries Geometries.
+ */
+ol.geom.GeometryCollection.prototype.setGeometries = function(geometries) {
+ this.setGeometriesArray(
+ ol.geom.GeometryCollection.cloneGeometries_(geometries));
+};
+
+
+/**
+ * @param {Array.} geometries Geometries.
+ */
+ol.geom.GeometryCollection.prototype.setGeometriesArray = function(geometries) {
+ this.geometries_ = geometries;
+ this.dispatchChangeEvent();
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.geom.GeometryCollection.prototype.transform = function(transformFn) {
+ var geometries = this.geometries_;
+ var i, ii;
+ for (i = 0, ii = geometries.length; i < ii; ++i) {
+ geometries[i].transform(transformFn);
+ }
+ this.dispatchChangeEvent();
+};
diff --git a/test/spec/ol/geom/geometrycollection.test.js b/test/spec/ol/geom/geometrycollection.test.js
new file mode 100644
index 0000000000..f409fbf7dd
--- /dev/null
+++ b/test/spec/ol/geom/geometrycollection.test.js
@@ -0,0 +1,110 @@
+goog.provide('ol.test.geom.GeometryCollection');
+
+
+describe('ol.geom.GeometryCollection', function() {
+
+ var outer = [[0, 0], [0, 10], [10, 10], [10, 0], [0, 0]],
+ inner1 = [[1, 1], [2, 1], [2, 2], [1, 2], [1, 1]],
+ inner2 = [[8, 8], [9, 8], [9, 9], [8, 9], [8, 8]];
+
+ describe('constructor', function() {
+
+ it('creates a geometry collection from an array of geometries', function() {
+ var point = new ol.geom.Point([10, 20]);
+ var line = new ol.geom.LineString([[10, 20], [30, 40]]);
+ var poly = new ol.geom.Polygon([outer, inner1, inner2]);
+ var multi = new ol.geom.GeometryCollection([point, line, poly]);
+ expect(multi).to.be.a(ol.geom.GeometryCollection);
+ expect(multi).to.be.a(ol.geom.Geometry);
+ });
+
+ });
+
+ describe('#getGeometries', function() {
+
+ it('returns a collection of geometries', function() {
+ var point = new ol.geom.Point([10, 20]);
+ var line = new ol.geom.LineString([[10, 20], [30, 40]]);
+ var poly = new ol.geom.Polygon([outer, inner1, inner2]);
+ var multi = new ol.geom.GeometryCollection([point, line, poly]);
+
+ var geometries = multi.getGeometries();
+ expect(geometries).to.be.an(Array);
+ expect(geometries).to.have.length(3);
+ expect(geometries[0]).to.be.a(ol.geom.Point);
+ expect(geometries[1]).to.be.a(ol.geom.LineString);
+ expect(geometries[2]).to.be.a(ol.geom.Polygon);
+ });
+
+ });
+
+ describe('#clone()', function() {
+
+ it('has a working clone method', function() {
+ var point = new ol.geom.Point([10, 20]);
+ var line = new ol.geom.LineString([[10, 20], [30, 40]]);
+ var poly = new ol.geom.Polygon([outer, inner1, inner2]);
+ var multi = new ol.geom.GeometryCollection([point, line, poly]);
+ var clone = multi.clone();
+ expect(clone).to.not.be(multi);
+ var geometries = clone.getGeometries();
+ expect(geometries[0].getCoordinates()).to.eql([10, 20]);
+ expect(geometries[1].getCoordinates()).to.eql([[10, 20], [30, 40]]);
+ expect(geometries[2].getCoordinates()).to.eql([outer, inner1, inner2]);
+ });
+
+ });
+
+ describe('#getExtent()', function() {
+
+ it('returns the bounding extent', function() {
+ var point = new ol.geom.Point([10, 2]);
+ var line = new ol.geom.LineString([[1, 20], [30, 40]]);
+ var multi = new ol.geom.GeometryCollection([point, line]);
+ var extent = multi.getExtent();
+ expect(extent[0]).to.be(1);
+ expect(extent[2]).to.be(30);
+ expect(extent[1]).to.be(2);
+ expect(extent[3]).to.be(40);
+ });
+
+ });
+
+ describe('#setGeometries', function() {
+
+ var line, multi, point, poly;
+ beforeEach(function() {
+ point = new ol.geom.Point([10, 20]);
+ line = new ol.geom.LineString([[10, 20], [30, 40]]);
+ poly = new ol.geom.Polygon([outer, inner1, inner2]);
+ multi = new ol.geom.GeometryCollection([point, line, poly]);
+ });
+
+ it('fires a change event', function() {
+ var listener = sinon.spy();
+ multi.on('change', listener);
+ point.setCoordinates([15, 25]);
+ expect(listener.calledOnce).to.be(false);
+ multi.setGeometries([point, line, poly]);
+ expect(listener.calledOnce).to.be(true);
+ });
+
+ it('updates the extent', function() {
+ expect(multi.getExtent()).to.eql([0, 0, 30, 40]);
+ line.setCoordinates([[10, 20], [300, 400]]);
+ expect(multi.getExtent()).to.eql([0, 0, 30, 40]);
+ multi.setGeometries([point, line, poly]);
+ expect(multi.getExtent()).to.eql([0, 0, 300, 400]);
+ });
+
+ });
+
+});
+
+
+goog.require('ol.Collection');
+goog.require('ol.geom.Geometry');
+goog.require('ol.geom.GeometryCollection');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');