diff --git a/examples/select-features.html b/examples/select-features.html
new file mode 100644
index 0000000000..2ff31fb9dd
--- /dev/null
+++ b/examples/select-features.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+ Select features example
+
+
+
+
+
+
+
+
+
+
+
+
+
Select features example
+
Example of using the Select interaction. Select features by clicking polygons. Hold the Shift-key to add to the selection.
+
+
select, vector
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/select-features.js b/examples/select-features.js
new file mode 100644
index 0000000000..ab54750dbd
--- /dev/null
+++ b/examples/select-features.js
@@ -0,0 +1,63 @@
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.interaction.Select');
+goog.require('ol.interaction.defaults');
+goog.require('ol.layer.TileLayer');
+goog.require('ol.layer.Vector');
+goog.require('ol.parser.ogc.GML_v3');
+goog.require('ol.source.MapQuestOpenAerial');
+goog.require('ol.source.Vector');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Rule');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+
+var raster = new ol.layer.TileLayer({
+ source: new ol.source.MapQuestOpenAerial()
+});
+
+var vector = new ol.layer.Vector({
+ id: 'vector',
+ source: new ol.source.Vector({
+ parser: new ol.parser.ogc.GML_v3(),
+ url: 'data/gml/topp-states-wfs.xml'
+ }),
+ style: new ol.style.Style({
+ rules: [
+ new ol.style.Rule({
+ filter: 'renderIntent("selected")',
+ symbolizers: [
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.5
+ })
+ ]
+ })
+ ],
+ symbolizers: [
+ new ol.style.Fill({
+ color: '#ffffff',
+ opacity: 0.25
+ }),
+ new ol.style.Stroke({
+ color: '#6666ff'
+ })
+ ]
+ })
+});
+
+var selectInteraction = new ol.interaction.Select({
+ layerFilter: function(layer) { return layer.get('id') == 'vector'; }
+});
+
+var map = new ol.Map({
+ interactions: ol.interaction.defaults().extend([selectInteraction]),
+ layers: [raster, vector],
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ center: [-11000000, 4600000],
+ zoom: 4
+ })
+});
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 973fd47471..8b94acbd1f 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -244,7 +244,7 @@
/**
* @typedef {Object} ol.interaction.DragPanOptions
* @property {ol.Kinetic|undefined} kinetic Kinetic.
- * @property {ol.interaction.ConditionType|undefined} condition Conditon.
+ * @property {ol.interaction.ConditionType|undefined} condition Condition.
*/
/**
@@ -295,6 +295,13 @@
* @property {number|undefined} delta Delta.
*/
+/**
+ * @typedef {Object} ol.interaction.SelectOptions
+ * @property {ol.interaction.ConditionType|undefined} condition Condition.
+ * @property {undefined|function(ol.layer.Layer):boolean} layerFilter Filter
+ * function to restrict selection to a subset of layers.
+ */
+
/**
* @typedef {Object} ol.interaction.TouchPanOptions
* @property {ol.Kinetic|undefined} kinetic Kinetic.
diff --git a/src/ol/expr/expression.js b/src/ol/expr/expression.js
index dda7330ea6..cdbe012936 100644
--- a/src/ol/expr/expression.js
+++ b/src/ol/expr/expression.js
@@ -99,6 +99,7 @@ ol.expr.functions = {
EXTENT: 'extent',
FID: 'fid',
GEOMETRY_TYPE: 'geometryType',
+ RENDER_INTENT: 'renderIntent',
INTERSECTS: 'intersects',
CONTAINS: 'contains',
DWITHIN: 'dwithin',
@@ -252,6 +253,17 @@ ol.expr.lib[ol.expr.functions.GEOMETRY_TYPE] = function(type) {
};
+/**
+ * Determine if a feature's renderIntent matches the given one.
+ * @param {string} renderIntent Render intent.
+ * @return {boolean} The feature's renderIntent matches the given one.
+ * @this {ol.Feature}
+ */
+ol.expr.lib[ol.expr.functions.RENDER_INTENT] = function(renderIntent) {
+ return this.renderIntent == renderIntent;
+};
+
+
ol.expr.lib[ol.expr.functions.INTERSECTS] = function(geom, opt_projection,
opt_attribute) {
throw new Error('Spatial function not implemented: ' +
diff --git a/src/ol/feature.js b/src/ol/feature.js
index 24de9c40e3..ad4465cce4 100644
--- a/src/ol/feature.js
+++ b/src/ol/feature.js
@@ -2,6 +2,7 @@ goog.provide('ol.Feature');
goog.require('ol.Object');
goog.require('ol.geom.Geometry');
+goog.require('ol.layer.VectorLayerRenderIntent');
@@ -34,6 +35,12 @@ ol.Feature = function(opt_values) {
*/
this.geometryName_;
+ /**
+ * The render intent for this feature.
+ * @type {ol.layer.VectorLayerRenderIntent|string}
+ */
+ this.renderIntent = ol.layer.VectorLayerRenderIntent.DEFAULT;
+
/**
* @type {Array.}
* @private
@@ -109,7 +116,7 @@ ol.Feature.prototype.set = function(key, value) {
* Set the feature's commonly used identifier. This identifier is usually the
* unique id in the source store.
*
- * @param {string} featureId The feature's identifier.
+ * @param {string|undefined} featureId The feature's identifier.
*/
ol.Feature.prototype.setFeatureId = function(featureId) {
this.featureId_ = featureId;
diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js
index 964e6fa4ce..383c255fa1 100644
--- a/src/ol/geom/geometry.js
+++ b/src/ol/geom/geometry.js
@@ -27,6 +27,19 @@ ol.geom.Geometry = function() {
ol.geom.Geometry.prototype.dimension;
+/**
+ * Create a clone of this geometry. The clone will not be represented in any
+ * shared structure.
+ * @return {ol.geom.Geometry} The cloned geometry.
+ */
+ol.geom.Geometry.prototype.clone = function() {
+ var clone = new this.constructor(this.getCoordinates());
+ clone.bounds_ = this.bounds_;
+ clone.dimension = this.dimension;
+ return clone;
+};
+
+
/**
* Get the rectangular 2D envelope for this geoemtry.
* @return {ol.Extent} The bounding rectangular envelope.
diff --git a/src/ol/interaction/condition.js b/src/ol/interaction/condition.js
index 78719a8c89..70702ab5db 100644
--- a/src/ol/interaction/condition.js
+++ b/src/ol/interaction/condition.js
@@ -2,6 +2,7 @@ goog.provide('ol.interaction.ConditionType');
goog.provide('ol.interaction.condition');
goog.require('goog.dom.TagName');
+goog.require('goog.events.EventType');
goog.require('goog.functions');
@@ -42,6 +43,15 @@ ol.interaction.condition.altShiftKeysOnly = function(browserEvent) {
ol.interaction.condition.always = goog.functions.TRUE;
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @return {boolean} True only the event is a click event.
+ */
+ol.interaction.condition.clickOnly = function(browserEvent) {
+ return browserEvent.type == goog.events.EventType.CLICK;
+};
+
+
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @return {boolean} True if only the no modifier keys are pressed.
diff --git a/src/ol/interaction/interaction.js b/src/ol/interaction/interaction.js
index 4fe73093b4..853b3c68f2 100644
--- a/src/ol/interaction/interaction.js
+++ b/src/ol/interaction/interaction.js
@@ -2,6 +2,7 @@
goog.provide('ol.interaction.Interaction');
+goog.require('goog.Disposable');
goog.require('ol.MapBrowserEvent');
goog.require('ol.animation.pan');
goog.require('ol.animation.rotate');
@@ -12,9 +13,12 @@ goog.require('ol.easing');
/**
* @constructor
+ * @extends {goog.Disposable}
*/
ol.interaction.Interaction = function() {
+ goog.base(this);
};
+goog.inherits(ol.interaction.Interaction, goog.Disposable);
/**
diff --git a/src/ol/interaction/selectinteraction.exports b/src/ol/interaction/selectinteraction.exports
new file mode 100644
index 0000000000..a5cd323f10
--- /dev/null
+++ b/src/ol/interaction/selectinteraction.exports
@@ -0,0 +1 @@
+@exportClass ol.interaction.Select ol.interaction.SelectOptions
diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js
new file mode 100644
index 0000000000..45fa13aef0
--- /dev/null
+++ b/src/ol/interaction/selectinteraction.js
@@ -0,0 +1,172 @@
+goog.provide('ol.interaction.Select');
+
+goog.require('goog.array');
+goog.require('ol.Feature');
+goog.require('ol.interaction.ConditionType');
+goog.require('ol.interaction.Interaction');
+goog.require('ol.interaction.condition');
+goog.require('ol.layer.Vector');
+goog.require('ol.layer.VectorLayerRenderIntent');
+goog.require('ol.source.Vector');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.interaction.Interaction}
+ * @param {ol.interaction.SelectOptions=} opt_options Options.
+ */
+ol.interaction.Select = function(opt_options) {
+ var options = goog.isDef(opt_options) ? opt_options : {};
+
+ /**
+ * @private
+ * @type {ol.interaction.ConditionType}
+ */
+ this.condition_ = goog.isDef(options.condition) ?
+ options.condition : ol.interaction.condition.clickOnly;
+
+ /**
+ * Mapping between original features and cloned features on selection layers.
+ * @type {Object.<*,Object.<*,ol.Feature>>}
+ * @private
+ */
+ this.featureMap_ = {};
+
+ /**
+ * Mapping between original layers and selection layers, by map.
+ * @type {Object.<*,{map:ol.Map,layers:Object.<*,ol.layer.Vector>}>}
+ * @protected
+ */
+ this.selectionLayers = {};
+
+ /**
+ * @type {null|function(ol.layer.Layer):boolean}
+ * @private
+ */
+ this.layerFilter_ = goog.isDef(options.layerFilter) ?
+ options.layerFilter : null;
+
+ goog.base(this);
+};
+goog.inherits(ol.interaction.Select, ol.interaction.Interaction);
+
+
+/**
+ * @inheritDoc
+ */
+ol.interaction.Select.prototype.disposeInternal = function() {
+ for (var m in this.selectionLayers) {
+ var selectionLayers = this.selectionLayers[m].layers;
+ var map = this.selectionLayers[m].map;
+ for (var l in selectionLayers) {
+ map.removeLayer(selectionLayers[l]);
+ }
+ }
+ goog.base(this, 'disposeInternal');
+};
+
+
+/**
+ * @inheritDoc.
+ */
+ol.interaction.Select.prototype.handleMapBrowserEvent = function(evt) {
+ var browserEvent = evt.browserEvent;
+ if (this.condition_(browserEvent)) {
+ var map = evt.map;
+ var layers = map.getLayerGroup().getLayersArray();
+ if (!goog.isNull(this.layerFilter_)) {
+ layers = goog.array.filter(layers, this.layerFilter_);
+ }
+ var clear = !ol.interaction.condition.shiftKeyOnly(browserEvent);
+
+ var that = this;
+ var select = function(featuresByLayer) {
+ that.select(map, featuresByLayer, layers, clear);
+ };
+
+ map.getFeatures({
+ layers: layers,
+ pixel: evt.getPixel(),
+ success: select
+ });
+ }
+ // TODO: Implement box selection
+ return true;
+};
+
+
+/**
+ * @param {ol.Map} map The map where the selction event originated.
+ * @param {Array.>} featuresByLayer Features by layer.
+ * @param {Array.} layers The queried layers.
+ * @param {boolean} clear Whether the current layer content should be cleared.
+ */
+ol.interaction.Select.prototype.select =
+ function(map, featuresByLayer, layers, clear) {
+ var mapId = goog.getUid(map);
+ for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) {
+ var layer = layers[i];
+ var layerId = goog.getUid(layer);
+ if (!(mapId in this.selectionLayers)) {
+ this.selectionLayers[mapId] = {map: map, layers: {}};
+ }
+ var selectionLayer = this.selectionLayers[mapId].layers[layerId];
+ if (!goog.isDef(selectionLayer)) {
+ selectionLayer = new ol.layer.Vector({
+ source: new ol.source.Vector({parser: null}),
+ style: goog.isFunction(layer.getStyle) ? layer.getStyle() : null
+ });
+ selectionLayer.setTemporary(true);
+ map.addLayer(selectionLayer);
+ this.selectionLayers[mapId].layers[layerId] = selectionLayer;
+ this.featureMap_[layerId] = {};
+ }
+
+ var features = featuresByLayer[i];
+ var numFeatures = features.length;
+ var selectedFeatures = [];
+ var featuresToAdd = [];
+ var unselectedFeatures = [];
+ var featuresToRemove = [];
+ var featureMap = this.featureMap_[layerId];
+ var oldFeatureMap = featureMap;
+ if (clear) {
+ for (var f in featureMap) {
+ unselectedFeatures.push(layer.getFeatureWithUid(f));
+ featuresToRemove.push(featureMap[f]);
+ }
+ featureMap = {};
+ this.featureMap_[layerId] = featureMap;
+ }
+ for (var j = 0; j < numFeatures; ++j) {
+ var feature = features[j];
+ var featureId = goog.getUid(feature);
+ var clone = featureMap[featureId];
+ if (clone) {
+ // TODO: make toggle configurable
+ unselectedFeatures.push(feature);
+ featuresToRemove.push(clone);
+ delete featureMap[featureId];
+ } else if (!(featureId in oldFeatureMap)) {
+ clone = new ol.Feature(feature.getAttributes());
+ clone.setGeometry(feature.getGeometry().clone());
+ clone.setFeatureId(feature.getFeatureId());
+ clone.setSymbolizers(feature.getSymbolizers());
+ clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED;
+ featureMap[featureId] = clone;
+ selectedFeatures.push(feature);
+ featuresToAdd.push(clone);
+ }
+ }
+ if (goog.isFunction(layer.setRenderIntent)) {
+ layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.HIDDEN,
+ selectedFeatures);
+ layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT,
+ unselectedFeatures);
+ }
+ selectionLayer.removeFeatures(featuresToRemove);
+ selectionLayer.addFeatures(featuresToAdd);
+ // TODO: Dispatch an event with selectedFeatures and unselectedFeatures
+ }
+};
diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js
index 397d6768e1..31f1cfb5cd 100644
--- a/src/ol/layer/vectorlayer.js
+++ b/src/ol/layer/vectorlayer.js
@@ -3,6 +3,7 @@ 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');
@@ -201,6 +202,16 @@ ol.layer.FeatureCache.prototype.getFeaturesByIds_ = function(ids) {
};
+/**
+ * @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.
@@ -225,7 +236,9 @@ ol.layer.FeatureCache.prototype.remove = function(feature) {
*/
ol.layer.VectorLayerEventType = {
ADD: 'add',
- REMOVE: 'remove'
+ CHANGE: goog.events.EventType.CHANGE,
+ REMOVE: 'remove',
+ INTENTCHANGE: 'intentchange'
};
@@ -287,6 +300,13 @@ ol.layer.Vector = function(options) {
*/
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);
@@ -313,6 +333,25 @@ ol.layer.Vector.prototype.addFeatures = function(features) {
};
+/**
+ * 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.
*/
@@ -321,6 +360,14 @@ ol.layer.Vector.prototype.getVectorSource = function() {
};
+/**
+ * @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,
@@ -426,6 +473,16 @@ ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral =
};
+/**
+ * @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.
@@ -532,12 +589,48 @@ ol.layer.Vector.prototype.removeFeatures = function(features) {
};
+/**
+ * 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 featureIds = goog.array.map(features,
+ var uids = goog.array.map(features,
function(feature) { return goog.getUid(feature); });
- return featureIds.join(', ');
+ return uids.join(', ');
};
diff --git a/src/ol/layer/vectorlayerrenderintent.js b/src/ol/layer/vectorlayerrenderintent.js
new file mode 100644
index 0000000000..757fef5aba
--- /dev/null
+++ b/src/ol/layer/vectorlayerrenderintent.js
@@ -0,0 +1,11 @@
+goog.provide('ol.layer.VectorLayerRenderIntent');
+
+
+/**
+ * @enum {string}
+ */
+ol.layer.VectorLayerRenderIntent = {
+ DEFAULT: 'default',
+ HIDDEN: 'hidden',
+ SELECTED: 'selected'
+};
diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
index 8d704eb2c0..eeb1b3cad7 100644
--- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
@@ -88,7 +88,9 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
ol.renderer.canvas.VectorLayer.TILECACHE_SIZE);
goog.events.listen(layer, [
ol.layer.VectorLayerEventType.ADD,
- ol.layer.VectorLayerEventType.REMOVE
+ ol.layer.VectorLayerEventType.CHANGE,
+ ol.layer.VectorLayerEventType.REMOVE,
+ ol.layer.VectorLayerEventType.INTENTCHANGE
],
this.handleLayerChange_, false, this);
diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js
index 354a91803e..1e4543f446 100644
--- a/src/ol/renderer/canvas/canvasvectorrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorrenderer.js
@@ -17,6 +17,7 @@ goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
+goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.style.IconLiteral');
goog.require('ol.style.LineLiteral');
goog.require('ol.style.Literal');
@@ -176,6 +177,9 @@ ol.renderer.canvas.VectorRenderer.prototype.renderLineStringFeatures_ =
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
+ if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) {
+ continue;
+ }
id = goog.getUid(feature);
currentSize = goog.isDef(this.symbolSizes_[id]) ?
this.symbolSizes_[id] : [0];
@@ -253,6 +257,9 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
context.globalAlpha = alpha;
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
+ if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) {
+ continue;
+ }
id = goog.getUid(feature);
size = this.symbolSizes_[id];
this.symbolSizes_[id] = goog.isDef(size) ?
@@ -296,7 +303,7 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
ol.renderer.canvas.VectorRenderer.prototype.renderText_ =
function(features, text, texts) {
var context = this.context_,
- vecs, vec;
+ feature, vecs, vec;
if (context.fillStyle !== text.color) {
context.fillStyle = text.color;
@@ -309,8 +316,12 @@ ol.renderer.canvas.VectorRenderer.prototype.renderText_ =
context.textBaseline = 'middle';
for (var i = 0, ii = features.length; i < ii; ++i) {
+ feature = features[i];
+ if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) {
+ continue;
+ }
vecs = ol.renderer.canvas.VectorRenderer.getLabelVectors(
- features[i].getGeometry());
+ feature.getGeometry());
for (var j = 0, jj = vecs.length; j < jj; ++j) {
vec = vecs[j];
goog.vec.Mat4.multVec3(this.transform_, vec, vec);
@@ -336,7 +347,7 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPolygonFeatures_ =
fillOpacity = symbolizer.fillOpacity,
globalAlpha,
i, ii, geometry, components, j, jj, poly,
- rings, numRings, ring, dim, k, kk, vec;
+ rings, numRings, ring, dim, k, kk, vec, feature;
if (strokeColor) {
context.strokeStyle = strokeColor;
@@ -359,7 +370,11 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPolygonFeatures_ =
*/
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
- geometry = features[i].getGeometry();
+ feature = features[i];
+ if (feature.renderIntent === ol.layer.VectorLayerRenderIntent.HIDDEN) {
+ continue;
+ }
+ geometry = feature.getGeometry();
if (geometry instanceof ol.geom.Polygon) {
components = [geometry];
} else {
diff --git a/src/ol/style/fillsymbolizer.js b/src/ol/style/fillsymbolizer.js
index b5e079117e..c0acfa6161 100644
--- a/src/ol/style/fillsymbolizer.js
+++ b/src/ol/style/fillsymbolizer.js
@@ -116,10 +116,20 @@ ol.style.Fill.prototype.setOpacity = function(opacity) {
/**
- * @typedef {{fillColor: (string),
- * fillOpacity: (number)}}
+ * @typedef {{color: (string),
+ * opacity: (number)}}
*/
ol.style.FillDefaults = {
color: '#ffffff',
opacity: 0.4
};
+
+
+/**
+ * @typedef {{color: (string),
+ * opacity: (number)}}
+ */
+ol.style.FillDefaultsSelect = {
+ color: '#ffffff',
+ opacity: 0.7
+};
diff --git a/src/ol/style/strokesymbolizer.js b/src/ol/style/strokesymbolizer.js
index f1f0b389aa..339bae811b 100644
--- a/src/ol/style/strokesymbolizer.js
+++ b/src/ol/style/strokesymbolizer.js
@@ -158,12 +158,24 @@ ol.style.Stroke.prototype.setWidth = function(width) {
/**
- * @typedef {{strokeColor: (string),
- * strokeOpacity: (number),
- * strokeWidth: (number)}}
+ * @typedef {{color: (string),
+ * opacity: (number),
+ * width: (number)}}
*/
ol.style.StrokeDefaults = {
color: '#696969',
opacity: 0.75,
width: 1.5
};
+
+
+/**
+ * @typedef {{color: (string),
+ * opacity: (number),
+ * width: (number)}}
+ */
+ol.style.StrokeDefaultsSelect = {
+ color: '#696969',
+ opacity: 0.9,
+ width: 2.0
+};
diff --git a/src/ol/style/style.js b/src/ol/style/style.js
index e098cc75cf..0a11949201 100644
--- a/src/ol/style/style.js
+++ b/src/ol/style/style.js
@@ -67,6 +67,19 @@ ol.style.Style.prototype.createLiterals = function(feature) {
* @type {ol.style.Style}
*/
ol.style.Style.defaults = new ol.style.Style({
+ rules: [
+ new ol.style.Rule({
+ filter: 'renderIntent("select")',
+ symbolizers: [
+ new ol.style.Shape({
+ fill: new ol.style.Fill(ol.style.FillDefaultsSelect),
+ stroke: new ol.style.Stroke(ol.style.StrokeDefaultsSelect)
+ }),
+ new ol.style.Fill(ol.style.FillDefaultsSelect),
+ new ol.style.Stroke(ol.style.StrokeDefaultsSelect)
+ ]
+ })
+ ],
symbolizers: [
new ol.style.Shape({
fill: new ol.style.Fill(),
diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js
index 9b396897e4..22927fa0ef 100644
--- a/test/spec/ol/expr/expression.test.js
+++ b/test/spec/ol/expr/expression.test.js
@@ -872,6 +872,24 @@ describe('ol.expr.lib', function() {
});
+ describe('renderIntent()', function() {
+
+ var feature = new ol.Feature();
+ feature.renderIntent = 'foo';
+
+ var isFoo = parse('renderIntent("foo")');
+ var isBar = parse('renderIntent("bar")');
+
+ it('True when renderIntent matches', function() {
+ expect(evaluate(isFoo, feature), true);
+ });
+
+ it('False when renderIntent does not match', function() {
+ expect(evaluate(isBar, feature), false);
+ });
+
+ });
+
});
describe('ol.expr.register()', function() {
diff --git a/test/spec/ol/geom/geometrycollection.test.js b/test/spec/ol/geom/geometrycollection.test.js
index d9c1616c33..622cd48943 100644
--- a/test/spec/ol/geom/geometrycollection.test.js
+++ b/test/spec/ol/geom/geometrycollection.test.js
@@ -55,6 +55,23 @@ describe('ol.geom.GeometryCollection', function() {
});
+ 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 components = clone.components;
+ expect(components[0]).to.eql([10, 20]);
+ expect(components[1]).to.eql([[10, 20], [30, 40]]);
+ expect(components[2]).to.eql([outer, inner1, inner2]);
+ });
+
+ });
+
describe('#getBounds()', function() {
it('returns the bounding extent', function() {
diff --git a/test/spec/ol/interaction/selectinteraction.test.js b/test/spec/ol/interaction/selectinteraction.test.js
new file mode 100644
index 0000000000..12de3a07a6
--- /dev/null
+++ b/test/spec/ol/interaction/selectinteraction.test.js
@@ -0,0 +1,84 @@
+goog.provide('ol.test.interaction.Select');
+
+describe('ol.interaction.Select', function() {
+ var map, target, select, vector, features;
+
+ beforeEach(function() {
+ target = document.createElement('div');
+ target.style.width = '256px';
+ target.style.height = '256px';
+ document.body.appendChild(target);
+ map = new ol.Map({
+ target: target
+ });
+ features = ol.parser.GeoJSON.read(JSON.stringify({
+ 'type': 'FeatureCollection',
+ 'features': [{
+ 'type': 'Feature',
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [-1, 1]
+ }
+ }, {
+ 'type': 'Feature',
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [1, -1]
+ }
+ }]
+ }));
+ vector = new ol.layer.Vector({source: new ol.source.Vector({})});
+ vector.addFeatures(features);
+ select = new ol.interaction.Select({
+ layerFilter: function(layer) { return layer === vector; }
+ });
+ map.getInteractions().push(select);
+ });
+
+ afterEach(function() {
+ goog.dispose(select);
+ goog.dispose(map);
+ document.body.removeChild(target);
+ select = null;
+ map = null;
+ target = null;
+ });
+
+ describe('#select', function() {
+
+ it('toggles selection of features', function() {
+ select.select(map, [features], [vector]);
+ var layer = select.selectionLayers[goog.getUid(map)]
+ .layers[goog.getUid(vector)];
+ expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2);
+ select.select(map, [features], [vector]);
+ expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0);
+ });
+
+ it('can append features to an existing selection', function() {
+ select.select(map, [[features[0]]], [vector]);
+ select.select(map, [[features[1]]], [vector]);
+ var layer = select.selectionLayers[goog.getUid(map)]
+ .layers[goog.getUid(vector)];
+ expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2);
+ });
+
+ it('can clear a selection before selecting new features', function() {
+ select.select(map, [[features[0]]], [vector], true);
+ select.select(map, [[features[1]]], [vector], true);
+ var layer = select.selectionLayers[goog.getUid(map)]
+ .layers[goog.getUid(vector)];
+ expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1);
+ });
+
+ });
+
+});
+
+goog.require('goog.dispose');
+goog.require('goog.object');
+goog.require('ol.Map');
+goog.require('ol.interaction.Select');
+goog.require('ol.layer.Vector');
+goog.require('ol.parser.GeoJSON');
+goog.require('ol.source.Vector');