goog.provide('ol.FeatureOverlay'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.Collection'); goog.require('ol.CollectionEventType'); goog.require('ol.Feature'); goog.require('ol.render.EventType'); goog.require('ol.renderer.vector'); goog.require('ol.style.Style'); /** * @classdesc * A mechanism for changing the style of a small number of features on a * temporary basis, for example highlighting. This is necessary with the Canvas * renderer, where, unlike in SVG, features cannot be individually referenced. * See examples/vector-layers for an example: create a FeatureOverlay with a * different style, copy the feature(s) you want rendered in this different * style into it, and then remove them again when you're finished. * * @constructor * @param {olx.FeatureOverlayOptions=} opt_options Options. * @api */ ol.FeatureOverlay = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; /** * @private * @type {ol.Collection.} */ this.features_ = null; /** * @private * @type {Array.} */ this.featuresListenerKeys_ = null; /** * @private * @type {Object.} */ this.featureChangeListenerKeys_ = null; /** * @private * @type {ol.Map} */ this.map_ = null; /** * @private * @type {goog.events.Key} */ this.postComposeListenerKey_ = null; /** * @private * @type {ol.style.Style|Array.|ol.style.StyleFunction} */ this.style_ = null; /** * @private * @type {ol.style.StyleFunction|undefined} */ this.styleFunction_ = undefined; this.setStyle(goog.isDef(options.style) ? options.style : ol.style.defaultStyleFunction); if (goog.isDef(options.features)) { if (goog.isArray(options.features)) { this.setFeatures(new ol.Collection(options.features.slice())); } else { goog.asserts.assertInstanceof(options.features, ol.Collection); this.setFeatures(options.features); } } else { this.setFeatures(new ol.Collection()); } if (goog.isDef(options.map)) { this.setMap(options.map); } }; /** * @param {ol.Feature} feature Feature. * @api */ ol.FeatureOverlay.prototype.addFeature = function(feature) { this.features_.push(feature); }; /** * @return {ol.Collection.} Features collection. * @api */ ol.FeatureOverlay.prototype.getFeatures = function() { return this.features_; }; /** * @return {?ol.Map} The map with which this feature overlay is associated. * @api */ ol.FeatureOverlay.prototype.getMap = function() { return this.map_; }; /** * @private */ ol.FeatureOverlay.prototype.handleFeatureChange_ = function() { this.render_(); }; /** * @private * @param {ol.CollectionEvent} collectionEvent Collection event. */ ol.FeatureOverlay.prototype.handleFeaturesAdd_ = function(collectionEvent) { goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_)); var feature = /** @type {ol.Feature} */ (collectionEvent.element); this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = goog.events.listen(feature, goog.events.EventType.CHANGE, this.handleFeatureChange_, false, this); this.render_(); }; /** * @private * @param {ol.CollectionEvent} collectionEvent Collection event. */ ol.FeatureOverlay.prototype.handleFeaturesRemove_ = function(collectionEvent) { goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_)); var feature = /** @type {ol.Feature} */ (collectionEvent.element); var key = goog.getUid(feature).toString(); goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]); delete this.featureChangeListenerKeys_[key]; this.render_(); }; /** * Handle changes in image style state. * @param {goog.events.Event} event Image style change event. * @private */ ol.FeatureOverlay.prototype.handleImageChange_ = function(event) { this.render_(); }; /** * @param {ol.render.Event} event Event. * @private */ ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) { if (goog.isNull(this.features_)) { return; } var styleFunction = this.styleFunction_; if (!goog.isDef(styleFunction)) { styleFunction = ol.style.defaultStyleFunction; } var replayGroup = /** @type {ol.render.IReplayGroup} */ (event.replayGroup); goog.asserts.assert(goog.isDef(replayGroup)); var frameState = event.frameState; var pixelRatio = frameState.pixelRatio; var resolution = frameState.viewState.resolution; var squaredTolerance = ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio); var i, ii, styles, featureStyleFunction; this.features_.forEach(function(feature) { featureStyleFunction = feature.getStyleFunction(); styles = goog.isDef(featureStyleFunction) ? featureStyleFunction.call(feature, resolution) : styleFunction(feature, resolution); if (!goog.isDefAndNotNull(styles)) { return; } ii = styles.length; for (i = 0; i < ii; ++i) { ol.renderer.vector.renderFeature(replayGroup, feature, styles[i], squaredTolerance, this.handleImageChange_, this); } }, this); }; /** * @param {ol.Feature} feature Feature. * @api */ ol.FeatureOverlay.prototype.removeFeature = function(feature) { this.features_.remove(feature); }; /** * @private */ ol.FeatureOverlay.prototype.render_ = function() { if (!goog.isNull(this.map_)) { this.map_.render(); } }; /** * @param {ol.Collection.} features Features collection. * @api */ ol.FeatureOverlay.prototype.setFeatures = function(features) { if (!goog.isNull(this.featuresListenerKeys_)) { goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey); this.featuresListenerKeys_ = null; } if (!goog.isNull(this.featureChangeListenerKeys_)) { goog.array.forEach( goog.object.getValues(this.featureChangeListenerKeys_), goog.events.unlistenByKey); this.featureChangeListenerKeys_ = null; } this.features_ = features; if (!goog.isNull(features)) { this.featuresListenerKeys_ = [ goog.events.listen(features, ol.CollectionEventType.ADD, this.handleFeaturesAdd_, false, this), goog.events.listen(features, ol.CollectionEventType.REMOVE, this.handleFeaturesRemove_, false, this) ]; this.featureChangeListenerKeys_ = {}; features.forEach(function(feature) { this.featureChangeListenerKeys_[goog.getUid(feature).toString()] = goog.events.listen(feature, goog.events.EventType.CHANGE, this.handleFeatureChange_, false, this); }, this); } this.render_(); }; /** * @param {ol.Map} map Map. * @api */ ol.FeatureOverlay.prototype.setMap = function(map) { if (!goog.isNull(this.postComposeListenerKey_)) { goog.events.unlistenByKey(this.postComposeListenerKey_); this.postComposeListenerKey_ = null; } this.render_(); this.map_ = map; if (!goog.isNull(map)) { this.postComposeListenerKey_ = goog.events.listen( map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false, this); map.render(); } }; /** * Set the style for features. This can be a single style object, an array * of styles, or a function that takes a feature and resolution and returns * an array of styles. * @param {ol.style.Style|Array.|ol.style.StyleFunction} style * Overlay style. * @api */ ol.FeatureOverlay.prototype.setStyle = function(style) { this.style_ = style; this.styleFunction_ = ol.style.createStyleFunction(style); this.render_(); }; /** * Get the style for features. This returns whatever was passed to the `style` * option at construction or to the `setStyle` method. * @return {ol.style.Style|Array.|ol.style.StyleFunction} * Overlay style. * @api */ ol.FeatureOverlay.prototype.getStyle = function() { return this.style_; }; /** * Get the style function. * @return {ol.style.StyleFunction|undefined} Style function. * @api */ ol.FeatureOverlay.prototype.getStyleFunction = function() { return this.styleFunction_; };