/** * @module ol/geom/GeometryCollection */ import EventType from '../events/EventType.js'; import Geometry from './Geometry.js'; import { closestSquaredDistanceXY, createOrUpdateEmpty, extend, getCenter, } from '../extent.js'; import {listen, unlistenByKey} from '../events.js'; /** * @classdesc * An array of {@link module:ol/geom/Geometry~Geometry} objects. * * @api */ class GeometryCollection extends Geometry { /** * @param {Array} [geometries] Geometries. */ constructor(geometries) { super(); /** * @private * @type {Array} */ this.geometries_ = geometries ? geometries : null; /** * @type {Array} */ this.changeEventsKeys_ = []; this.listenGeometriesChange_(); } /** * @private */ unlistenGeometriesChange_() { this.changeEventsKeys_.forEach(unlistenByKey); this.changeEventsKeys_.length = 0; } /** * @private */ listenGeometriesChange_() { if (!this.geometries_) { return; } for (let i = 0, ii = this.geometries_.length; i < ii; ++i) { this.changeEventsKeys_.push( listen(this.geometries_[i], EventType.CHANGE, this.changed, this) ); } } /** * Make a complete copy of the geometry. * @return {!GeometryCollection} Clone. * @api */ clone() { const geometryCollection = new GeometryCollection(null); geometryCollection.setGeometries(this.geometries_); geometryCollection.applyProperties(this); return geometryCollection; } /** * @param {number} x X. * @param {number} y Y. * @param {import("../coordinate.js").Coordinate} closestPoint Closest point. * @param {number} minSquaredDistance Minimum squared distance. * @return {number} Minimum squared distance. */ closestPointXY(x, y, closestPoint, minSquaredDistance) { if (minSquaredDistance < closestSquaredDistanceXY(this.getExtent(), x, y)) { return minSquaredDistance; } const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { minSquaredDistance = geometries[i].closestPointXY( x, y, closestPoint, minSquaredDistance ); } return minSquaredDistance; } /** * @param {number} x X. * @param {number} y Y. * @return {boolean} Contains (x, y). */ containsXY(x, y) { const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { if (geometries[i].containsXY(x, y)) { return true; } } return false; } /** * @param {import("../extent.js").Extent} extent Extent. * @protected * @return {import("../extent.js").Extent} extent Extent. */ computeExtent(extent) { createOrUpdateEmpty(extent); const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { extend(extent, geometries[i].getExtent()); } return extent; } /** * Return the geometries that make up this geometry collection. * @return {Array} Geometries. * @api */ getGeometries() { return cloneGeometries(this.geometries_); } /** * @return {Array} Geometries. */ getGeometriesArray() { return this.geometries_; } /** * @return {Array} Geometries. */ getGeometriesArrayRecursive() { /** @type {Array} */ let geometriesArray = []; const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { if (geometries[i].getType() === this.getType()) { geometriesArray = geometriesArray.concat( /** @type {GeometryCollection} */ ( geometries[i] ).getGeometriesArrayRecursive() ); } else { geometriesArray.push(geometries[i]); } } return geometriesArray; } /** * Create a simplified version of this geometry using the Douglas Peucker algorithm. * @param {number} squaredTolerance Squared tolerance. * @return {GeometryCollection} Simplified GeometryCollection. */ getSimplifiedGeometry(squaredTolerance) { if (this.simplifiedGeometryRevision !== this.getRevision()) { this.simplifiedGeometryMaxMinSquaredTolerance = 0; this.simplifiedGeometryRevision = this.getRevision(); } if ( squaredTolerance < 0 || (this.simplifiedGeometryMaxMinSquaredTolerance !== 0 && squaredTolerance < this.simplifiedGeometryMaxMinSquaredTolerance) ) { return this; } const simplifiedGeometries = []; const geometries = this.geometries_; let simplified = false; for (let i = 0, ii = geometries.length; i < ii; ++i) { const geometry = geometries[i]; const simplifiedGeometry = geometry.getSimplifiedGeometry(squaredTolerance); simplifiedGeometries.push(simplifiedGeometry); if (simplifiedGeometry !== geometry) { simplified = true; } } if (simplified) { const simplifiedGeometryCollection = new GeometryCollection(null); simplifiedGeometryCollection.setGeometriesArray(simplifiedGeometries); return simplifiedGeometryCollection; } this.simplifiedGeometryMaxMinSquaredTolerance = squaredTolerance; return this; } /** * Get the type of this geometry. * @return {import("./Geometry.js").Type} Geometry type. * @api */ getType() { return 'GeometryCollection'; } /** * Test if the geometry and the passed extent intersect. * @param {import("../extent.js").Extent} extent Extent. * @return {boolean} `true` if the geometry and the extent intersect. * @api */ intersectsExtent(extent) { const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { if (geometries[i].intersectsExtent(extent)) { return true; } } return false; } /** * @return {boolean} Is empty. */ isEmpty() { return this.geometries_.length === 0; } /** * Rotate the geometry around a given coordinate. This modifies the geometry * coordinates in place. * @param {number} angle Rotation angle in radians. * @param {import("../coordinate.js").Coordinate} anchor The rotation center. * @api */ rotate(angle, anchor) { const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { geometries[i].rotate(angle, anchor); } this.changed(); } /** * Scale the geometry (with an optional origin). This modifies the geometry * coordinates in place. * @abstract * @param {number} sx The scaling factor in the x-direction. * @param {number} [sy] The scaling factor in the y-direction (defaults to sx). * @param {import("../coordinate.js").Coordinate} [anchor] The scale origin (defaults to the center * of the geometry extent). * @api */ scale(sx, sy, anchor) { if (!anchor) { anchor = getCenter(this.getExtent()); } const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { geometries[i].scale(sx, sy, anchor); } this.changed(); } /** * Set the geometries that make up this geometry collection. * @param {Array} geometries Geometries. * @api */ setGeometries(geometries) { this.setGeometriesArray(cloneGeometries(geometries)); } /** * @param {Array} geometries Geometries. */ setGeometriesArray(geometries) { this.unlistenGeometriesChange_(); this.geometries_ = geometries; this.listenGeometriesChange_(); this.changed(); } /** * Apply a transform function to the coordinates of the geometry. * The geometry is modified in place. * If you do not want the geometry modified in place, first `clone()` it and * then use this function on the clone. * @param {import("../proj.js").TransformFunction} transformFn Transform function. * Called with a flat array of geometry coordinates. * @api */ applyTransform(transformFn) { const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { geometries[i].applyTransform(transformFn); } this.changed(); } /** * Translate the geometry. This modifies the geometry coordinates in place. If * instead you want a new geometry, first `clone()` this geometry. * @param {number} deltaX Delta X. * @param {number} deltaY Delta Y. * @api */ translate(deltaX, deltaY) { const geometries = this.geometries_; for (let i = 0, ii = geometries.length; i < ii; ++i) { geometries[i].translate(deltaX, deltaY); } this.changed(); } /** * Clean up. */ disposeInternal() { this.unlistenGeometriesChange_(); super.disposeInternal(); } } /** * @param {Array} geometries Geometries. * @return {Array} Cloned geometries. */ function cloneGeometries(geometries) { const clonedGeometries = []; for (let i = 0, ii = geometries.length; i < ii; ++i) { clonedGeometries.push(geometries[i].clone()); } return clonedGeometries; } export default GeometryCollection;