From e2526621d1343ba2c71d8df844ede314de3c2314 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 7 Aug 2013 22:41:02 +0200 Subject: [PATCH 01/39] Select example and skeleton with event listeners --- examples/select-features.html | 56 +++++++++++++++++ examples/select-features.js | 48 +++++++++++++++ src/objectliterals.jsdoc | 9 +++ src/ol/control/selectcontrol.js | 106 ++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 examples/select-features.html create mode 100644 examples/select-features.js create mode 100644 src/ol/control/selectcontrol.js diff --git a/examples/select-features.html b/examples/select-features.html new file mode 100644 index 0000000000..8e34f3422b --- /dev/null +++ b/examples/select-features.html @@ -0,0 +1,56 @@ + + + + + + + + + + + Select featuers example + + + + + +
+ +
+
+
+
+
+ +
+ +
+

Select features example

+

Example of using the Select control.

+
+

See the select-features.js source to see how this is done.

+
+
SelectFeature, vector
+
+ +
+ +
+ + + + + + diff --git a/examples/select-features.js b/examples/select-features.js new file mode 100644 index 0000000000..cb13f361e6 --- /dev/null +++ b/examples/select-features.js @@ -0,0 +1,48 @@ +goog.require('ol.Map'); +goog.require('ol.RendererHint'); +goog.require('ol.View2D'); +goog.require('ol.control.Select'); +goog.require('ol.control.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.Polygon'); +goog.require('ol.style.Rule'); +goog.require('ol.style.Style'); + +var raster = new ol.layer.TileLayer({ + source: new ol.source.MapQuestOpenAerial() +}); + +var vector = new ol.layer.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({ + symbolizers: [ + new ol.style.Polygon({ + strokeColor: '#bada55' + }) + ] + }) + ]}) +}); + +var selectControl = new ol.control.Select({layers: [vector]}); + +var map = new ol.Map({ + controls: ol.control.defaults({}, [selectControl]), + layers: [raster, vector], + renderer: ol.RendererHint.CANVAS, + target: 'map', + view: new ol.View2D({ + center: [-10997171.194994785, 5206335.565590534], + zoom: 4 + }) +}); + +selectControl.activate(); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 973fd47471..04cda33390 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -201,6 +201,15 @@ * Default is '' (empty string). */ +/** + * @typedef {Object} ol.control.SelectOptions + * @property {string|undefined} className CSS class name. Default is 'ol-select'. + * @property {Element|undefined} element Element. + * @property {Array.|undefined} layers Layers to select features on. + * @property {ol.Map|undefined} map Map. + * @property {Element|undefined} target Target. + */ + /** * @typedef {Object} ol.control.ScaleLineOptions * @property {string|undefined} className CSS Class name. Default is 'ol-scale-line'. diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js new file mode 100644 index 0000000000..20c5b34933 --- /dev/null +++ b/src/ol/control/selectcontrol.js @@ -0,0 +1,106 @@ +goog.provide('ol.control.Select'); + +goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classes'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.control.Control'); +goog.require('ol.css'); + + + +/** + * @constructor + * @extends {ol.control.Control} + * @param {ol.control.SelectOptions=} opt_options Options. + */ +ol.control.Select = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; + + this.layers_ = options.layers; + + var className = goog.isDef(options.className) ? options.className : + 'ol-select'; + + var element = goog.dom.createDom(goog.dom.TagName.DIV, { + 'class': className + ' ' + ol.css.CLASS_UNSELECTABLE + }); + var button = goog.dom.createDom(goog.dom.TagName.A, { + 'href': '#Select' + }); + goog.dom.appendChild(element, button); + + goog.events.listen(element, [ + goog.events.EventType.TOUCHEND, + goog.events.EventType.CLICK + ], this.toggleActive_, false, this); + + goog.base(this, { + element: element, + map: options.map, + target: options.target + }); +}; +goog.inherits(ol.control.Select, ol.control.Control); + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.control.Select.prototype.toggleActive_ = function(browserEvent) { + // prevent #Select anchor from getting appended to the url + browserEvent.preventDefault(); + if (this.active_) { + this.deactivate(); + } else { + this.activate(); + } +}; + + +/** + * Activate the control. + */ +ol.control.Select.prototype.activate = function() { + goog.dom.classes.add(this.element, 'active'); + // TODO: Add box selection + this.listenerKeys.push( + goog.events.listen(this.getMap(), ol.MapBrowserEvent.EventType.CLICK, + this.handleClick, true, this)); +}; + + +/** + * Dectivate the control. + */ +ol.control.Select.prototype.deactivate = function() { + if (!goog.array.isEmpty(this.listenerKeys)) { + goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); + this.listenerKeys.length = 0; + } + goog.dom.classes.remove(this.element, 'active'); +}; + + +/** + * @param {ol.MapBrowserEvent} evt Event. + */ +ol.control.Select.prototype.handleClick = function(evt) { + this.getMap().getFeatures({ + layers: this.layers_, + pixel: evt.getPixel(), + success: this.select + }); +}; + + +/** + * @param {Array.>} featuresByLayer Features by layer. + */ +ol.control.Select.prototype.select = function(featuresByLayer) { + // TODO: Do something with the features. +}; From 871388d2c0fff028d4ed1b4533c72bd9fe557879 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 9 Aug 2013 20:11:33 +0200 Subject: [PATCH 02/39] Adding selection layer and implementing basic click selection --- examples/select-features.html | 2 +- src/objectliterals.jsdoc | 1 + src/ol/control/selectcontrol.js | 55 ++++++++++++++++++++++++++++++--- src/ol/layer/vectorlayer.js | 40 ++++++++++++++++++++++-- 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/examples/select-features.html b/examples/select-features.html index 8e34f3422b..048f603d13 100644 --- a/examples/select-features.html +++ b/examples/select-features.html @@ -8,7 +8,7 @@ - Select featuers example + Select features example diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 04cda33390..7c2d5488d6 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -367,6 +367,7 @@ * @property {number|undefined} opacity Opacity. 0-1. Default is 1. * @property {ol.source.Source} source Source for this layer. * @property {ol.style.Style|undefined} style Style. + * @property {boolean} temp True for temporary layers. For internal use only. * @property {boolean|undefined} visible Visibility. Default is true (visible). */ diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 20c5b34933..326f5115cd 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -9,6 +9,9 @@ goog.require('goog.events.EventType'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.control.Control'); goog.require('ol.css'); +goog.require('ol.interaction.condition'); +goog.require('ol.layer.Vector'); +goog.require('ol.source.Vector'); @@ -20,8 +23,14 @@ goog.require('ol.css'); ol.control.Select = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; + this.layer = new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + temp: true + }); + this.layers_ = options.layers; + // TODO: css var className = goog.isDef(options.className) ? options.className : 'ol-select'; @@ -67,7 +76,8 @@ ol.control.Select.prototype.toggleActive_ = function(browserEvent) { */ ol.control.Select.prototype.activate = function() { goog.dom.classes.add(this.element, 'active'); - // TODO: Add box selection + this.getMap().addLayer(this.layer); + // TODO: Implement box selection this.listenerKeys.push( goog.events.listen(this.getMap(), ol.MapBrowserEvent.EventType.CLICK, this.handleClick, true, this)); @@ -82,6 +92,7 @@ ol.control.Select.prototype.deactivate = function() { goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); this.listenerKeys.length = 0; } + this.getMap().removeLayer(this.layer); goog.dom.classes.remove(this.element, 'active'); }; @@ -90,17 +101,53 @@ ol.control.Select.prototype.deactivate = function() { * @param {ol.MapBrowserEvent} evt Event. */ ol.control.Select.prototype.handleClick = function(evt) { + var clear = true; + if (ol.interaction.condition.shiftKeyOnly(evt.browserEvent)) { + clear = false; + } + + function select(featuresByLayer) { + this.select(featuresByLayer, clear); + } + this.getMap().getFeatures({ layers: this.layers_, pixel: evt.getPixel(), - success: this.select + success: goog.bind(select, this) }); }; /** * @param {Array.>} featuresByLayer Features by layer. + * @param {boolean} clear Whether the current layer content should be cleared. */ -ol.control.Select.prototype.select = function(featuresByLayer) { - // TODO: Do something with the features. +ol.control.Select.prototype.select = function(featuresByLayer, clear) { + for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { + var features = featuresByLayer[i]; + var numFeatures = features.length; + var selectedFeatures = []; + var unselectedFeatures = []; + for (var j = 0; j < numFeatures; ++j) { + var feature = features[j]; + var selectedFeature = this.layer.getFeatureWithUid(goog.getUid(feature)); + if (selectedFeature) { + // TODO: make toggle configurable + unselectedFeatures.push(selectedFeature); + } else { + selectedFeatures.push(feature); + } + } + var layer = this.layers_[i]; + if (goog.isFunction(layer.setRenderIntent)) { + // TODO: Implement setRenderIntent for ol.layer.Vector + layer.setRenderIntent('hidden', selectedFeatures); + layer.setRenderIntent('default', unselectedFeatures); + } + if (clear) { + this.layer.clear(); + } + this.layer.removeFeatures(unselectedFeatures); + this.layer.addFeatures(selectedFeatures); + } }; diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 397d6768e1..3055d98b20 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -201,6 +201,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. @@ -287,6 +297,11 @@ ol.layer.Vector = function(options) { */ this.polygonVertices_ = new ol.geom.SharedVertices(); + /** + * @type {boolean} Whether this is a temporary layer + */ + this.temp = goog.isDef(options.temp) ? options.temp : false; + }; goog.inherits(ol.layer.Vector, ol.layer.Layer); @@ -313,6 +328,17 @@ 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: goog.events.EventType.CHANGE + })); +}; + + /** * @return {ol.source.Vector} Source. */ @@ -426,6 +452,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. @@ -537,7 +573,7 @@ ol.layer.Vector.prototype.removeFeatures = function(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(', '); }; From 19a00bbe2741344bce18d419bc4e324659e910aa Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 9 Aug 2013 20:34:55 +0200 Subject: [PATCH 03/39] Making the control's button work --- examples/select-features.html | 48 ++++++++++++++++++++++++++++++++- src/ol/control/selectcontrol.js | 44 +++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/examples/select-features.html b/examples/select-features.html index 048f603d13..2c6428b330 100644 --- a/examples/select-features.html +++ b/examples/select-features.html @@ -9,6 +9,52 @@ Select features example + @@ -38,7 +84,7 @@

Select features example

-

Example of using the Select control.

+

Example of using the Select control. Select features by clicking polygons. Hold the Shift-key to add to the selection. Click the 'S' button to toggle the control's active state.

See the select-features.js source to see how this is done.

diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 326f5115cd..2a510fa163 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -23,14 +23,28 @@ goog.require('ol.source.Vector'); ol.control.Select = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; + /** + * @type {boolean} + * @private + */ + this.active_ = false; + + /** + * @type {ol.layer.Vector} + * @protected + */ this.layer = new ol.layer.Vector({ source: new ol.source.Vector({parser: null}), temp: true }); + /** + * @type {Array.} + * @private + */ this.layers_ = options.layers; - // TODO: css + // TODO: css/button refactoring var className = goog.isDef(options.className) ? options.className : 'ol-select'; @@ -75,12 +89,15 @@ ol.control.Select.prototype.toggleActive_ = function(browserEvent) { * Activate the control. */ ol.control.Select.prototype.activate = function() { - goog.dom.classes.add(this.element, 'active'); - this.getMap().addLayer(this.layer); - // TODO: Implement box selection - this.listenerKeys.push( - goog.events.listen(this.getMap(), ol.MapBrowserEvent.EventType.CLICK, - this.handleClick, true, this)); + if (!this.active_) { + this.active_ = true; + goog.dom.classes.add(this.element, 'active'); + this.getMap().addLayer(this.layer); + // TODO: Implement box selection + this.listenerKeys.push( + goog.events.listen(this.getMap(), ol.MapBrowserEvent.EventType.CLICK, + this.handleClick, true, this)); + } }; @@ -88,12 +105,15 @@ ol.control.Select.prototype.activate = function() { * Dectivate the control. */ ol.control.Select.prototype.deactivate = function() { - if (!goog.array.isEmpty(this.listenerKeys)) { - goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); - this.listenerKeys.length = 0; + if (this.active_) { + if (!goog.array.isEmpty(this.listenerKeys)) { + goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); + this.listenerKeys.length = 0; + } + this.getMap().removeLayer(this.layer); + goog.dom.classes.remove(this.element, 'active'); + this.active_ = false; } - this.getMap().removeLayer(this.layer); - goog.dom.classes.remove(this.element, 'active'); }; From 6be1ab80d2487f0ba3682e90a060c3d6f6b2c5d4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 10:26:49 +0200 Subject: [PATCH 04/39] Require layers --- src/objectliterals.jsdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 7c2d5488d6..951e955068 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -205,7 +205,7 @@ * @typedef {Object} ol.control.SelectOptions * @property {string|undefined} className CSS class name. Default is 'ol-select'. * @property {Element|undefined} element Element. - * @property {Array.|undefined} layers Layers to select features on. + * @property {Array.} layers Layers to select features on. * @property {ol.Map|undefined} map Map. * @property {Element|undefined} target Target. */ From b06526781c544e5a2e8c6c3226d1efa477fe4643 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 10:27:25 +0200 Subject: [PATCH 05/39] Adding change event --- src/ol/control/selectcontrol.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 2a510fa163..b314ab313d 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -14,6 +14,15 @@ goog.require('ol.layer.Vector'); goog.require('ol.source.Vector'); +/** + * @typedef {{layer: ol.layer.Layer, + * selected: (Array.|undefined), + * type: goog.events.EventType, + * unselected: (Array.|undefined)}} + */ +ol.control.SelectEventObject; + + /** * @constructor @@ -169,5 +178,11 @@ ol.control.Select.prototype.select = function(featuresByLayer, clear) { } this.layer.removeFeatures(unselectedFeatures); this.layer.addFeatures(selectedFeatures); + this.dispatchEvent(/** @type {ol.control.SelectEventObject} */ ({ + layer: layer, + selected: selectedFeatures, + type: goog.events.EventType.CHANGE, + unselected: unselectedFeatures + })); } }; From 3d22ec4ca8de639ff0e4dacb589fdf26a7bb2fc5 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 10:28:10 +0200 Subject: [PATCH 06/39] Simplifying clear/append detection --- src/ol/control/selectcontrol.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index b314ab313d..f6a1aeefba 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -130,10 +130,7 @@ ol.control.Select.prototype.deactivate = function() { * @param {ol.MapBrowserEvent} evt Event. */ ol.control.Select.prototype.handleClick = function(evt) { - var clear = true; - if (ol.interaction.condition.shiftKeyOnly(evt.browserEvent)) { - clear = false; - } + var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent); function select(featuresByLayer) { this.select(featuresByLayer, clear); From 0c54cbd9cc1e2f63bbfde620780a299e674f16ef Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 10:28:21 +0200 Subject: [PATCH 07/39] Adding tests --- test/spec/ol/control/selectcontrol.test.js | 141 +++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 test/spec/ol/control/selectcontrol.test.js diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js new file mode 100644 index 0000000000..d345ea7b68 --- /dev/null +++ b/test/spec/ol/control/selectcontrol.test.js @@ -0,0 +1,141 @@ +goog.provide('ol.test.control.Select'); + +describe('ol.control.Select', function() { + var map, target, select; + + beforeEach(function() { + target = document.createElement('div'); + target.style.width = '256px'; + target.style.height = '256px'; + document.body.appendChild(target); + map = new ol.Map({ + target: target + }); + select = new ol.control.Select({ + map: map + }); + }); + + afterEach(function() { + goog.dispose(select); + goog.dispose(map); + document.body.removeChild(target); + select = null; + map = null; + target = null; + }); + + describe('DOM creation', function() { + + it('creates the expected DOM elements', function() { + var selectButtons = goog.dom.getElementsByClass('ol-select', target), + selectButton = selectButtons[0], + hasUnselectableCls; + + expect(selectButtons.length).to.be(1); + + hasUnselectableCls = goog.dom.classes.has(selectButton, + 'ol-unselectable'); + expect(hasUnselectableCls).to.be(true); + }); + + it('has an .active class only when activated', function() { + var selectButton = goog.dom.getElementsByClass('ol-select', target)[0]; + select.activate(); + expect(goog.dom.classes.has(selectButton, 'active')).to.be(true); + select.deactivate(); + expect(goog.dom.classes.has(selectButton, 'active')).to.be(false); + }); + + }); + + describe('#activate and #deactivate', function() { + it('adds a temp layer to the map only when active', function() { + expect(map.getLayers().getLength()).to.be(0); + select.activate(); + expect(map.getLayers().getLength()).to.be(1); + expect(map.getLayers().getAt(0).temp).to.be(true); + select.deactivate(); + expect(map.getLayers().getLength()).to.be(0); + }); + it('has a private property so it knows if it is active', function() { + expect(select.active_).to.be(false); + select.activate(); + expect(select.active_).to.be(true); + select.deactivate(); + expect(select.active_).to.be(false); + }); + it('toggles active state on click', function() { + var selectButton = goog.dom.getElementsByClass('ol-select', target)[0]; + var event = new goog.events.BrowserEvent({type: 'click'}); + goog.events.fireListeners(selectButton, event.type, false, event); + expect(select.active_).to.be(true); + goog.events.fireListeners(selectButton, event.type, false, event); + expect(select.active_).to.be(false); + }); + }); + + describe('#select', function() { + + var layer, features; + + beforeEach(function() { + 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] + } + }] + })); + layer = new ol.layer.Vector({source: new ol.source.Vector({})}); + layer.addFeatures(features); + select.layers_ = [layer]; + }); + + it('toggles selection of features', function() { + select.select([features]); + expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) + .to.be(2); + select.select([features]); + expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) + .to.be(0); + }); + + it('can append features to an existing selection', function() { + select.select([[features[0]]]); + select.select([[features[1]]]); + expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) + .to.be(2); + }); + + it('can clear a selection before selecting new features', function() { + select.select([[features[0]]], true); + select.select([[features[1]]], true); + expect(goog.object.getValues(select.layer.featureCache_.idLookup_)[0]) + .to.eql(features[1]); + }); + + }); + +}); + +goog.require('goog.dispose'); +goog.require('goog.dom'); +goog.require('goog.dom.classes'); +goog.require('goog.events'); +goog.require('goog.events.BrowserEvent'); +goog.require('goog.object'); +goog.require('ol.Map'); +goog.require('ol.control.Select'); +goog.require('ol.layer.Vector'); +goog.require('ol.parser.GeoJSON'); +goog.require('ol.source.Vector'); From 414f4828d6f4ad54034faa4710e3e81e9bb8e85f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 10:50:06 +0200 Subject: [PATCH 08/39] Making Travis happy --- src/objectliterals.jsdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 951e955068..38faf10f22 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -367,7 +367,7 @@ * @property {number|undefined} opacity Opacity. 0-1. Default is 1. * @property {ol.source.Source} source Source for this layer. * @property {ol.style.Style|undefined} style Style. - * @property {boolean} temp True for temporary layers. For internal use only. + * @property {boolean|undefined} temp True for temporary layers. For internal use only. * @property {boolean|undefined} visible Visibility. Default is true (visible). */ From d7e277d0f57e42046e4066ffdbf364d7c5b99fda Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 13:21:47 +0200 Subject: [PATCH 09/39] Adding exports --- src/ol/control/selectcontrol.exports | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/ol/control/selectcontrol.exports diff --git a/src/ol/control/selectcontrol.exports b/src/ol/control/selectcontrol.exports new file mode 100644 index 0000000000..ac9685b9bc --- /dev/null +++ b/src/ol/control/selectcontrol.exports @@ -0,0 +1,3 @@ +@exportClass ol.control.Select ol.control.SelectOptions +@exportProperty ol.control.Select.prototype.activate +@exportProperty ol.control.Select.prototype.deactivate From fdd79a385a4f3ba2ae76d0fce10304c64ba489f9 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 11 Aug 2013 23:59:18 +0200 Subject: [PATCH 10/39] Making JSDoc happy --- src/ol/layer/vectorlayer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 3055d98b20..e392454ee3 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -298,7 +298,8 @@ ol.layer.Vector = function(options) { this.polygonVertices_ = new ol.geom.SharedVertices(); /** - * @type {boolean} Whether this is a temporary layer + * True if this is a temporary layer. + * @type {boolean} */ this.temp = goog.isDef(options.temp) ? options.temp : false; From f05629b3c3e42d909a9c818f4d8bb41f1d04cf55 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 19 Aug 2013 15:35:21 +0200 Subject: [PATCH 11/39] Work with clones rather than the original features --- src/ol/control/selectcontrol.js | 39 +++++++++++++++----- src/ol/feature.js | 14 +++++++ src/ol/geom/geometry.js | 13 +++++++ test/spec/ol/control/selectcontrol.test.js | 4 +- test/spec/ol/feature.test.js | 18 +++++++++ test/spec/ol/geom/geometrycollection.test.js | 17 +++++++++ 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index f6a1aeefba..db85cf9fc1 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -38,6 +38,12 @@ ol.control.Select = function(opt_options) { */ this.active_ = false; + /** + * @type {Object.} + * @private + */ + this.featureMap_ = {}; + /** * @type {ol.layer.Vector} * @protected @@ -150,31 +156,44 @@ ol.control.Select.prototype.handleClick = function(evt) { */ ol.control.Select.prototype.select = function(featuresByLayer, clear) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { + var layer = this.layers_[i]; var features = featuresByLayer[i]; var numFeatures = features.length; var selectedFeatures = []; + var featuresToAdd = []; var unselectedFeatures = []; + var featuresToRemove = []; for (var j = 0; j < numFeatures; ++j) { var feature = features[j]; - var selectedFeature = this.layer.getFeatureWithUid(goog.getUid(feature)); - if (selectedFeature) { + var uid = goog.getUid(feature); + var clone = this.featureMap_[uid]; + if (clone) { // TODO: make toggle configurable - unselectedFeatures.push(selectedFeature); - } else { selectedFeatures.push(feature); + featuresToRemove.push(clone); + delete this.featureMap_[uid]; + } + if (clear) { + for (var f in this.featureMap_) { + unselectedFeatures.push(layer.getFeatureWithUid(f)); + featuresToRemove.push(this.featureMap_[f]); + } + this.featureMap_ = {}; + } + if (!clone) { + clone = feature.clone(); + this.featureMap_[uid] = clone; + selectedFeatures.push(feature); + featuresToAdd.push(clone); } } - var layer = this.layers_[i]; if (goog.isFunction(layer.setRenderIntent)) { // TODO: Implement setRenderIntent for ol.layer.Vector layer.setRenderIntent('hidden', selectedFeatures); layer.setRenderIntent('default', unselectedFeatures); } - if (clear) { - this.layer.clear(); - } - this.layer.removeFeatures(unselectedFeatures); - this.layer.addFeatures(selectedFeatures); + this.layer.removeFeatures(featuresToRemove); + this.layer.addFeatures(featuresToAdd); this.dispatchEvent(/** @type {ol.control.SelectEventObject} */ ({ layer: layer, selected: selectedFeatures, diff --git a/src/ol/feature.js b/src/ol/feature.js index 24de9c40e3..aa19448f98 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -44,6 +44,20 @@ ol.Feature = function(opt_values) { goog.inherits(ol.Feature, ol.Object); +/** + * Create a clone of this feature. Both the feature and its geometry will be + * cloned. + * @return {ol.Feature} A clone of this feature, with a cloned geometry. + */ +ol.Feature.prototype.clone = function() { + var clone = new ol.Feature(this.getAttributes()); + clone.setGeometry(this.getGeometry().clone()); + clone.featureId_ = this.featureId_; + clone.symbolizers_ = this.symbolizers_; + return clone; +}; + + /** * Gets a copy of the attributes of this feature. * @return {Object.} Attributes object. 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/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index d345ea7b68..91a9a729fd 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -120,8 +120,8 @@ describe('ol.control.Select', function() { it('can clear a selection before selecting new features', function() { select.select([[features[0]]], true); select.select([[features[1]]], true); - expect(goog.object.getValues(select.layer.featureCache_.idLookup_)[0]) - .to.eql(features[1]); + expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) + .to.be(1); }); }); diff --git a/test/spec/ol/feature.test.js b/test/spec/ol/feature.test.js index 12ae10ba37..fb31212579 100644 --- a/test/spec/ol/feature.test.js +++ b/test/spec/ol/feature.test.js @@ -34,6 +34,24 @@ describe('ol.Feature', function() { }); + describe('#clone()', function() { + + it('creates a clone with a cloned geometry', function() { + var feature = new ol.Feature({ + loc: new ol.geom.Point([10, 20]), + foo: 'bar' + }); + feature.setFeatureId('foo'); + var clone = feature.clone(); + expect(clone).to.not.be(feature); + expect(clone.get('foo')).to.be('bar'); + expect(clone.getFeatureId()).to.be('foo'); + expect(clone.getGeometry()).to.not.be(feature.getGeometry()); + expect(clone.getGeometry().getCoordinates()).to.eql([10, 20]); + }); + + }); + describe('#get()', function() { it('returns values set at construction', 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() { From 760694582e6c89874cb6b178ea2278edbd8ce5db Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 19 Aug 2013 15:46:56 +0200 Subject: [PATCH 12/39] Adding missing @requires --- src/ol/layer/vectorlayer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index e392454ee3..c267529b77 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'); From 14b69d15f190f3721b58eb08158e71b0de7cf579 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 20 Aug 2013 08:41:35 +0200 Subject: [PATCH 13/39] One selection layer per source layer This way all styles can be defined on the source layer, by defining a rule with a select renderIntent. It also will make addition and removal of layers easier while the select control is active. --- src/ol/control/selectcontrol.js | 67 +++++++++++++++------- src/ol/layer/vectorlayer.js | 8 +++ test/spec/ol/control/selectcontrol.test.js | 60 +++++++++---------- 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index db85cf9fc1..35c7db6d40 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -39,25 +39,25 @@ ol.control.Select = function(opt_options) { this.active_ = false; /** - * @type {Object.} + * @type {Array.>} * @private */ - this.featureMap_ = {}; + this.featureMap_ = []; /** - * @type {ol.layer.Vector} + * @type {Array.} * @protected */ - this.layer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), - temp: true - }); + this.selectionLayers; /** * @type {Array.} * @private */ - this.layers_ = options.layers; + this.layers_ = goog.isDef(options.layers) ? options.layers : []; + + // TODO: handle addition, removal and re-ordering of layers + this.createSelectionLayers_(); // TODO: css/button refactoring var className = goog.isDef(options.className) ? options.className : @@ -85,6 +85,23 @@ ol.control.Select = function(opt_options) { goog.inherits(ol.control.Select, ol.control.Control); +/** + * Create a selection layer for each source layer. + * @private + */ +ol.control.Select.prototype.createSelectionLayers_ = function() { + this.selectionLayers = []; + for (var i = 0, ii = this.layers_.length; i < ii; ++i) { + this.featureMap_.push({}); + this.selectionLayers.push(new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + style: this.layers_[i].getStyle(), + temp: true + })); + } +}; + + /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private @@ -107,10 +124,14 @@ ol.control.Select.prototype.activate = function() { if (!this.active_) { this.active_ = true; goog.dom.classes.add(this.element, 'active'); - this.getMap().addLayer(this.layer); + var map = this.getMap(); + for (var i = 0, ii = this.selectionLayers.length; i < ii; ++i) { + map.addLayer(this.selectionLayers[i]); + } + // TODO: Implement box selection this.listenerKeys.push( - goog.events.listen(this.getMap(), ol.MapBrowserEvent.EventType.CLICK, + goog.events.listen(map, ol.MapBrowserEvent.EventType.CLICK, this.handleClick, true, this)); } }; @@ -125,7 +146,10 @@ ol.control.Select.prototype.deactivate = function() { goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); this.listenerKeys.length = 0; } - this.getMap().removeLayer(this.layer); + var map = this.getMap(); + for (var i = 0, ii = this.selectionLayers.length; i < ii; ++i) { + map.removeLayer(this.selectionLayers[i]); + } goog.dom.classes.remove(this.element, 'active'); this.active_ = false; } @@ -157,43 +181,46 @@ ol.control.Select.prototype.handleClick = function(evt) { ol.control.Select.prototype.select = function(featuresByLayer, clear) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { var layer = this.layers_[i]; + var selectionLayer = this.selectionLayers[i]; var features = featuresByLayer[i]; var numFeatures = features.length; var selectedFeatures = []; var featuresToAdd = []; var unselectedFeatures = []; var featuresToRemove = []; + var featureMap = this.featureMap_[i]; for (var j = 0; j < numFeatures; ++j) { var feature = features[j]; var uid = goog.getUid(feature); - var clone = this.featureMap_[uid]; + var clone = featureMap[uid]; if (clone) { // TODO: make toggle configurable selectedFeatures.push(feature); featuresToRemove.push(clone); - delete this.featureMap_[uid]; + delete featureMap[uid]; } if (clear) { - for (var f in this.featureMap_) { + for (var f in featureMap) { unselectedFeatures.push(layer.getFeatureWithUid(f)); - featuresToRemove.push(this.featureMap_[f]); + featuresToRemove.push(featureMap[f]); } - this.featureMap_ = {}; + featureMap = {}; + this.featureMap_[i] = featureMap; } if (!clone) { clone = feature.clone(); - this.featureMap_[uid] = clone; + featureMap[uid] = clone; selectedFeatures.push(feature); featuresToAdd.push(clone); } } if (goog.isFunction(layer.setRenderIntent)) { - // TODO: Implement setRenderIntent for ol.layer.Vector + // TODO: Implement setRenderIntent for ol.Layer.Vector layer.setRenderIntent('hidden', selectedFeatures); layer.setRenderIntent('default', unselectedFeatures); } - this.layer.removeFeatures(featuresToRemove); - this.layer.addFeatures(featuresToAdd); + selectionLayer.removeFeatures(featuresToRemove); + selectionLayer.addFeatures(featuresToAdd); this.dispatchEvent(/** @type {ol.control.SelectEventObject} */ ({ layer: layer, selected: selectedFeatures, diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index c267529b77..b79e26a329 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -349,6 +349,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, diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index 91a9a729fd..58aa476bf4 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -1,7 +1,7 @@ goog.provide('ol.test.control.Select'); describe('ol.control.Select', function() { - var map, target, select; + var map, target, select, features; beforeEach(function() { target = document.createElement('div'); @@ -11,7 +11,26 @@ describe('ol.control.Select', function() { 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] + } + }] + })); + var layer = new ol.layer.Vector({source: new ol.source.Vector({})}); + layer.addFeatures(features); select = new ol.control.Select({ + layers: [layer], map: map }); }); @@ -77,51 +96,26 @@ describe('ol.control.Select', function() { describe('#select', function() { - var layer, features; - - beforeEach(function() { - 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] - } - }] - })); - layer = new ol.layer.Vector({source: new ol.source.Vector({})}); - layer.addFeatures(features); - select.layers_ = [layer]; - }); - it('toggles selection of features', function() { + var layer = select.selectionLayers[0]; select.select([features]); - expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) - .to.be(2); + expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); select.select([features]); - expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) - .to.be(0); + expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); }); it('can append features to an existing selection', function() { + var layer = select.selectionLayers[0]; select.select([[features[0]]]); select.select([[features[1]]]); - expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) - .to.be(2); + expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); }); it('can clear a selection before selecting new features', function() { + var layer = select.selectionLayers[0]; select.select([[features[0]]], true); select.select([[features[1]]], true); - expect(goog.object.getCount(select.layer.featureCache_.idLookup_)) - .to.be(1); + expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); }); }); From 89052079b7f8eba08c8f44147b5d3c255cdb51f0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 20 Aug 2013 08:48:03 +0200 Subject: [PATCH 14/39] Example update after recent API changes --- examples/select-features.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/select-features.js b/examples/select-features.js index cb13f361e6..097b459616 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -8,8 +8,8 @@ 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.Polygon'); -goog.require('ol.style.Rule'); +goog.require('ol.style.Fill'); +goog.require('ol.style.Stroke'); goog.require('ol.style.Style'); var raster = new ol.layer.TileLayer({ @@ -21,21 +21,23 @@ var vector = new ol.layer.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({ - symbolizers: [ - new ol.style.Polygon({ - strokeColor: '#bada55' - }) - ] - }) - ]}) + style: new ol.style.Style({ + symbolizers: [ + new ol.style.Fill({ + color: '#ffffff', + opacity: 0.25 + }), + new ol.style.Stroke({ + color: '#6666ff' + }) + ] + }) }); var selectControl = new ol.control.Select({layers: [vector]}); var map = new ol.Map({ - controls: ol.control.defaults({}, [selectControl]), + controls: ol.control.defaults().extend([selectControl]), layers: [raster, vector], renderer: ol.RendererHint.CANVAS, target: 'map', From 4f2d37b6a39b6a914ea56abec523f05d91dd8be9 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 20 Aug 2013 23:15:05 +0200 Subject: [PATCH 15/39] New ThisIdentifier expression This allows member expressions to use the 'this' keyword. --- src/ol/expr/expressions.js | 22 ++++++++++++++++++++++ src/ol/expr/lexer.js | 4 +++- src/ol/expr/parser.js | 13 +++++++++++++ test/spec/ol/expr/expression.test.js | 6 ++++++ test/spec/ol/expr/expressions.test.js | 20 ++++++++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/ol/expr/expressions.js b/src/ol/expr/expressions.js index f98d595b8e..1b42603ee6 100644 --- a/src/ol/expr/expressions.js +++ b/src/ol/expr/expressions.js @@ -10,6 +10,7 @@ goog.provide('ol.expr.Math'); goog.provide('ol.expr.MathOp'); goog.provide('ol.expr.Member'); goog.provide('ol.expr.Not'); +goog.provide('ol.expr.ThisIdentifier'); @@ -624,3 +625,24 @@ ol.expr.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { ol.expr.Not.prototype.getArgument = function() { return this.argument_; }; + + + +/** + * An identifier for the 'this' keyword. + * + * @constructor + * @extends {ol.expr.Expression} + */ +ol.expr.ThisIdentifier = function() {}; +goog.inherits(ol.expr.ThisIdentifier, ol.expr.Expression); +goog.addSingletonGetter(ol.expr.ThisIdentifier); + + +/** + * @inheritDoc + */ +ol.expr.ThisIdentifier.prototype.evaluate = + function(opt_scope, opt_fns, opt_this) { + return opt_this; +}; diff --git a/src/ol/expr/lexer.js b/src/ol/expr/lexer.js index c1af520c34..41a69eb8c6 100644 --- a/src/ol/expr/lexer.js +++ b/src/ol/expr/lexer.js @@ -85,6 +85,7 @@ ol.expr.TokenType = { NUMERIC_LITERAL: 'Numeric', PUNCTUATOR: 'Punctuator', STRING_LITERAL: 'String', + THIS_IDENTIFIER: 'This', UNKNOWN: 'Unknown' }; @@ -515,7 +516,8 @@ ol.expr.Lexer.prototype.scanIdentifier_ = function(code) { if (id.length === 1) { type = ol.expr.TokenType.IDENTIFIER; } else if (this.isKeyword_(id)) { - type = ol.expr.TokenType.KEYWORD; + type = (id === 'this') ? + ol.expr.TokenType.THIS_IDENTIFIER : ol.expr.TokenType.KEYWORD; } else if (id === 'null') { type = ol.expr.TokenType.NULL_LITERAL; } else if (id === 'true' || id === 'false') { diff --git a/src/ol/expr/parser.js b/src/ol/expr/parser.js index f11cdd5603..03be0dd75b 100644 --- a/src/ol/expr/parser.js +++ b/src/ol/expr/parser.js @@ -30,6 +30,7 @@ goog.require('ol.expr.Math'); goog.require('ol.expr.MathOp'); goog.require('ol.expr.Member'); goog.require('ol.expr.Not'); +goog.require('ol.expr.ThisIdentifier'); goog.require('ol.expr.Token'); goog.require('ol.expr.TokenType'); goog.require('ol.expr.UnexpectedToken'); @@ -198,6 +199,16 @@ ol.expr.Parser.prototype.createMemberExpression_ = function(object, }; +/** + * Create a 'this' identifier. + * @return {ol.expr.ThisIdentifier} The 'this' identifier. + * @private + */ +ol.expr.Parser.prototype.createThisIdentifier_ = function() { + return ol.expr.ThisIdentifier.getInstance(); +}; + + /** * Create a unary expression. The only true unary operator supported here is * "!". For +/-, we apply the operator to literal expressions and return @@ -434,6 +445,8 @@ ol.expr.Parser.prototype.parsePrimaryExpression_ = function(lexer) { expr = this.createLiteral_(token.value === 'true'); } else if (type === ol.expr.TokenType.NULL_LITERAL) { expr = this.createLiteral_(null); + } else if (type === ol.expr.TokenType.THIS_IDENTIFIER) { + expr = this.createThisIdentifier_(); } else { throw new ol.expr.UnexpectedToken(token); } diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index 9b396897e4..b358ecb848 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -92,6 +92,12 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate(scope)).to.be(42); }); + it('parses member expressions in the \'this\' scope', function() { + var expr = ol.expr.parse('this.foo'); + var thisScope = {foo: 'bar'}; + expect(expr.evaluate(undefined, undefined, thisScope)).to.be('bar'); + }); + it('consumes whitespace as expected', function() { var expr = ol.expr.parse(' foo . bar . baz '); expect(expr).to.be.a(ol.expr.Member); diff --git a/test/spec/ol/expr/expressions.test.js b/test/spec/ol/expr/expressions.test.js index c64ea7cd52..3a6f07a743 100644 --- a/test/spec/ol/expr/expressions.test.js +++ b/test/spec/ol/expr/expressions.test.js @@ -624,6 +624,25 @@ describe('ol.expr.Not', function() { }); +describe('ol.expr.ThisIdentifier', function() { + + describe('#getInstance()', function() { + it('has a getInstance method to return the singleton', function() { + expect(ol.expr.ThisIdentifier.getInstance()) + .to.be.a(ol.expr.ThisIdentifier); + }); + }); + + describe('#evaluate()', function() { + it('evaluates to the passed scope', function() { + expect(ol.expr.ThisIdentifier.getInstance() + .evaluate(undefined, undefined, 'foo')).to.be('foo'); + }); + }); + +}); + + goog.require('ol.expr.Call'); goog.require('ol.expr.Comparison'); goog.require('ol.expr.ComparisonOp'); @@ -636,3 +655,4 @@ goog.require('ol.expr.Math'); goog.require('ol.expr.MathOp'); goog.require('ol.expr.Member'); goog.require('ol.expr.Not'); +goog.require('ol.expr.ThisIdentifier'); From 3a50a754aad74d5d75fdad6718b6af9009d3a2fd Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 20 Aug 2013 23:29:44 +0200 Subject: [PATCH 16/39] Implementing renderIntent handling --- examples/select-features.js | 12 ++++++++ src/ol/control/selectcontrol.js | 10 +++++-- src/ol/feature.js | 7 +++++ src/ol/layer/vectorlayer.js | 30 +++++++++++++++++-- src/ol/layer/vectorlayerrenderintent.js | 11 +++++++ .../canvas/canvasvectorlayerrenderer.js | 4 ++- .../renderer/canvas/canvasvectorrenderer.js | 23 +++++++++++--- 7 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 src/ol/layer/vectorlayerrenderintent.js diff --git a/examples/select-features.js b/examples/select-features.js index 097b459616..f15b04f2e2 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -9,6 +9,7 @@ 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'); @@ -22,6 +23,17 @@ var vector = new ol.layer.Vector({ url: 'data/gml/topp-states-wfs.xml' }), style: new ol.style.Style({ + rules: [ + new ol.style.Rule({ + filter: 'this.renderIntent == "selected"', + symbolizers: [ + new ol.style.Fill({ + color: '#ffffff', + opacity: 0.5 + }) + ] + }) + ], symbolizers: [ new ol.style.Fill({ color: '#ffffff', diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 35c7db6d40..db4bcf8968 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -11,6 +11,7 @@ goog.require('ol.control.Control'); goog.require('ol.css'); goog.require('ol.interaction.condition'); goog.require('ol.layer.Vector'); +goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.source.Vector'); @@ -195,7 +196,7 @@ ol.control.Select.prototype.select = function(featuresByLayer, clear) { var clone = featureMap[uid]; if (clone) { // TODO: make toggle configurable - selectedFeatures.push(feature); + unselectedFeatures.push(feature); featuresToRemove.push(clone); delete featureMap[uid]; } @@ -210,14 +211,17 @@ ol.control.Select.prototype.select = function(featuresByLayer, clear) { if (!clone) { clone = feature.clone(); featureMap[uid] = clone; + clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; selectedFeatures.push(feature); featuresToAdd.push(clone); } } if (goog.isFunction(layer.setRenderIntent)) { // TODO: Implement setRenderIntent for ol.Layer.Vector - layer.setRenderIntent('hidden', selectedFeatures); - layer.setRenderIntent('default', unselectedFeatures); + layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.HIDDEN, + selectedFeatures); + layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT, + unselectedFeatures); } selectionLayer.removeFeatures(featuresToRemove); selectionLayer.addFeatures(featuresToAdd); diff --git a/src/ol/feature.js b/src/ol/feature.js index aa19448f98..69ca5aea0d 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 diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index b79e26a329..fe010c7e18 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -236,7 +236,9 @@ ol.layer.FeatureCache.prototype.remove = function(feature) { */ ol.layer.VectorLayerEventType = { ADD: 'add', - REMOVE: 'remove' + CHANGE: goog.events.EventType.CHANGE, + REMOVE: 'remove', + SYMBOLIZER: 'symbolizer' }; @@ -336,7 +338,7 @@ ol.layer.Vector.prototype.addFeatures = function(features) { ol.layer.Vector.prototype.clear = function() { this.featureCache_.clear(); this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ - type: goog.events.EventType.CHANGE + type: ol.layer.VectorLayerEventType.CHANGE })); }; @@ -578,6 +580,30 @@ ol.layer.Vector.prototype.removeFeatures = function(features) { }; +/** + * Changes the renderIntent for an array of features. + * @param {string} renderIntent Render intent. + * @param {Array.} features Features to change the renderIntent for. + */ +ol.layer.Vector.prototype.setRenderIntent = function(renderIntent, features) { + 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.SYMBOLIZER + })); +}; + + /** * @param {Array.} features Features. * @return {string} Feature info. 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..fa6091f008 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.SYMBOLIZER ], 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 { From 56c228d00963b2282b824039b91a2f2a691de466 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 21 Aug 2013 09:52:52 +0200 Subject: [PATCH 17/39] Removing option for temporary layer Instead, adding setter and getter for a private temp_ property, as suggested by @elemoine. --- src/objectliterals.jsdoc | 1 - src/ol/control/selectcontrol.js | 9 +++++---- src/ol/layer/vectorlayer.js | 19 ++++++++++++++++++- test/spec/ol/control/selectcontrol.test.js | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 38faf10f22..bd8b80ab45 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -367,7 +367,6 @@ * @property {number|undefined} opacity Opacity. 0-1. Default is 1. * @property {ol.source.Source} source Source for this layer. * @property {ol.style.Style|undefined} style Style. - * @property {boolean|undefined} temp True for temporary layers. For internal use only. * @property {boolean|undefined} visible Visibility. Default is true (visible). */ diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index db4bcf8968..c9e7e4d37c 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -94,11 +94,12 @@ ol.control.Select.prototype.createSelectionLayers_ = function() { this.selectionLayers = []; for (var i = 0, ii = this.layers_.length; i < ii; ++i) { this.featureMap_.push({}); - this.selectionLayers.push(new ol.layer.Vector({ + var selectionLayer = new ol.layer.Vector({ source: new ol.source.Vector({parser: null}), - style: this.layers_[i].getStyle(), - temp: true - })); + style: this.layers_[i].getStyle() + }); + selectionLayer.setTemporary(true); + this.selectionLayers.push(selectionLayer); } }; diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index fe010c7e18..ef2a3542a2 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -303,8 +303,9 @@ ol.layer.Vector = function(options) { /** * True if this is a temporary layer. * @type {boolean} + * @private */ - this.temp = goog.isDef(options.temp) ? options.temp : false; + this.temp_ = false; }; goog.inherits(ol.layer.Vector, ol.layer.Layer); @@ -343,6 +344,14 @@ ol.layer.Vector.prototype.clear = function() { }; +/** + * @return {boolean} Whether this layer is temporary. + */ +ol.layer.Vector.prototype.getTemporary = function() { + return this.temp_; +}; + + /** * @return {ol.source.Vector} Source. */ @@ -604,6 +613,14 @@ ol.layer.Vector.prototype.setRenderIntent = function(renderIntent, features) { }; +/** + * @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. diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index 58aa476bf4..870f938b8b 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -73,7 +73,7 @@ describe('ol.control.Select', function() { expect(map.getLayers().getLength()).to.be(0); select.activate(); expect(map.getLayers().getLength()).to.be(1); - expect(map.getLayers().getAt(0).temp).to.be(true); + expect(map.getLayers().getAt(0).getTemporary()).to.be(true); select.deactivate(); expect(map.getLayers().getLength()).to.be(0); }); From 0391a028c50f32d8bdce4ebb93ee7636f456e8f7 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 21 Aug 2013 14:23:07 +0200 Subject: [PATCH 18/39] Adding layer visibility/addition/removal handling The Select control creates a selection layer for each layer it is configured with. When a configured layer is removed from the map, the selection layer will be made invisible. For configured layers on the map, the 'visible' property of the selection layer is bound to the configured layer. --- src/ol/control/selectcontrol.js | 45 ++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index c9e7e4d37c..154511c7f2 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -6,6 +6,7 @@ goog.require('goog.dom.TagName'); goog.require('goog.dom.classes'); goog.require('goog.events'); goog.require('goog.events.EventType'); +goog.require('ol.CollectionEventType'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.control.Control'); goog.require('ol.css'); @@ -94,11 +95,13 @@ ol.control.Select.prototype.createSelectionLayers_ = function() { this.selectionLayers = []; for (var i = 0, ii = this.layers_.length; i < ii; ++i) { this.featureMap_.push({}); + var layer = this.layers_[i]; var selectionLayer = new ol.layer.Vector({ source: new ol.source.Vector({parser: null}), - style: this.layers_[i].getStyle() + style: layer.getStyle() }); selectionLayer.setTemporary(true); + selectionLayer.bindTo('visible', layer); this.selectionLayers.push(selectionLayer); } }; @@ -168,14 +171,38 @@ ol.control.Select.prototype.handleClick = function(evt) { this.select(featuresByLayer, clear); } - this.getMap().getFeatures({ - layers: this.layers_, + var map = this.getMap(); + map.getFeatures({ + layers: goog.array.filter(this.layers_, this.layerFilterFunction, this), pixel: evt.getPixel(), success: goog.bind(select, this) }); }; +/** + * @param {ol.CollectionEvent} evt Event. + */ +ol.control.Select.prototype.handleLayerCollectionChange = function(evt) { + var layer = evt.elem; + var index = goog.array.indexOf(this.layers_, layer); + if (index !== -1) { + this.selectionLayers[index].setVisible( + evt.type === ol.CollectionEventType.ADD); + } +}; + + +/** + * @param {ol.layer.Layer} layer Layer. + * @param {number} index Index. + * @return {boolean} Whether to include the layer. + */ +ol.control.Select.prototype.layerFilterFunction = function(layer, index) { + return this.selectionLayers[index].getVisible(); +}; + + /** * @param {Array.>} featuresByLayer Features by layer. * @param {boolean} clear Whether the current layer content should be cleared. @@ -234,3 +261,15 @@ ol.control.Select.prototype.select = function(featuresByLayer, clear) { })); } }; + + +/** + * @inheritDoc + */ +ol.control.Select.prototype.setMap = function(map) { + goog.base(this, 'setMap', map); + var layers = map.getLayers(); + goog.events.listen(layers, + [ol.CollectionEventType.ADD, ol.CollectionEventType.REMOVE], + this.handleLayerCollectionChange, false, this); +}; From d5c0fdd557e453ee415e803c2a6cdc364ac2418d Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 21 Aug 2013 14:23:19 +0200 Subject: [PATCH 19/39] Removing addressed TODOs --- src/ol/control/selectcontrol.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 154511c7f2..951691ca53 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -58,7 +58,6 @@ ol.control.Select = function(opt_options) { */ this.layers_ = goog.isDef(options.layers) ? options.layers : []; - // TODO: handle addition, removal and re-ordering of layers this.createSelectionLayers_(); // TODO: css/button refactoring @@ -245,7 +244,6 @@ ol.control.Select.prototype.select = function(featuresByLayer, clear) { } } if (goog.isFunction(layer.setRenderIntent)) { - // TODO: Implement setRenderIntent for ol.Layer.Vector layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.HIDDEN, selectedFeatures); layer.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT, From a417b75c1f2553f074d7578e89addab44e64edd1 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 21 Aug 2013 18:59:08 +0200 Subject: [PATCH 20/39] Simplifying layer mapping By making selectionLayer an object keyed by source layer UIDs, we save some indexOf lookups. --- src/ol/control/selectcontrol.js | 34 ++++++++++++---------- test/spec/ol/control/selectcontrol.test.js | 18 ++++++------ 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 951691ca53..117218df72 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -47,7 +47,7 @@ ol.control.Select = function(opt_options) { this.featureMap_ = []; /** - * @type {Array.} + * @type {Object.<*, ol.layer.Vector>} * @protected */ this.selectionLayers; @@ -91,7 +91,7 @@ goog.inherits(ol.control.Select, ol.control.Control); * @private */ ol.control.Select.prototype.createSelectionLayers_ = function() { - this.selectionLayers = []; + this.selectionLayers = {}; for (var i = 0, ii = this.layers_.length; i < ii; ++i) { this.featureMap_.push({}); var layer = this.layers_[i]; @@ -101,7 +101,7 @@ ol.control.Select.prototype.createSelectionLayers_ = function() { }); selectionLayer.setTemporary(true); selectionLayer.bindTo('visible', layer); - this.selectionLayers.push(selectionLayer); + this.selectionLayers[goog.getUid(layer)] = selectionLayer; } }; @@ -129,7 +129,7 @@ ol.control.Select.prototype.activate = function() { this.active_ = true; goog.dom.classes.add(this.element, 'active'); var map = this.getMap(); - for (var i = 0, ii = this.selectionLayers.length; i < ii; ++i) { + for (var i in this.selectionLayers) { map.addLayer(this.selectionLayers[i]); } @@ -151,7 +151,7 @@ ol.control.Select.prototype.deactivate = function() { this.listenerKeys.length = 0; } var map = this.getMap(); - for (var i = 0, ii = this.selectionLayers.length; i < ii; ++i) { + for (var i in this.selectionLayers) { map.removeLayer(this.selectionLayers[i]); } goog.dom.classes.remove(this.element, 'active'); @@ -164,15 +164,16 @@ ol.control.Select.prototype.deactivate = function() { * @param {ol.MapBrowserEvent} evt Event. */ ol.control.Select.prototype.handleClick = function(evt) { + var layers = goog.array.filter(this.layers_, this.layerFilterFunction, this); var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent); function select(featuresByLayer) { - this.select(featuresByLayer, clear); + this.select(featuresByLayer, layers, clear); } var map = this.getMap(); map.getFeatures({ - layers: goog.array.filter(this.layers_, this.layerFilterFunction, this), + layers: layers, pixel: evt.getPixel(), success: goog.bind(select, this) }); @@ -183,11 +184,10 @@ ol.control.Select.prototype.handleClick = function(evt) { * @param {ol.CollectionEvent} evt Event. */ ol.control.Select.prototype.handleLayerCollectionChange = function(evt) { - var layer = evt.elem; - var index = goog.array.indexOf(this.layers_, layer); - if (index !== -1) { - this.selectionLayers[index].setVisible( - evt.type === ol.CollectionEventType.ADD); + var layer = /** @type {ol.layer.Layer} */ (evt.elem); + var selectionLayer = this.selectionLayers[goog.getUid(layer)]; + if (goog.isDef(selectionLayer)) { + selectionLayer.setVisible(evt.type === ol.CollectionEventType.ADD); } }; @@ -198,18 +198,20 @@ ol.control.Select.prototype.handleLayerCollectionChange = function(evt) { * @return {boolean} Whether to include the layer. */ ol.control.Select.prototype.layerFilterFunction = function(layer, index) { - return this.selectionLayers[index].getVisible(); + return this.selectionLayers[goog.getUid(layer)].getVisible(); }; /** * @param {Array.>} featuresByLayer Features by layer. + * @param {Array.} layers The queried layers. * @param {boolean} clear Whether the current layer content should be cleared. */ -ol.control.Select.prototype.select = function(featuresByLayer, clear) { +ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { - var layer = this.layers_[i]; - var selectionLayer = this.selectionLayers[i]; + var layer = layers[i]; + var selectionLayer = + this.selectionLayers[goog.getUid(layer)]; var features = featuresByLayer[i]; var numFeatures = features.length; var selectedFeatures = []; diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index 870f938b8b..1191b29159 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -97,24 +97,24 @@ describe('ol.control.Select', function() { describe('#select', function() { it('toggles selection of features', function() { - var layer = select.selectionLayers[0]; - select.select([features]); + var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; + select.select([features], select.layers_); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); - select.select([features]); + select.select([features], select.layers_); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); }); it('can append features to an existing selection', function() { - var layer = select.selectionLayers[0]; - select.select([[features[0]]]); - select.select([[features[1]]]); + var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; + select.select([[features[0]]], select.layers_); + select.select([[features[1]]], select.layers_); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); }); it('can clear a selection before selecting new features', function() { - var layer = select.selectionLayers[0]; - select.select([[features[0]]], true); - select.select([[features[1]]], true); + var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; + select.select([[features[0]]], select.layers_, true); + select.select([[features[1]]], select.layers_, true); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); }); From c6e61e2d23f27d4646aa709dbb4c837411d26819 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 22 Aug 2013 19:29:08 +0200 Subject: [PATCH 21/39] Dynamic layers and lazy selection layer creation With this change, the user provides a filter function instead of an array of layers. Selection layers are created lazily, and addition/removal of layers is not handled by the control to give the user more options, as suggested by @elemoine. --- examples/select-features.js | 4 +- src/objectliterals.jsdoc | 3 +- src/ol/control/selectcontrol.js | 106 +++++++-------------- test/spec/ol/control/selectcontrol.test.js | 30 +++--- 4 files changed, 54 insertions(+), 89 deletions(-) diff --git a/examples/select-features.js b/examples/select-features.js index f15b04f2e2..268d21e92d 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -46,7 +46,9 @@ var vector = new ol.layer.Vector({ }) }); -var selectControl = new ol.control.Select({layers: [vector]}); +var selectControl = new ol.control.Select({ + layerFilter: function(layer) { return layer === vector; } +}); var map = new ol.Map({ controls: ol.control.defaults().extend([selectControl]), diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index bd8b80ab45..0267e0b594 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -205,7 +205,8 @@ * @typedef {Object} ol.control.SelectOptions * @property {string|undefined} className CSS class name. Default is 'ol-select'. * @property {Element|undefined} element Element. - * @property {Array.} layers Layers to select features on. + * @property {undefined|function(ol.layer.Layer):boolean} layerFilter Filter + * function to restrict selection to a subset of layers. * @property {ol.Map|undefined} map Map. * @property {Element|undefined} target Target. */ diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js index 117218df72..17b6e9931e 100644 --- a/src/ol/control/selectcontrol.js +++ b/src/ol/control/selectcontrol.js @@ -6,7 +6,6 @@ goog.require('goog.dom.TagName'); goog.require('goog.dom.classes'); goog.require('goog.events'); goog.require('goog.events.EventType'); -goog.require('ol.CollectionEventType'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.control.Control'); goog.require('ol.css'); @@ -41,24 +40,25 @@ ol.control.Select = function(opt_options) { this.active_ = false; /** - * @type {Array.>} + * Mapping between original features and cloned features on selection layers. + * @type {Object.<*,Object.>} * @private */ - this.featureMap_ = []; + this.featureMap_ = {}; /** - * @type {Object.<*, ol.layer.Vector>} + * Mapping between original layers and selection layers. + * @type {Object.<*,ol.layer.Vector>} * @protected */ - this.selectionLayers; + this.selectionLayers = {}; /** - * @type {Array.} + * @type {null|function(ol.layer.Layer):boolean} * @private */ - this.layers_ = goog.isDef(options.layers) ? options.layers : []; - - this.createSelectionLayers_(); + this.layerFilter_ = goog.isDef(options.layerFilter) ? + options.layerFilter : null; // TODO: css/button refactoring var className = goog.isDef(options.className) ? options.className : @@ -86,26 +86,6 @@ ol.control.Select = function(opt_options) { goog.inherits(ol.control.Select, ol.control.Control); -/** - * Create a selection layer for each source layer. - * @private - */ -ol.control.Select.prototype.createSelectionLayers_ = function() { - this.selectionLayers = {}; - for (var i = 0, ii = this.layers_.length; i < ii; ++i) { - this.featureMap_.push({}); - var layer = this.layers_[i]; - var selectionLayer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), - style: layer.getStyle() - }); - selectionLayer.setTemporary(true); - selectionLayer.bindTo('visible', layer); - this.selectionLayers[goog.getUid(layer)] = selectionLayer; - } -}; - - /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private @@ -164,14 +144,17 @@ ol.control.Select.prototype.deactivate = function() { * @param {ol.MapBrowserEvent} evt Event. */ ol.control.Select.prototype.handleClick = function(evt) { - var layers = goog.array.filter(this.layers_, this.layerFilterFunction, this); + var map = this.getMap(); + var layers = map.getLayerGroup().getLayersArray(); + if (!goog.isNull(this.layerFilter_)) { + layers = goog.array.filter(layers, this.layerFilter_); + } var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent); function select(featuresByLayer) { this.select(featuresByLayer, layers, clear); } - var map = this.getMap(); map.getFeatures({ layers: layers, pixel: evt.getPixel(), @@ -180,28 +163,6 @@ ol.control.Select.prototype.handleClick = function(evt) { }; -/** - * @param {ol.CollectionEvent} evt Event. - */ -ol.control.Select.prototype.handleLayerCollectionChange = function(evt) { - var layer = /** @type {ol.layer.Layer} */ (evt.elem); - var selectionLayer = this.selectionLayers[goog.getUid(layer)]; - if (goog.isDef(selectionLayer)) { - selectionLayer.setVisible(evt.type === ol.CollectionEventType.ADD); - } -}; - - -/** - * @param {ol.layer.Layer} layer Layer. - * @param {number} index Index. - * @return {boolean} Whether to include the layer. - */ -ol.control.Select.prototype.layerFilterFunction = function(layer, index) { - return this.selectionLayers[goog.getUid(layer)].getVisible(); -}; - - /** * @param {Array.>} featuresByLayer Features by layer. * @param {Array.} layers The queried layers. @@ -210,24 +171,35 @@ ol.control.Select.prototype.layerFilterFunction = function(layer, index) { ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { var layer = layers[i]; - var selectionLayer = - this.selectionLayers[goog.getUid(layer)]; + var layerId = goog.getUid(layer); + var selectionLayer = this.selectionLayers[layerId]; + if (!goog.isDef(selectionLayer)) { + selectionLayer = new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + style: layer.getStyle() + }); + selectionLayer.setTemporary(true); + this.getMap().addLayer(selectionLayer); + this.selectionLayers[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_[i]; + var featureMap = this.featureMap_[layerId]; for (var j = 0; j < numFeatures; ++j) { var feature = features[j]; - var uid = goog.getUid(feature); - var clone = featureMap[uid]; + var featureId = goog.getUid(feature); + var clone = featureMap[featureId]; if (clone) { // TODO: make toggle configurable unselectedFeatures.push(feature); featuresToRemove.push(clone); - delete featureMap[uid]; + delete featureMap[featureId]; } if (clear) { for (var f in featureMap) { @@ -235,11 +207,11 @@ ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { featuresToRemove.push(featureMap[f]); } featureMap = {}; - this.featureMap_[i] = featureMap; + this.featureMap_[layerId] = featureMap; } if (!clone) { clone = feature.clone(); - featureMap[uid] = clone; + featureMap[featureId] = clone; clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; selectedFeatures.push(feature); featuresToAdd.push(clone); @@ -261,15 +233,3 @@ ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { })); } }; - - -/** - * @inheritDoc - */ -ol.control.Select.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); - var layers = map.getLayers(); - goog.events.listen(layers, - [ol.CollectionEventType.ADD, ol.CollectionEventType.REMOVE], - this.handleLayerCollectionChange, false, this); -}; diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js index 1191b29159..3bce16ba7d 100644 --- a/test/spec/ol/control/selectcontrol.test.js +++ b/test/spec/ol/control/selectcontrol.test.js @@ -1,7 +1,7 @@ goog.provide('ol.test.control.Select'); describe('ol.control.Select', function() { - var map, target, select, features; + var map, target, select, vector, features; beforeEach(function() { target = document.createElement('div'); @@ -27,10 +27,10 @@ describe('ol.control.Select', function() { } }] })); - var layer = new ol.layer.Vector({source: new ol.source.Vector({})}); - layer.addFeatures(features); + vector = new ol.layer.Vector({source: new ol.source.Vector({})}); + vector.addFeatures(features); select = new ol.control.Select({ - layers: [layer], + layerFilter: function(layer) { return layer === vector; }, map: map }); }); @@ -69,9 +69,11 @@ describe('ol.control.Select', function() { }); describe('#activate and #deactivate', function() { - it('adds a temp layer to the map only when active', function() { + it('adds a temp layer to the map only when active and in use', function() { expect(map.getLayers().getLength()).to.be(0); select.activate(); + expect(map.getLayers().getLength()).to.be(0); + select.select([features[0]], [vector]); expect(map.getLayers().getLength()).to.be(1); expect(map.getLayers().getAt(0).getTemporary()).to.be(true); select.deactivate(); @@ -97,24 +99,24 @@ describe('ol.control.Select', function() { describe('#select', function() { it('toggles selection of features', function() { - var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; - select.select([features], select.layers_); + select.select([features], [vector]); + var layer = select.selectionLayers[goog.getUid(vector)]; expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); - select.select([features], select.layers_); + select.select([features], [vector]); expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); }); it('can append features to an existing selection', function() { - var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; - select.select([[features[0]]], select.layers_); - select.select([[features[1]]], select.layers_); + select.select([[features[0]]], [vector]); + select.select([[features[1]]], [vector]); + var layer = select.selectionLayers[goog.getUid(vector)]; expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); }); it('can clear a selection before selecting new features', function() { - var layer = select.selectionLayers[goog.getUid(select.layers_[0])]; - select.select([[features[0]]], select.layers_, true); - select.select([[features[1]]], select.layers_, true); + select.select([[features[0]]], [vector], true); + select.select([[features[1]]], [vector], true); + var layer = select.selectionLayers[goog.getUid(vector)]; expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); }); From 100b85a7b0f578c2b189e2387f4d172af3da8c13 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 26 Aug 2013 17:03:14 +0200 Subject: [PATCH 22/39] Converting control to an interaction To dispatch events, the interaction base class now inherits from goog.events.EventTarget. --- examples/select-features.html | 48 +--- examples/select-features.js | 10 +- src/objectliterals.jsdoc | 16 +- src/ol/control/selectcontrol.exports | 3 - src/ol/control/selectcontrol.js | 235 ------------------ src/ol/interaction/interaction.js | 4 + src/ol/interaction/selectinteraction.exports | 2 + src/ol/interaction/selectinteraction.js | 180 ++++++++++++++ test/spec/ol/control/selectcontrol.test.js | 137 ---------- .../ol/interaction/selectinteraction.test.js | 84 +++++++ 10 files changed, 281 insertions(+), 438 deletions(-) delete mode 100644 src/ol/control/selectcontrol.exports delete mode 100644 src/ol/control/selectcontrol.js create mode 100644 src/ol/interaction/selectinteraction.exports create mode 100644 src/ol/interaction/selectinteraction.js delete mode 100644 test/spec/ol/control/selectcontrol.test.js create mode 100644 test/spec/ol/interaction/selectinteraction.test.js diff --git a/examples/select-features.html b/examples/select-features.html index 2c6428b330..0a05c475fb 100644 --- a/examples/select-features.html +++ b/examples/select-features.html @@ -9,52 +9,6 @@ Select features example - @@ -84,7 +38,7 @@

Select features example

-

Example of using the Select control. Select features by clicking polygons. Hold the Shift-key to add to the selection. Click the 'S' button to toggle the control's active state.

+

Example of using the Select interaction. Select features by clicking polygons. Hold the Shift-key to add to the selection.

See the select-features.js source to see how this is done.

diff --git a/examples/select-features.js b/examples/select-features.js index 268d21e92d..88f8512e39 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -1,8 +1,8 @@ goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); -goog.require('ol.control.Select'); -goog.require('ol.control.defaults'); +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'); @@ -46,12 +46,12 @@ var vector = new ol.layer.Vector({ }) }); -var selectControl = new ol.control.Select({ +var selectInteraction = new ol.interaction.Select({ layerFilter: function(layer) { return layer === vector; } }); var map = new ol.Map({ - controls: ol.control.defaults().extend([selectControl]), + interactions: ol.interaction.defaults().extend([selectInteraction]), layers: [raster, vector], renderer: ol.RendererHint.CANVAS, target: 'map', @@ -60,5 +60,3 @@ var map = new ol.Map({ zoom: 4 }) }); - -selectControl.activate(); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 0267e0b594..239ecf8a23 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -201,16 +201,6 @@ * Default is '' (empty string). */ -/** - * @typedef {Object} ol.control.SelectOptions - * @property {string|undefined} className CSS class name. Default is 'ol-select'. - * @property {Element|undefined} element Element. - * @property {undefined|function(ol.layer.Layer):boolean} layerFilter Filter - * function to restrict selection to a subset of layers. - * @property {ol.Map|undefined} map Map. - * @property {Element|undefined} target Target. - */ - /** * @typedef {Object} ol.control.ScaleLineOptions * @property {string|undefined} className CSS Class name. Default is 'ol-scale-line'. @@ -305,6 +295,12 @@ * @property {number|undefined} delta Delta. */ +/** + * @typedef {Object} ol.interaction.SelectOptions + * @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/control/selectcontrol.exports b/src/ol/control/selectcontrol.exports deleted file mode 100644 index ac9685b9bc..0000000000 --- a/src/ol/control/selectcontrol.exports +++ /dev/null @@ -1,3 +0,0 @@ -@exportClass ol.control.Select ol.control.SelectOptions -@exportProperty ol.control.Select.prototype.activate -@exportProperty ol.control.Select.prototype.deactivate diff --git a/src/ol/control/selectcontrol.js b/src/ol/control/selectcontrol.js deleted file mode 100644 index 17b6e9931e..0000000000 --- a/src/ol/control/selectcontrol.js +++ /dev/null @@ -1,235 +0,0 @@ -goog.provide('ol.control.Select'); - -goog.require('goog.array'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); -goog.require('goog.dom.classes'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); -goog.require('ol.MapBrowserEvent.EventType'); -goog.require('ol.control.Control'); -goog.require('ol.css'); -goog.require('ol.interaction.condition'); -goog.require('ol.layer.Vector'); -goog.require('ol.layer.VectorLayerRenderIntent'); -goog.require('ol.source.Vector'); - - -/** - * @typedef {{layer: ol.layer.Layer, - * selected: (Array.|undefined), - * type: goog.events.EventType, - * unselected: (Array.|undefined)}} - */ -ol.control.SelectEventObject; - - - -/** - * @constructor - * @extends {ol.control.Control} - * @param {ol.control.SelectOptions=} opt_options Options. - */ -ol.control.Select = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; - - /** - * @type {boolean} - * @private - */ - this.active_ = false; - - /** - * Mapping between original features and cloned features on selection layers. - * @type {Object.<*,Object.>} - * @private - */ - this.featureMap_ = {}; - - /** - * Mapping between original layers and selection layers. - * @type {Object.<*,ol.layer.Vector>} - * @protected - */ - this.selectionLayers = {}; - - /** - * @type {null|function(ol.layer.Layer):boolean} - * @private - */ - this.layerFilter_ = goog.isDef(options.layerFilter) ? - options.layerFilter : null; - - // TODO: css/button refactoring - var className = goog.isDef(options.className) ? options.className : - 'ol-select'; - - var element = goog.dom.createDom(goog.dom.TagName.DIV, { - 'class': className + ' ' + ol.css.CLASS_UNSELECTABLE - }); - var button = goog.dom.createDom(goog.dom.TagName.A, { - 'href': '#Select' - }); - goog.dom.appendChild(element, button); - - goog.events.listen(element, [ - goog.events.EventType.TOUCHEND, - goog.events.EventType.CLICK - ], this.toggleActive_, false, this); - - goog.base(this, { - element: element, - map: options.map, - target: options.target - }); -}; -goog.inherits(ol.control.Select, ol.control.Control); - - -/** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @private - */ -ol.control.Select.prototype.toggleActive_ = function(browserEvent) { - // prevent #Select anchor from getting appended to the url - browserEvent.preventDefault(); - if (this.active_) { - this.deactivate(); - } else { - this.activate(); - } -}; - - -/** - * Activate the control. - */ -ol.control.Select.prototype.activate = function() { - if (!this.active_) { - this.active_ = true; - goog.dom.classes.add(this.element, 'active'); - var map = this.getMap(); - for (var i in this.selectionLayers) { - map.addLayer(this.selectionLayers[i]); - } - - // TODO: Implement box selection - this.listenerKeys.push( - goog.events.listen(map, ol.MapBrowserEvent.EventType.CLICK, - this.handleClick, true, this)); - } -}; - - -/** - * Dectivate the control. - */ -ol.control.Select.prototype.deactivate = function() { - if (this.active_) { - if (!goog.array.isEmpty(this.listenerKeys)) { - goog.array.forEach(this.listenerKeys, goog.events.unlistenByKey); - this.listenerKeys.length = 0; - } - var map = this.getMap(); - for (var i in this.selectionLayers) { - map.removeLayer(this.selectionLayers[i]); - } - goog.dom.classes.remove(this.element, 'active'); - this.active_ = false; - } -}; - - -/** - * @param {ol.MapBrowserEvent} evt Event. - */ -ol.control.Select.prototype.handleClick = function(evt) { - var map = this.getMap(); - var layers = map.getLayerGroup().getLayersArray(); - if (!goog.isNull(this.layerFilter_)) { - layers = goog.array.filter(layers, this.layerFilter_); - } - var clear = !ol.interaction.condition.shiftKeyOnly(evt.browserEvent); - - function select(featuresByLayer) { - this.select(featuresByLayer, layers, clear); - } - - map.getFeatures({ - layers: layers, - pixel: evt.getPixel(), - success: goog.bind(select, this) - }); -}; - - -/** - * @param {Array.>} featuresByLayer Features by layer. - * @param {Array.} layers The queried layers. - * @param {boolean} clear Whether the current layer content should be cleared. - */ -ol.control.Select.prototype.select = function(featuresByLayer, layers, clear) { - for (var i = 0, ii = featuresByLayer.length; i < ii; ++i) { - var layer = layers[i]; - var layerId = goog.getUid(layer); - var selectionLayer = this.selectionLayers[layerId]; - if (!goog.isDef(selectionLayer)) { - selectionLayer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), - style: layer.getStyle() - }); - selectionLayer.setTemporary(true); - this.getMap().addLayer(selectionLayer); - this.selectionLayers[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]; - 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]; - } - if (clear) { - for (var f in featureMap) { - unselectedFeatures.push(layer.getFeatureWithUid(f)); - featuresToRemove.push(featureMap[f]); - } - featureMap = {}; - this.featureMap_[layerId] = featureMap; - } - if (!clone) { - clone = feature.clone(); - featureMap[featureId] = clone; - clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; - 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); - this.dispatchEvent(/** @type {ol.control.SelectEventObject} */ ({ - layer: layer, - selected: selectedFeatures, - type: goog.events.EventType.CHANGE, - unselected: unselectedFeatures - })); - } -}; diff --git a/src/ol/interaction/interaction.js b/src/ol/interaction/interaction.js index 4fe73093b4..e751fe859e 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.events.EventTarget'); 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.events.EventTarget} */ ol.interaction.Interaction = function() { + goog.base(this); }; +goog.inherits(ol.interaction.Interaction, goog.events.EventTarget); /** diff --git a/src/ol/interaction/selectinteraction.exports b/src/ol/interaction/selectinteraction.exports new file mode 100644 index 0000000000..50dd4bd10c --- /dev/null +++ b/src/ol/interaction/selectinteraction.exports @@ -0,0 +1,2 @@ +@exportClass ol.interaction.Select ol.interaction.SelectOptions +@exportProperty ol.interaction.Select.prototype.dispose diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js new file mode 100644 index 0000000000..f86af24bd6 --- /dev/null +++ b/src/ol/interaction/selectinteraction.js @@ -0,0 +1,180 @@ +goog.provide('ol.interaction.Select'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.MapBrowserEvent.EventType'); +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'); + + +/** + * @typedef {{layer: ol.layer.Layer, + * map: ol.Map, + * selected: (Array.|undefined), + * type: goog.events.EventType, + * unselected: (Array.|undefined)}} + */ +ol.interaction.SelectEventObject; + + + +/** + * @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 : {}; + + /** + * 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) { + if (evt.type === ol.MapBrowserEvent.EventType.CLICK) { + 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(evt.browserEvent); + + var select = function(featuresByLayer) { + this.select(map, featuresByLayer, layers, clear); + }; + + map.getFeatures({ + layers: layers, + pixel: evt.getPixel(), + success: goog.bind(select, this) + }); + } + // 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)) { + goog.asserts.assertFunction(layer.getStyle, + 'At least one of the layers has no "getStyle()" function.'); + selectionLayer = new ol.layer.Vector({ + source: new ol.source.Vector({parser: null}), + style: layer.getStyle() + }); + 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 = feature.clone(); + featureMap[featureId] = clone; + clone.renderIntent = ol.layer.VectorLayerRenderIntent.SELECTED; + 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); + this.dispatchEvent(/** @type {ol.interaction.SelectEventObject} */ ({ + layer: layer, + map: map, + selected: selectedFeatures, + type: goog.events.EventType.CHANGE, + unselected: unselectedFeatures + })); + } +}; diff --git a/test/spec/ol/control/selectcontrol.test.js b/test/spec/ol/control/selectcontrol.test.js deleted file mode 100644 index 3bce16ba7d..0000000000 --- a/test/spec/ol/control/selectcontrol.test.js +++ /dev/null @@ -1,137 +0,0 @@ -goog.provide('ol.test.control.Select'); - -describe('ol.control.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.control.Select({ - layerFilter: function(layer) { return layer === vector; }, - map: map - }); - }); - - afterEach(function() { - goog.dispose(select); - goog.dispose(map); - document.body.removeChild(target); - select = null; - map = null; - target = null; - }); - - describe('DOM creation', function() { - - it('creates the expected DOM elements', function() { - var selectButtons = goog.dom.getElementsByClass('ol-select', target), - selectButton = selectButtons[0], - hasUnselectableCls; - - expect(selectButtons.length).to.be(1); - - hasUnselectableCls = goog.dom.classes.has(selectButton, - 'ol-unselectable'); - expect(hasUnselectableCls).to.be(true); - }); - - it('has an .active class only when activated', function() { - var selectButton = goog.dom.getElementsByClass('ol-select', target)[0]; - select.activate(); - expect(goog.dom.classes.has(selectButton, 'active')).to.be(true); - select.deactivate(); - expect(goog.dom.classes.has(selectButton, 'active')).to.be(false); - }); - - }); - - describe('#activate and #deactivate', function() { - it('adds a temp layer to the map only when active and in use', function() { - expect(map.getLayers().getLength()).to.be(0); - select.activate(); - expect(map.getLayers().getLength()).to.be(0); - select.select([features[0]], [vector]); - expect(map.getLayers().getLength()).to.be(1); - expect(map.getLayers().getAt(0).getTemporary()).to.be(true); - select.deactivate(); - expect(map.getLayers().getLength()).to.be(0); - }); - it('has a private property so it knows if it is active', function() { - expect(select.active_).to.be(false); - select.activate(); - expect(select.active_).to.be(true); - select.deactivate(); - expect(select.active_).to.be(false); - }); - it('toggles active state on click', function() { - var selectButton = goog.dom.getElementsByClass('ol-select', target)[0]; - var event = new goog.events.BrowserEvent({type: 'click'}); - goog.events.fireListeners(selectButton, event.type, false, event); - expect(select.active_).to.be(true); - goog.events.fireListeners(selectButton, event.type, false, event); - expect(select.active_).to.be(false); - }); - }); - - describe('#select', function() { - - it('toggles selection of features', function() { - select.select([features], [vector]); - var layer = select.selectionLayers[goog.getUid(vector)]; - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(2); - select.select([features], [vector]); - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(0); - }); - - it('can append features to an existing selection', function() { - select.select([[features[0]]], [vector]); - select.select([[features[1]]], [vector]); - var layer = select.selectionLayers[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([[features[0]]], [vector], true); - select.select([[features[1]]], [vector], true); - var layer = select.selectionLayers[goog.getUid(vector)]; - expect(goog.object.getCount(layer.featureCache_.idLookup_)).to.be(1); - }); - - }); - -}); - -goog.require('goog.dispose'); -goog.require('goog.dom'); -goog.require('goog.dom.classes'); -goog.require('goog.events'); -goog.require('goog.events.BrowserEvent'); -goog.require('goog.object'); -goog.require('ol.Map'); -goog.require('ol.control.Select'); -goog.require('ol.layer.Vector'); -goog.require('ol.parser.GeoJSON'); -goog.require('ol.source.Vector'); 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'); From e5b095bfdf864375d6f4af667ad8a3b349d162cd Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 26 Aug 2013 17:03:52 +0200 Subject: [PATCH 23/39] Simplifying setRenderIntent API for bulk changing all features --- src/ol/layer/vectorlayer.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index ef2a3542a2..b8443eb164 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -592,9 +592,13 @@ ol.layer.Vector.prototype.removeFeatures = function(features) { /** * Changes the renderIntent for an array of features. * @param {string} renderIntent Render intent. - * @param {Array.} features Features to change the renderIntent for. + * @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, features) { +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) { From bb93a86528270d142f447db98221cfbc1fe7adcf Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 26 Aug 2013 17:05:33 +0200 Subject: [PATCH 24/39] Do not export 'dispose' for now --- src/ol/interaction/selectinteraction.exports | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ol/interaction/selectinteraction.exports b/src/ol/interaction/selectinteraction.exports index 50dd4bd10c..a5cd323f10 100644 --- a/src/ol/interaction/selectinteraction.exports +++ b/src/ol/interaction/selectinteraction.exports @@ -1,2 +1 @@ @exportClass ol.interaction.Select ol.interaction.SelectOptions -@exportProperty ol.interaction.Select.prototype.dispose From 89bdd3bc2c3ac485723fb2bb788e80fc87a9bd0a Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Wed, 28 Aug 2013 15:33:15 +0200 Subject: [PATCH 25/39] Use a ol.interaction.condition in ol.interaction.Select --- src/objectliterals.jsdoc | 3 ++- src/ol/interaction/condition.js | 11 ++++++++++- src/ol/interaction/selectinteraction.js | 14 +++++++++++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 239ecf8a23..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. */ /** @@ -297,6 +297,7 @@ /** * @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. */ diff --git a/src/ol/interaction/condition.js b/src/ol/interaction/condition.js index 78719a8c89..78f5d4fb11 100644 --- a/src/ol/interaction/condition.js +++ b/src/ol/interaction/condition.js @@ -1,7 +1,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 +42,15 @@ ol.interaction.condition.altShiftKeysOnly = function(browserEvent) { ol.interaction.condition.always = goog.functions.TRUE; +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @return {boolean} True if only the shift key is pressed. + */ +ol.interaction.condition.clickEventOnly = 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/selectinteraction.js b/src/ol/interaction/selectinteraction.js index f86af24bd6..095f271ee1 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -4,7 +4,7 @@ goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.events.EventType'); -goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Interaction'); goog.require('ol.interaction.condition'); goog.require('ol.layer.Vector'); @@ -31,6 +31,13 @@ ol.interaction.SelectEventObject; 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.clickEventOnly; + /** * Mapping between original features and cloned features on selection layers. * @type {Object.<*,Object.<*,ol.Feature>>} @@ -76,13 +83,14 @@ ol.interaction.Select.prototype.disposeInternal = function() { * @inheritDoc. */ ol.interaction.Select.prototype.handleMapBrowserEvent = function(evt) { - if (evt.type === ol.MapBrowserEvent.EventType.CLICK) { + 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(evt.browserEvent); + var clear = !ol.interaction.condition.shiftKeyOnly(browserEvent); var select = function(featuresByLayer) { this.select(map, featuresByLayer, layers, clear); From c47634b2ee284cfb7dd9ba95e3a1bfeed9073d8f Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 16:55:42 +0200 Subject: [PATCH 26/39] Renaming condition to clickOnly, and fixing docs --- src/ol/interaction/condition.js | 4 ++-- src/ol/interaction/selectinteraction.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ol/interaction/condition.js b/src/ol/interaction/condition.js index 78f5d4fb11..8d171688d6 100644 --- a/src/ol/interaction/condition.js +++ b/src/ol/interaction/condition.js @@ -44,9 +44,9 @@ ol.interaction.condition.always = goog.functions.TRUE; /** * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @return {boolean} True if only the shift key is pressed. + * @return {boolean} True only the event is a click event. */ -ol.interaction.condition.clickEventOnly = function(browserEvent) { +ol.interaction.condition.clickOnly = function(browserEvent) { return browserEvent.type == goog.events.EventType.CLICK; }; diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 095f271ee1..7b44ad03f0 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -36,7 +36,7 @@ ol.interaction.Select = function(opt_options) { * @type {ol.interaction.ConditionType} */ this.condition_ = goog.isDef(options.condition) ? - options.condition : ol.interaction.condition.clickEventOnly; + options.condition : ol.interaction.condition.clickOnly; /** * Mapping between original features and cloned features on selection layers. From 526dbebadc9e0139e71c5aaae95b1e539bbe38ff Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 17:18:38 +0200 Subject: [PATCH 27/39] Re-adding `@requires` that got lost during rebase. --- src/ol/interaction/condition.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ol/interaction/condition.js b/src/ol/interaction/condition.js index 8d171688d6..70702ab5db 100644 --- a/src/ol/interaction/condition.js +++ b/src/ol/interaction/condition.js @@ -1,6 +1,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'); From 0fbf07fef116706863e6c34209370fdfac616a1a Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 18:00:09 +0200 Subject: [PATCH 28/39] Using a renderIntent lib function instead of this identifier A 'this' identifier is quite useless with compressed JavaScript, and in fact it turned out to fail in advanced mode when trying to access a feature's renderIntent property with it. The added renderIntent lib function as a Call expression does the job well. --- examples/select-features.js | 2 +- src/ol/expr/expression.js | 12 ++++++++++++ src/ol/expr/expressions.js | 22 ---------------------- src/ol/expr/lexer.js | 3 +-- src/ol/expr/parser.js | 13 ------------- test/spec/ol/expr/expression.test.js | 24 ++++++++++++++++++------ test/spec/ol/expr/expressions.test.js | 20 -------------------- 7 files changed, 32 insertions(+), 64 deletions(-) diff --git a/examples/select-features.js b/examples/select-features.js index 88f8512e39..4145b6f25c 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -25,7 +25,7 @@ var vector = new ol.layer.Vector({ style: new ol.style.Style({ rules: [ new ol.style.Rule({ - filter: 'this.renderIntent == "selected"', + filter: 'renderIntent("selected")', symbolizers: [ new ol.style.Fill({ color: '#ffffff', 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/expr/expressions.js b/src/ol/expr/expressions.js index 1b42603ee6..f98d595b8e 100644 --- a/src/ol/expr/expressions.js +++ b/src/ol/expr/expressions.js @@ -10,7 +10,6 @@ goog.provide('ol.expr.Math'); goog.provide('ol.expr.MathOp'); goog.provide('ol.expr.Member'); goog.provide('ol.expr.Not'); -goog.provide('ol.expr.ThisIdentifier'); @@ -625,24 +624,3 @@ ol.expr.Not.prototype.evaluate = function(opt_scope, opt_fns, opt_this) { ol.expr.Not.prototype.getArgument = function() { return this.argument_; }; - - - -/** - * An identifier for the 'this' keyword. - * - * @constructor - * @extends {ol.expr.Expression} - */ -ol.expr.ThisIdentifier = function() {}; -goog.inherits(ol.expr.ThisIdentifier, ol.expr.Expression); -goog.addSingletonGetter(ol.expr.ThisIdentifier); - - -/** - * @inheritDoc - */ -ol.expr.ThisIdentifier.prototype.evaluate = - function(opt_scope, opt_fns, opt_this) { - return opt_this; -}; diff --git a/src/ol/expr/lexer.js b/src/ol/expr/lexer.js index 41a69eb8c6..d0fa744a22 100644 --- a/src/ol/expr/lexer.js +++ b/src/ol/expr/lexer.js @@ -516,8 +516,7 @@ ol.expr.Lexer.prototype.scanIdentifier_ = function(code) { if (id.length === 1) { type = ol.expr.TokenType.IDENTIFIER; } else if (this.isKeyword_(id)) { - type = (id === 'this') ? - ol.expr.TokenType.THIS_IDENTIFIER : ol.expr.TokenType.KEYWORD; + type = ol.expr.TokenType.KEYWORD; } else if (id === 'null') { type = ol.expr.TokenType.NULL_LITERAL; } else if (id === 'true' || id === 'false') { diff --git a/src/ol/expr/parser.js b/src/ol/expr/parser.js index 03be0dd75b..f11cdd5603 100644 --- a/src/ol/expr/parser.js +++ b/src/ol/expr/parser.js @@ -30,7 +30,6 @@ goog.require('ol.expr.Math'); goog.require('ol.expr.MathOp'); goog.require('ol.expr.Member'); goog.require('ol.expr.Not'); -goog.require('ol.expr.ThisIdentifier'); goog.require('ol.expr.Token'); goog.require('ol.expr.TokenType'); goog.require('ol.expr.UnexpectedToken'); @@ -199,16 +198,6 @@ ol.expr.Parser.prototype.createMemberExpression_ = function(object, }; -/** - * Create a 'this' identifier. - * @return {ol.expr.ThisIdentifier} The 'this' identifier. - * @private - */ -ol.expr.Parser.prototype.createThisIdentifier_ = function() { - return ol.expr.ThisIdentifier.getInstance(); -}; - - /** * Create a unary expression. The only true unary operator supported here is * "!". For +/-, we apply the operator to literal expressions and return @@ -445,8 +434,6 @@ ol.expr.Parser.prototype.parsePrimaryExpression_ = function(lexer) { expr = this.createLiteral_(token.value === 'true'); } else if (type === ol.expr.TokenType.NULL_LITERAL) { expr = this.createLiteral_(null); - } else if (type === ol.expr.TokenType.THIS_IDENTIFIER) { - expr = this.createThisIdentifier_(); } else { throw new ol.expr.UnexpectedToken(token); } diff --git a/test/spec/ol/expr/expression.test.js b/test/spec/ol/expr/expression.test.js index b358ecb848..22927fa0ef 100644 --- a/test/spec/ol/expr/expression.test.js +++ b/test/spec/ol/expr/expression.test.js @@ -92,12 +92,6 @@ describe('ol.expr.parse()', function() { expect(expr.evaluate(scope)).to.be(42); }); - it('parses member expressions in the \'this\' scope', function() { - var expr = ol.expr.parse('this.foo'); - var thisScope = {foo: 'bar'}; - expect(expr.evaluate(undefined, undefined, thisScope)).to.be('bar'); - }); - it('consumes whitespace as expected', function() { var expr = ol.expr.parse(' foo . bar . baz '); expect(expr).to.be.a(ol.expr.Member); @@ -878,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/expr/expressions.test.js b/test/spec/ol/expr/expressions.test.js index 3a6f07a743..c64ea7cd52 100644 --- a/test/spec/ol/expr/expressions.test.js +++ b/test/spec/ol/expr/expressions.test.js @@ -624,25 +624,6 @@ describe('ol.expr.Not', function() { }); -describe('ol.expr.ThisIdentifier', function() { - - describe('#getInstance()', function() { - it('has a getInstance method to return the singleton', function() { - expect(ol.expr.ThisIdentifier.getInstance()) - .to.be.a(ol.expr.ThisIdentifier); - }); - }); - - describe('#evaluate()', function() { - it('evaluates to the passed scope', function() { - expect(ol.expr.ThisIdentifier.getInstance() - .evaluate(undefined, undefined, 'foo')).to.be('foo'); - }); - }); - -}); - - goog.require('ol.expr.Call'); goog.require('ol.expr.Comparison'); goog.require('ol.expr.ComparisonOp'); @@ -655,4 +636,3 @@ goog.require('ol.expr.Math'); goog.require('ol.expr.MathOp'); goog.require('ol.expr.Member'); goog.require('ol.expr.Not'); -goog.require('ol.expr.ThisIdentifier'); From 2394b39f6fc044737477146c078d5fd24b143217 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 19:16:18 +0200 Subject: [PATCH 29/39] Use select as keyword --- examples/select-features.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/select-features.html b/examples/select-features.html index 0a05c475fb..2ff31fb9dd 100644 --- a/examples/select-features.html +++ b/examples/select-features.html @@ -42,7 +42,7 @@

See the select-features.js source to see how this is done.

-
SelectFeature, vector
+
select, vector
From 0c1f2328f9fcfecbc7cd6d9982d5e214cc8dff28 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 19:18:17 +0200 Subject: [PATCH 30/39] Do not implement a specific clone method What we need here is a mix of deep and shallow cloning, and we do not want to do this in a generic ol.Feature#clone() method. --- src/ol/feature.js | 16 +--------------- src/ol/interaction/selectinteraction.js | 7 +++++-- test/spec/ol/feature.test.js | 18 ------------------ 3 files changed, 6 insertions(+), 35 deletions(-) diff --git a/src/ol/feature.js b/src/ol/feature.js index 69ca5aea0d..ad4465cce4 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -51,20 +51,6 @@ ol.Feature = function(opt_values) { goog.inherits(ol.Feature, ol.Object); -/** - * Create a clone of this feature. Both the feature and its geometry will be - * cloned. - * @return {ol.Feature} A clone of this feature, with a cloned geometry. - */ -ol.Feature.prototype.clone = function() { - var clone = new ol.Feature(this.getAttributes()); - clone.setGeometry(this.getGeometry().clone()); - clone.featureId_ = this.featureId_; - clone.symbolizers_ = this.symbolizers_; - return clone; -}; - - /** * Gets a copy of the attributes of this feature. * @return {Object.} Attributes object. @@ -130,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/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 7b44ad03f0..559f8b1b39 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -162,9 +162,12 @@ ol.interaction.Select.prototype.select = featuresToRemove.push(clone); delete featureMap[featureId]; } else if (!(featureId in oldFeatureMap)) { - clone = feature.clone(); - featureMap[featureId] = clone; + 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); } diff --git a/test/spec/ol/feature.test.js b/test/spec/ol/feature.test.js index fb31212579..12ae10ba37 100644 --- a/test/spec/ol/feature.test.js +++ b/test/spec/ol/feature.test.js @@ -34,24 +34,6 @@ describe('ol.Feature', function() { }); - describe('#clone()', function() { - - it('creates a clone with a cloned geometry', function() { - var feature = new ol.Feature({ - loc: new ol.geom.Point([10, 20]), - foo: 'bar' - }); - feature.setFeatureId('foo'); - var clone = feature.clone(); - expect(clone).to.not.be(feature); - expect(clone.get('foo')).to.be('bar'); - expect(clone.getFeatureId()).to.be('foo'); - expect(clone.getGeometry()).to.not.be(feature.getGeometry()); - expect(clone.getGeometry().getCoordinates()).to.eql([10, 20]); - }); - - }); - describe('#get()', function() { it('returns values set at construction', function() { From 95cf0e12648eccf38047866c31d62d7fe6dac838 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 19:18:40 +0200 Subject: [PATCH 31/39] Less closures --- src/ol/interaction/selectinteraction.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 559f8b1b39..2a9a984193 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -92,14 +92,15 @@ ol.interaction.Select.prototype.handleMapBrowserEvent = function(evt) { } var clear = !ol.interaction.condition.shiftKeyOnly(browserEvent); + var that = this; var select = function(featuresByLayer) { - this.select(map, featuresByLayer, layers, clear); + that.select(map, featuresByLayer, layers, clear); }; map.getFeatures({ layers: layers, pixel: evt.getPixel(), - success: goog.bind(select, this) + success: select }); } // TODO: Implement box selection From 43c67ba09fdb12b231b3815fee9df36c38d2a7a9 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 19:19:07 +0200 Subject: [PATCH 32/39] Do not dispatch selection events for now --- src/ol/interaction/selectinteraction.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 2a9a984193..4290dc4fac 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -2,8 +2,7 @@ goog.provide('ol.interaction.Select'); goog.require('goog.array'); goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.EventType'); +goog.require('ol.Feature'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Interaction'); goog.require('ol.interaction.condition'); @@ -12,16 +11,6 @@ goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.source.Vector'); -/** - * @typedef {{layer: ol.layer.Layer, - * map: ol.Map, - * selected: (Array.|undefined), - * type: goog.events.EventType, - * unselected: (Array.|undefined)}} - */ -ol.interaction.SelectEventObject; - - /** * @constructor @@ -181,12 +170,6 @@ ol.interaction.Select.prototype.select = } selectionLayer.removeFeatures(featuresToRemove); selectionLayer.addFeatures(featuresToAdd); - this.dispatchEvent(/** @type {ol.interaction.SelectEventObject} */ ({ - layer: layer, - map: map, - selected: selectedFeatures, - type: goog.events.EventType.CHANGE, - unselected: unselectedFeatures - })); + // TODO: Dispatch an event with selectedFeatures and unselectedFeatures } }; From 8a180e63b2eb009ef02916e1013fb3cb0533d861 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 28 Aug 2013 19:42:00 +0200 Subject: [PATCH 33/39] Filter layer by id --- examples/select-features.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/select-features.js b/examples/select-features.js index 4145b6f25c..636cd7ba43 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -18,6 +18,7 @@ var raster = new ol.layer.TileLayer({ }); 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' @@ -47,7 +48,7 @@ var vector = new ol.layer.Vector({ }); var selectInteraction = new ol.interaction.Select({ - layerFilter: function(layer) { return layer === vector; } + layerFilter: function(layer) { return layer.get('id') == 'vector'; } }); var map = new ol.Map({ From 6bb23cdda5e04e17639c409f08cbd2cd13c0d912 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 11:43:35 +0200 Subject: [PATCH 34/39] No longer inheriting from EventTarget --- src/ol/interaction/interaction.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/ol/interaction/interaction.js b/src/ol/interaction/interaction.js index e751fe859e..4fe73093b4 100644 --- a/src/ol/interaction/interaction.js +++ b/src/ol/interaction/interaction.js @@ -2,7 +2,6 @@ goog.provide('ol.interaction.Interaction'); -goog.require('goog.events.EventTarget'); goog.require('ol.MapBrowserEvent'); goog.require('ol.animation.pan'); goog.require('ol.animation.rotate'); @@ -13,12 +12,9 @@ goog.require('ol.easing'); /** * @constructor - * @extends {goog.events.EventTarget} */ ol.interaction.Interaction = function() { - goog.base(this); }; -goog.inherits(ol.interaction.Interaction, goog.events.EventTarget); /** From 2a4aef0b58830fbfb4f7d2d80444ce0b1f656fdd Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 11:44:52 +0200 Subject: [PATCH 35/39] THIS_IDENTIFIER no longer needed --- src/ol/expr/lexer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ol/expr/lexer.js b/src/ol/expr/lexer.js index d0fa744a22..c1af520c34 100644 --- a/src/ol/expr/lexer.js +++ b/src/ol/expr/lexer.js @@ -85,7 +85,6 @@ ol.expr.TokenType = { NUMERIC_LITERAL: 'Numeric', PUNCTUATOR: 'Punctuator', STRING_LITERAL: 'String', - THIS_IDENTIFIER: 'This', UNKNOWN: 'Unknown' }; From e2c4fec25332721e0d28e44d97445803060cd13b Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 12:00:06 +0200 Subject: [PATCH 36/39] Let's at least be disposable, so we can clean up after ourselves --- src/ol/interaction/interaction.js | 4 ++++ 1 file changed, 4 insertions(+) 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); /** From 83720975f8a277006eeb20857fd57f9d64e78684 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 12:10:55 +0200 Subject: [PATCH 37/39] More specific INTENTCHANGE instead of SYMBOLIZER event --- src/ol/layer/vectorlayer.js | 4 ++-- src/ol/renderer/canvas/canvasvectorlayerrenderer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index b8443eb164..31f1cfb5cd 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -238,7 +238,7 @@ ol.layer.VectorLayerEventType = { ADD: 'add', CHANGE: goog.events.EventType.CHANGE, REMOVE: 'remove', - SYMBOLIZER: 'symbolizer' + INTENTCHANGE: 'intentchange' }; @@ -612,7 +612,7 @@ ol.layer.Vector.prototype.setRenderIntent = this.dispatchEvent(/** @type {ol.layer.VectorLayerEventObject} */ ({ extent: extent, features: features, - type: ol.layer.VectorLayerEventType.SYMBOLIZER + type: ol.layer.VectorLayerEventType.INTENTCHANGE })); }; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index fa6091f008..eeb1b3cad7 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -90,7 +90,7 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) { ol.layer.VectorLayerEventType.ADD, ol.layer.VectorLayerEventType.CHANGE, ol.layer.VectorLayerEventType.REMOVE, - ol.layer.VectorLayerEventType.SYMBOLIZER + ol.layer.VectorLayerEventType.INTENTCHANGE ], this.handleLayerChange_, false, this); From 9dae49dc18819ebcc76af64eda93c84baee8c5a4 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 13:47:43 +0200 Subject: [PATCH 38/39] Symbolizer defaults for the select renderIntent --- src/ol/interaction/selectinteraction.js | 5 +---- src/ol/style/fillsymbolizer.js | 14 ++++++++++++-- src/ol/style/strokesymbolizer.js | 18 +++++++++++++++--- src/ol/style/style.js | 13 +++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 4290dc4fac..45fa13aef0 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -1,7 +1,6 @@ goog.provide('ol.interaction.Select'); goog.require('goog.array'); -goog.require('goog.asserts'); goog.require('ol.Feature'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Interaction'); @@ -114,11 +113,9 @@ ol.interaction.Select.prototype.select = } var selectionLayer = this.selectionLayers[mapId].layers[layerId]; if (!goog.isDef(selectionLayer)) { - goog.asserts.assertFunction(layer.getStyle, - 'At least one of the layers has no "getStyle()" function.'); selectionLayer = new ol.layer.Vector({ source: new ol.source.Vector({parser: null}), - style: layer.getStyle() + style: goog.isFunction(layer.getStyle) ? layer.getStyle() : null }); selectionLayer.setTemporary(true); map.addLayer(selectionLayer); 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(), From 643eb5c4cc7fde615fd9dfc437fe8aef0350f8eb Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 30 Aug 2013 14:46:32 +0200 Subject: [PATCH 39/39] Better initial extent --- examples/select-features.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/select-features.js b/examples/select-features.js index 636cd7ba43..ab54750dbd 100644 --- a/examples/select-features.js +++ b/examples/select-features.js @@ -57,7 +57,7 @@ var map = new ol.Map({ renderer: ol.RendererHint.CANVAS, target: 'map', view: new ol.View2D({ - center: [-10997171.194994785, 5206335.565590534], + center: [-11000000, 4600000], zoom: 4 }) });