goog.provide('ol.layer.Vector'); goog.provide('ol.layer.VectorLayerEventType'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.Feature'); goog.require('ol.expr'); goog.require('ol.expr.Literal'); goog.require('ol.expr.Logical'); goog.require('ol.expr.LogicalOp'); goog.require('ol.expr.functions'); goog.require('ol.extent'); goog.require('ol.geom.GeometryType'); goog.require('ol.geom.SharedVertices'); goog.require('ol.layer.Layer'); goog.require('ol.proj'); goog.require('ol.source.Vector'); goog.require('ol.structs.RTree'); goog.require('ol.style'); goog.require('ol.style.Style'); goog.require('ol.style.TextLiteral'); /** * @constructor */ ol.layer.FeatureCache = function() { /** * @type {Object.} * @private */ this.idLookup_; /** * @type {Object.} * @private */ this.geometryTypeIndex_; /** * @type {ol.structs.RTree} * @private */ this.rTree_; this.clear(); }; /** * Clear the cache. */ ol.layer.FeatureCache.prototype.clear = function() { this.idLookup_ = {}; var geometryTypeIndex = {}; for (var key in ol.geom.GeometryType) { geometryTypeIndex[ol.geom.GeometryType[key]] = {}; } this.geometryTypeIndex_ = geometryTypeIndex; this.rTree_ = new ol.structs.RTree(); }; /** * Add a feature to the cache. * @param {ol.Feature} feature Feature to be cached. */ ol.layer.FeatureCache.prototype.add = function(feature) { var id = goog.getUid(feature).toString(), geometry = feature.getGeometry(); this.idLookup_[id] = feature; // index by geometry type and bounding box if (!goog.isNull(geometry)) { var geometryType = geometry.getType(); this.geometryTypeIndex_[geometryType][id] = feature; this.rTree_.insert(geometry.getBounds(), feature, geometryType); } }; /** * @param {ol.expr.Expression=} opt_expr Expression for filtering. * @return {Object.} Object of features, keyed by id. */ ol.layer.FeatureCache.prototype.getFeaturesObject = function(opt_expr) { var features; if (!goog.isDef(opt_expr)) { features = this.idLookup_; } else { // check for geometryType or extent expression var name = ol.expr.isLibCall(opt_expr); if (name === ol.expr.functions.GEOMETRY_TYPE) { var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 1); goog.asserts.assert(args[0] instanceof ol.expr.Literal); var type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); features = this.geometryTypeIndex_[type]; } else if (name === ol.expr.functions.EXTENT) { var args = /** @type {ol.expr.Call} */ (opt_expr).getArgs(); goog.asserts.assert(args.length === 4); for (var i = 0; i < 4; ++i) { goog.asserts.assert(args[i] instanceof ol.expr.Literal); } var extent = [ /** @type {ol.expr.Literal} */ (args[0]).evaluate(), /** @type {ol.expr.Literal} */ (args[1]).evaluate(), /** @type {ol.expr.Literal} */ (args[2]).evaluate(), /** @type {ol.expr.Literal} */ (args[3]).evaluate() ]; features = this.rTree_.searchReturningObject(extent); } else { // not a call expression, check logical if (opt_expr instanceof ol.expr.Logical) { var op = /** @type {ol.expr.Logical} */ (opt_expr).getOperator(); if (op === ol.expr.LogicalOp.AND) { var expressions = [opt_expr.getLeft(), opt_expr.getRight()]; var expr, args, type, extent; for (var i = 0; i <= 1; ++i) { expr = expressions[i]; name = ol.expr.isLibCall(expr); if (name === ol.expr.functions.GEOMETRY_TYPE) { args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 1); goog.asserts.assert(args[0] instanceof ol.expr.Literal); type = /** @type {ol.expr.Literal } */ (args[0]).evaluate(); goog.asserts.assertString(type); } else if (name === ol.expr.functions.EXTENT) { args = /** @type {ol.expr.Call} */ (expr).getArgs(); goog.asserts.assert(args.length === 4); for (var j = 0; j < 4; ++j) { goog.asserts.assert(args[j] instanceof ol.expr.Literal); } extent = [[ /** @type {ol.expr.Literal} */ (args[0]).evaluate(), /** @type {ol.expr.Literal} */ (args[1]).evaluate() ], [ /** @type {ol.expr.Literal} */ (args[2]).evaluate(), /** @type {ol.expr.Literal} */ (args[3]).evaluate() ]]; } } if (type && extent) { features = this.getFeaturesObjectForExtent(extent, /** @type {ol.geom.GeometryType} */ (type)); } } } } if (!goog.isDef(features)) { // TODO: support fast lane for other filter types var candidates = this.idLookup_, feature; features = {}; for (i in candidates) { feature = candidates[i]; if (ol.expr.evaluateFeature(opt_expr, feature)) { features[i] = feature; } } } } return features; }; /** * Get all features whose bounding box intersects the provided extent. * * @param {ol.Extent} extent Bounding extent. * @param {ol.geom.GeometryType=} opt_type Optional geometry type. * @return {Object.} Features. */ ol.layer.FeatureCache.prototype.getFeaturesObjectForExtent = function(extent, opt_type) { var features; if (goog.isDef(opt_type) && goog.object.isEmpty(this.geometryTypeIndex_[opt_type])) { features = {}; } else { features = this.rTree_.searchReturningObject(extent, opt_type); } return features; }; /** * Get features by ids. * @param {Array.} ids Array of (internal) identifiers. * @return {Array.} Array of features. * @private */ ol.layer.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { var len = ids.length, features = new Array(len), i; for (i = 0; i < len; ++i) { features[i] = this.idLookup_[ids[i]]; } return features; }; /** * @param {string} uid Feature uid. * @return {ol.Feature|undefined} The feature with the provided uid if it is in * the cache, otherwise undefined. */ ol.layer.FeatureCache.prototype.getFeatureWithUid = function(uid) { return this.idLookup_[uid]; }; /** * Remove a feature from the cache. * @param {ol.Feature} feature Feature. */ ol.layer.FeatureCache.prototype.remove = function(feature) { var id = goog.getUid(feature).toString(), geometry = feature.getGeometry(); delete this.idLookup_[id]; // index by geometry type and bounding box if (!goog.isNull(geometry)) { var geometryType = geometry.getType(); delete this.geometryTypeIndex_[geometryType][id]; this.rTree_.remove(geometry.getBounds(), feature); } }; /** * @enum {string} */ ol.layer.VectorLayerEventType = { ADD: 'add', CHANGE: goog.events.EventType.CHANGE, REMOVE: 'remove', INTENTCHANGE: 'intentchange' }; /** * @typedef {{extent: (ol.Extent|undefined), * features: (Array.|undefined), * type: ol.layer.VectorLayerEventType}} */ ol.layer.VectorLayerEventObject; /** * @constructor * @extends {ol.layer.Layer} * @param {ol.layer.VectorLayerOptions} options Vector layer options. */ ol.layer.Vector = function(options) { goog.base(this, /** @type {ol.layer.LayerOptions} */ (options)); /** * @private * @type {ol.style.Style} */ this.style_ = goog.isDef(options.style) ? options.style : null; /** * @type {ol.layer.FeatureCache} * @private */ this.featureCache_ = new ol.layer.FeatureCache(); /** * @type {function(Array.):string} * @private */ this.transformFeatureInfo_ = goog.isDef(options.transformFeatureInfo) ? options.transformFeatureInfo : ol.layer.Vector.uidTransformFeatureInfo; /** * TODO: this means we need to know dimension at construction * @type {ol.geom.SharedVertices} * @private */ this.pointVertices_ = new ol.geom.SharedVertices(); /** * TODO: this means we need to know dimension at construction * @type {ol.geom.SharedVertices} * @private */ this.lineVertices_ = new ol.geom.SharedVertices(); /** * TODO: this means we need to know dimension at construction * @type {ol.geom.SharedVertices} * @private */ this.polygonVertices_ = new ol.geom.SharedVertices(); /** * True if this is a temporary layer. * @type {boolean} * @private */ this.temp_ = false; }; goog.inherits(ol.layer.Vector, ol.layer.Layer); /** * @param {Array.} features Array of features. */ ol.layer.Vector.prototype.addFeatures = function(features) { var extent = ol.extent.createEmpty(), feature, geometry; for (var i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; this.featureCache_.add(feature); geometry = feature.getGeometry(); if (!goog.isNull(geometry)) { ol.extent.extend(extent, geometry.getBounds()); } } this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ extent: extent, features: features, type: ol.layer.VectorLayerEventType.ADD })); }; /** * Remove all features from the layer. */ ol.layer.Vector.prototype.clear = function() { this.featureCache_.clear(); this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ type: ol.layer.VectorLayerEventType.CHANGE })); }; /** * @return {boolean} Whether this layer is temporary. */ ol.layer.Vector.prototype.getTemporary = function() { return this.temp_; }; /** * @return {ol.source.Vector} Source. */ ol.layer.Vector.prototype.getVectorSource = function() { return /** @type {ol.source.Vector} */ (this.getSource()); }; /** * @return {ol.style.Style} This layer's style. */ ol.layer.Vector.prototype.getStyle = function() { return this.style_; }; /** * Get all features whose bounding box intersects the provided extent. This * method is intended for being called by the renderer. When null is returned, * the renderer should not waste time rendering, and `opt_callback` is * usually a function that requests a renderFrame, which will be called as soon * as the data for `extent` is available. * * @param {ol.Extent} extent Bounding extent. * @param {ol.proj.Projection} projection Target projection. * @param {ol.geom.GeometryType=} opt_type Optional geometry type. * @param {Function=} opt_callback Callback to call when data is parsed. * @return {Object.} Features or null if source is loading * data for `extent`. */ ol.layer.Vector.prototype.getFeaturesObjectForExtent = function(extent, projection, opt_type, opt_callback) { var source = this.getSource(); return source.prepareFeatures(this, extent, projection, opt_callback) == ol.source.VectorLoadState.LOADING ? null : this.featureCache_.getFeaturesObjectForExtent(extent, opt_type); }; /** * @return {ol.geom.SharedVertices} Shared line vertices. */ ol.layer.Vector.prototype.getLineVertices = function() { return this.lineVertices_; }; /** * @return {ol.geom.SharedVertices} Shared point vertices. */ ol.layer.Vector.prototype.getPointVertices = function() { return this.pointVertices_; }; /** * @return {ol.geom.SharedVertices} Shared polygon vertices. */ ol.layer.Vector.prototype.getPolygonVertices = function() { return this.polygonVertices_; }; /** * @param {Object.} features Features. * @return {Array.} symbolizers for features. Each array in this array * contains 3 items: an array of features, the symbolizer literal, and * an array with optional additional data for each feature. */ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral = function(features) { var uniqueLiterals = {}, featuresBySymbolizer = [], style = this.style_, i, j, l, feature, symbolizers, literals, numLiterals, literal, uniqueLiteral, key, item; for (i in features) { feature = features[i]; // feature level symbolizers take precedence symbolizers = feature.getSymbolizers(); if (!goog.isNull(symbolizers)) { literals = ol.style.Style.createLiterals(symbolizers, feature); } else { // layer style second if (goog.isNull(style)) { style = ol.style.getDefault(); } literals = style.createLiterals(feature); } numLiterals = literals.length; for (j = 0; j < numLiterals; ++j) { literal = literals[j]; for (l in uniqueLiterals) { uniqueLiteral = featuresBySymbolizer[uniqueLiterals[l]][1]; if (literal.equals(uniqueLiteral)) { literal = uniqueLiteral; break; } } key = goog.getUid(literal); if (!goog.object.containsKey(uniqueLiterals, key)) { uniqueLiterals[key] = featuresBySymbolizer.length; featuresBySymbolizer.push([ /** @type {Array.} */ ([]), /** @type {ol.style.Literal} */ (literal), /** @type {Array} */ ([]) ]); } item = featuresBySymbolizer[uniqueLiterals[key]]; item[0].push(feature); if (literal instanceof ol.style.TextLiteral) { item[2].push(literals[j].text); } } } return featuresBySymbolizer; }; /** * @param {string|number} uid Feature uid. * @return {ol.Feature|undefined} The feature with the provided uid if it is on * the layer, otherwise undefined. */ ol.layer.Vector.prototype.getFeatureWithUid = function(uid) { return this.featureCache_.getFeatureWithUid(/** @type {string} */ (uid)); }; /** * @param {Object|Element|Document|string} data Feature data. * @param {ol.parser.Parser} parser Feature parser. * @param {ol.proj.Projection} projection This sucks. The layer should be a * view in one projection. */ ol.layer.Vector.prototype.parseFeatures = function(data, parser, projection) { var lookup = {}; lookup[ol.geom.GeometryType.POINT] = this.pointVertices_; lookup[ol.geom.GeometryType.LINESTRING] = this.lineVertices_; lookup[ol.geom.GeometryType.POLYGON] = this.polygonVertices_; lookup[ol.geom.GeometryType.MULTIPOINT] = this.pointVertices_; lookup[ol.geom.GeometryType.MULTILINESTRING] = this.lineVertices_; lookup[ol.geom.GeometryType.MULTIPOLYGON] = this.polygonVertices_; var callback = function(feature, type) { return lookup[type]; }; var addFeatures = function(data) { var features = data.features; var sourceProjection = this.getSource().getProjection(); if (goog.isNull(sourceProjection)) { sourceProjection = data.metadata.projection; } var transform = ol.proj.getTransform(sourceProjection, projection); transform( this.pointVertices_.coordinates, this.pointVertices_.coordinates, this.pointVertices_.getDimension()); transform( this.lineVertices_.coordinates, this.lineVertices_.coordinates, this.lineVertices_.getDimension()); transform( this.polygonVertices_.coordinates, this.polygonVertices_.coordinates, this.polygonVertices_.getDimension()); this.addFeatures(features); }; var options = {callback: callback}, result; if (goog.isString(data)) { if (goog.isFunction(parser.readFeaturesFromStringAsync)) { parser.readFeaturesFromStringAsync(data, goog.bind(addFeatures, this), options); } else { goog.asserts.assert( goog.isFunction(parser.readFeaturesFromString), 'Expected parser with a readFeaturesFromString method.'); result = parser.readFeaturesFromString(data, options); addFeatures.call(this, result); } } else if (goog.isObject(data)) { if (goog.isFunction(parser.readFeaturesFromObjectAsync)) { parser.readFeaturesFromObjectAsync(data, goog.bind(addFeatures, this), options); } else { goog.asserts.assert( goog.isFunction(parser.readFeaturesFromObject), 'Expected parser with a readFeaturesFromObject method.'); result = parser.readFeaturesFromObject(data, options); addFeatures.call(this, result); } } else { // TODO: parse more data types throw new Error('Data type not supported: ' + data); } }; /** * @return {function(Array.):string} Feature info function. */ ol.layer.Vector.prototype.getTransformFeatureInfo = function() { return this.transformFeatureInfo_; }; /** * Remove features from the layer. * @param {Array.} features Features to remove. */ ol.layer.Vector.prototype.removeFeatures = function(features) { var extent = ol.extent.createEmpty(), feature, geometry; for (var i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; this.featureCache_.remove(feature); geometry = feature.getGeometry(); if (!goog.isNull(geometry)) { ol.extent.extend(extent, geometry.getBounds()); } } this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ extent: extent, features: features, type: ol.layer.VectorLayerEventType.REMOVE })); }; /** * Changes the renderIntent for an array of features. * @param {string} renderIntent Render intent. * @param {Array.=} opt_features Features to change the renderIntent * for. If not provided, all features will be changed. */ ol.layer.Vector.prototype.setRenderIntent = function(renderIntent, opt_features) { var features = goog.isDef(opt_features) ? opt_features : goog.object.getValues(this.featureCache_.getFeaturesObject()); var extent = ol.extent.createEmpty(), feature, geometry; for (var i = features.length - 1; i >= 0; --i) { feature = features[i]; feature.renderIntent = renderIntent; geometry = feature.getGeometry(); if (!goog.isNull(geometry)) { ol.extent.extend(extent, geometry.getBounds()); } } this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ extent: extent, features: features, type: ol.layer.VectorLayerEventType.INTENTCHANGE })); }; /** * @param {boolean} temp Whether this layer is temporary. */ ol.layer.Vector.prototype.setTemporary = function(temp) { this.temp_ = temp; }; /** * @param {Array.} features Features. * @return {string} Feature info. */ ol.layer.Vector.uidTransformFeatureInfo = function(features) { var uids = goog.array.map(features, function(feature) { return goog.getUid(feature); }); return uids.join(', '); };