diff --git a/examples/draw-features.js b/examples/draw-features.js index 637035f756..1d48f6d9b4 100644 --- a/examples/draw-features.js +++ b/examples/draw-features.js @@ -18,7 +18,7 @@ var raster = new ol.layer.Tile({ }); var vector = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), + source: new ol.source.Vector(), style: new ol.style.Style({ rules: [ new ol.style.Rule({ diff --git a/examples/icon.js b/examples/icon.js index 07ef45ce88..d41c2f5517 100644 --- a/examples/icon.js +++ b/examples/icon.js @@ -1,11 +1,12 @@ +goog.require('ol.Feature'); goog.require('ol.Map'); goog.require('ol.Overlay'); goog.require('ol.OverlayPositioning'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); +goog.require('ol.geom.Point'); goog.require('ol.layer.Tile'); goog.require('ol.layer.Vector'); -goog.require('ol.parser.GeoJSON'); goog.require('ol.source.TileJSON'); goog.require('ol.source.Vector'); goog.require('ol.style.Icon'); @@ -18,22 +19,6 @@ var raster = new ol.layer.Tile({ }) }); -var data = { - type: 'FeatureCollection', - features: [{ - type: 'Feature', - properties: { - name: 'Null Island', - population: 4000, - rainfall: 500 - }, - geometry: { - type: 'Point', - coordinates: [0, 0] - } - }] -}; - var style = new ol.style.Style({ symbolizers: [ new ol.style.Icon({ @@ -45,8 +30,14 @@ var style = new ol.style.Style({ var vector = new ol.layer.Vector({ source: new ol.source.Vector({ - parser: new ol.parser.GeoJSON(), - data: data + features: [ + new ol.Feature({ + name: 'Null Island', + population: 4000, + rainfall: 500, + geometry: new ol.geom.Point([0, 0]) + }) + ] }), style: style }); diff --git a/examples/style-rules.js b/examples/style-rules.js index c730704afc..c78bcb207f 100644 --- a/examples/style-rules.js +++ b/examples/style-rules.js @@ -1,11 +1,12 @@ +goog.require('ol.Feature'); goog.require('ol.Map'); goog.require('ol.RendererHint'); goog.require('ol.View2D'); goog.require('ol.control'); goog.require('ol.expr'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); goog.require('ol.layer.Vector'); -goog.require('ol.parser.GeoJSON'); -goog.require('ol.proj'); goog.require('ol.source.Vector'); goog.require('ol.style.Fill'); goog.require('ol.style.Rule'); @@ -62,108 +63,60 @@ var style = new ol.style.Style({rules: [ var vector = new ol.layer.Vector({ style: style, source: new ol.source.Vector({ - data: { - 'type': 'FeatureCollection', - 'features': [{ - 'type': 'Feature', - 'properties': { - 'color': '#BADA55', - 'where': 'inner' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[-10000000, -10000000], [10000000, 10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'color': '#BADA55', - 'where': 'inner' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[-10000000, 10000000], [10000000, -10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'color': '#013', - 'where': 'outer' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[-10000000, -10000000], [-10000000, 10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'color': '#013', - 'where': 'outer' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[-10000000, 10000000], [10000000, 10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'color': '#013', - 'where': 'outer' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[10000000, 10000000], [10000000, -10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'color': '#013', - 'where': 'outer' - }, - 'geometry': { - 'type': 'LineString', - 'coordinates': [[10000000, -10000000], [-10000000, -10000000]] - } - }, { - 'type': 'Feature', - 'properties': { - 'label': 'South' - }, - 'geometry': { - 'type': 'Point', - 'coordinates': [0, -6000000] - } - }, { - 'type': 'Feature', - 'properties': { - 'label': 'West' - }, - 'geometry': { - 'type': 'Point', - 'coordinates': [-6000000, 0] - } - }, { - 'type': 'Feature', - 'properties': { - 'label': 'North' - }, - 'geometry': { - 'type': 'Point', - 'coordinates': [0, 6000000] - } - }, { - 'type': 'Feature', - 'properties': { - 'label': 'East' - }, - 'geometry': { - 'type': 'Point', - 'coordinates': [6000000, 0] - } - }] - }, - parser: new ol.parser.GeoJSON(), - projection: ol.proj.get('EPSG:3857') + features: [ + new ol.Feature({ + color: '#BADA55', + where: 'inner', + geometry: new ol.geom.LineString( + [[-10000000, -10000000], [10000000, 10000000]]) + }), + new ol.Feature({ + color: '#BADA55', + where: 'inner', + geometry: new ol.geom.LineString( + [[-10000000, 10000000], [10000000, -10000000]]) + }), + new ol.Feature({ + color: '#013', + where: 'outer', + geometry: new ol.geom.LineString( + [[-10000000, -10000000], [-10000000, 10000000]]) + }), + new ol.Feature({ + color: '#013', + where: 'outer', + geometry: new ol.geom.LineString( + [[-10000000, 10000000], [10000000, 10000000]]) + }), + new ol.Feature({ + color: '#013', + where: 'outer', + geometry: new ol.geom.LineString( + [[10000000, 10000000], [10000000, -10000000]]) + }), + new ol.Feature({ + color: '#013', + where: 'outer', + geometry: new ol.geom.LineString( + [[10000000, -10000000], [-10000000, -10000000]]) + }), + new ol.Feature({ + label: 'South', + geometry: new ol.geom.Point([0, -6000000]) + }), + new ol.Feature({ + label: 'West', + geometry: new ol.geom.Point([-6000000, 0]) + }), + new ol.Feature({ + label: 'North', + geometry: new ol.geom.Point([0, 6000000]) + }), + new ol.Feature({ + label: 'East', + geometry: new ol.geom.Point([6000000, 0]) + }) + ] }) }); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index d416c8b63e..83e0e9a6e1 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -718,16 +718,19 @@ /** * @typedef {Object} ol.source.VectorOptions * @property {Array.|undefined} attributions Attributions. - * @property {Object|string|undefined} data Data to parse. + * @property {Array.|undefined} features Any features to be added + * to the source. Providing features is an alternative to providing + * `url` and `parser` options. * @property {ol.Extent|undefined} extent Extent. * @property {string|undefined} logo Logo. - * @property {ol.parser.Parser} parser Parser instance to parse data - * provided as `data` or fetched from `url`. + * @property {ol.parser.Parser|undefined} parser Parser instance to parse data + * fetched from `url`. * @property {ol.proj.ProjectionLike|undefined} projection Projection. Usually the * projection is provided by the parser, so this only needs to be set if * the parser does not know the SRS (e.g. in some GML flavors), or if the * projection determined by the parser needs to be overridden. - * @property {string|undefined} url Server url providing the vector data. + * @property {string|undefined} url Server url providing the vector data. If + * provided, the `parser` option must also be supplied. * @todo stability experimental */ diff --git a/src/ol/feature.js b/src/ol/feature.js index 787922c46d..cb9a478d63 100644 --- a/src/ol/feature.js +++ b/src/ol/feature.js @@ -1,6 +1,7 @@ goog.provide('ol.Feature'); goog.provide('ol.FeatureEvent'); goog.provide('ol.FeatureEventType'); +goog.provide('ol.FeatureRenderIntent'); goog.require('goog.events'); goog.require('goog.events.Event'); @@ -8,7 +9,6 @@ goog.require('goog.events.EventType'); goog.require('ol.Object'); goog.require('ol.geom.Geometry'); goog.require('ol.geom.GeometryEvent'); -goog.require('ol.layer.VectorLayerRenderIntent'); @@ -51,10 +51,10 @@ ol.Feature = function(opt_values) { /** * The render intent for this feature. - * @type {ol.layer.VectorLayerRenderIntent|string} + * @type {ol.FeatureRenderIntent|string} * @private */ - this.renderIntent_ = ol.layer.VectorLayerRenderIntent.DEFAULT; + this.renderIntent_ = ol.FeatureRenderIntent.DEFAULT; /** * @type {Array.} @@ -247,6 +247,18 @@ ol.Feature.prototype.setSymbolizers = function(symbolizers) { ol.Feature.DEFAULT_GEOMETRY = 'geometry'; +/** + * @enum {string} + */ +ol.FeatureRenderIntent = { + DEFAULT: 'default', + FUTURE: 'future', + HIDDEN: 'hidden', + SELECTED: 'selected', + TEMPORARY: 'temporary' +}; + + /** * @enum {string} */ diff --git a/src/ol/interaction/drawinteraction.js b/src/ol/interaction/drawinteraction.js index 274fa87977..8a698818a1 100644 --- a/src/ol/interaction/drawinteraction.js +++ b/src/ol/interaction/drawinteraction.js @@ -4,6 +4,7 @@ goog.require('goog.asserts'); goog.require('ol.Coordinate'); goog.require('ol.Feature'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.Map'); goog.require('ol.MapBrowserEvent'); goog.require('ol.MapBrowserEvent.EventType'); @@ -16,7 +17,6 @@ goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.interaction.Interaction'); goog.require('ol.layer.Vector'); -goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.source.Vector'); @@ -113,7 +113,7 @@ ol.interaction.Draw.prototype.setMap = function(map) { if (!goog.isNull(map)) { if (goog.isNull(this.sketchLayer_)) { var layer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}), + source: new ol.source.Vector(), style: this.layer_.getStyle() }); layer.setTemporary(true); @@ -233,7 +233,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) { var start = event.getCoordinate(); this.finishCoordinate_ = start; var sketchFeature = new ol.Feature(); - sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.SELECTED); + sketchFeature.setRenderIntent(ol.FeatureRenderIntent.SELECTED); var features = [sketchFeature]; var geometry; if (this.mode_ === ol.interaction.DrawMode.POINT) { @@ -242,7 +242,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) { var sketchPoint = new ol.Feature({ geom: new ol.geom.Point(start.slice()) }); - sketchPoint.setRenderIntent(ol.layer.VectorLayerRenderIntent.TEMPORARY); + sketchPoint.setRenderIntent(ol.FeatureRenderIntent.TEMPORARY); this.sketchPoint_ = sketchPoint; features.push(sketchPoint); @@ -256,7 +256,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) { sketchFeature.setGeometry(geometry); this.sketchFeature_ = sketchFeature; - this.sketchLayer_.addFeatures(features); + this.sketchLayer_.getVectorSource().addFeatures(features); }; @@ -325,7 +325,7 @@ ol.interaction.Draw.prototype.addToDrawing_ = function(event) { ol.interaction.Draw.prototype.finishDrawing_ = function(event) { var sketchFeature = this.abortDrawing_(); goog.asserts.assert(!goog.isNull(sketchFeature)); - sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT); + sketchFeature.setRenderIntent(ol.FeatureRenderIntent.DEFAULT); var geometry = sketchFeature.getGeometry(); var coordinates = geometry.getCoordinates(); if (this.mode_ === ol.interaction.DrawMode.LINESTRING) { @@ -344,7 +344,7 @@ ol.interaction.Draw.prototype.finishDrawing_ = function(event) { } else if (this.type_ === ol.geom.GeometryType.MULTIPOLYGON) { sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates])); } - this.layer_.addFeatures([sketchFeature]); + this.layer_.getVectorSource().addFeatures([sketchFeature]); }; @@ -363,7 +363,7 @@ ol.interaction.Draw.prototype.abortDrawing_ = function() { features.push(this.sketchPoint_); this.sketchPoint_ = null; } - this.sketchLayer_.removeFeatures(features); + this.sketchLayer_.getVectorSource().removeFeatures(features); } return sketchFeature; }; diff --git a/src/ol/interaction/modifyinteraction.js b/src/ol/interaction/modifyinteraction.js index e473be2f9c..692c0cd74d 100644 --- a/src/ol/interaction/modifyinteraction.js +++ b/src/ol/interaction/modifyinteraction.js @@ -6,6 +6,7 @@ goog.require('goog.events'); goog.require('goog.functions'); goog.require('ol.CollectionEventType'); goog.require('ol.Feature'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.MapBrowserEvent.EventType'); goog.require('ol.ViewHint'); goog.require('ol.coordinate'); @@ -18,9 +19,8 @@ goog.require('ol.geom.Polygon'); goog.require('ol.interaction.Drag'); goog.require('ol.layer.Layer'); goog.require('ol.layer.Vector'); -goog.require('ol.layer.VectorEventType'); -goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.source.Vector'); +goog.require('ol.source.VectorEventType'); goog.require('ol.structs.RBush'); @@ -59,6 +59,13 @@ ol.interaction.Modify = function(opt_options) { */ this.layerFilter_ = layerFilter; + /** + * Layer lookup. Keys source id to layer. + * @type {Object.} + * @private + */ + this.layerLookup_ = null; + /** * Temporary sketch layer. * @type {ol.layer.Vector} @@ -121,12 +128,13 @@ ol.interaction.Modify.prototype.setMap = function(map) { } if (!goog.isNull(map)) { + this.layerLookup_ = {}; if (goog.isNull(this.rBush_)) { this.rBush_ = new ol.structs.RBush(); } if (goog.isNull(this.sketchLayer_)) { var sketchLayer = new ol.layer.Vector({ - source: new ol.source.Vector({parser: null}) + source: new ol.source.Vector() }); this.sketchLayer_ = sketchLayer; sketchLayer.setTemporary(true); @@ -141,6 +149,7 @@ ol.interaction.Modify.prototype.setMap = function(map) { false, this); } else { // removing from a map, clean up + this.layerLookup_ = null; this.rBush_ = null; this.sketchLayer_ = null; } @@ -168,9 +177,11 @@ ol.interaction.Modify.prototype.handleLayerAdded_ = function(evt) { ol.interaction.Modify.prototype.addLayer_ = function(layer) { if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector && !layer.getTemporary()) { - this.addIndex_(layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter), + var source = layer.getVectorSource(); + this.layerLookup_[goog.getUid(source)] = layer; + this.addIndex_(source.getFeatures(ol.layer.Vector.selectedFeaturesFilter), layer); - goog.events.listen(layer, ol.layer.VectorEventType.INTENTCHANGE, + goog.events.listen(source, ol.source.VectorEventType.INTENTCHANGE, this.handleIntentChange_, false, this); } }; @@ -195,9 +206,11 @@ ol.interaction.Modify.prototype.handleLayerRemoved_ = function(evt) { ol.interaction.Modify.prototype.removeLayer_ = function(layer) { if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector && !layer.getTemporary()) { + var source = layer.getVectorSource(); + delete this.layerLookup_[goog.getUid(source)]; this.removeIndex_( - layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter)); - goog.events.unlisten(layer, ol.layer.VectorEventType.INTENTCHANGE, + source.getFeatures(ol.layer.Vector.selectedFeaturesFilter)); + goog.events.unlisten(source, ol.source.VectorEventType.INTENTCHANGE, this.handleIntentChange_, false, this); } }; @@ -248,17 +261,19 @@ ol.interaction.Modify.prototype.removeIndex_ = function(features) { /** * Listen for feature additions. - * @param {ol.layer.VectorEvent} evt Event object. + * @param {ol.source.VectorEvent} evt Event object. * @private */ ol.interaction.Modify.prototype.handleIntentChange_ = function(evt) { - var layer = evt.target; + var source = evt.target; + goog.asserts.assertInstanceof(source, ol.source.Vector); + var layer = this.layerLookup_[goog.getUid(source)]; goog.asserts.assertInstanceof(layer, ol.layer.Vector); var features = evt.features; for (var i = 0, ii = features.length; i < ii; ++i) { var feature = features[i]; var renderIntent = feature.getRenderIntent(); - if (renderIntent == ol.layer.VectorLayerRenderIntent.SELECTED) { + if (renderIntent == ol.FeatureRenderIntent.SELECTED) { this.addIndex_([feature], layer); } else { this.removeIndex_([feature]); @@ -322,7 +337,7 @@ ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ = if (goog.isNull(vertexFeature)) { vertexFeature = new ol.Feature({g: new ol.geom.Point(coordinates)}); this.vertexFeature_ = vertexFeature; - this.sketchLayer_.addFeatures([vertexFeature]); + this.sketchLayer_.getVectorSource().addFeatures([vertexFeature]); } else { var geometry = vertexFeature.getGeometry(); geometry.setCoordinates(coordinates); @@ -341,7 +356,7 @@ ol.interaction.Modify.prototype.handleDragStart = function(evt) { this.dragSegments_ = []; var vertexFeature = this.vertexFeature_; if (!goog.isNull(vertexFeature) && vertexFeature.getRenderIntent() != - ol.layer.VectorLayerRenderIntent.HIDDEN) { + ol.FeatureRenderIntent.HIDDEN) { var renderIntent = vertexFeature.getRenderIntent(); var insertVertices = []; var vertex = vertexFeature.getGeometry().getCoordinates(); @@ -360,7 +375,7 @@ ol.interaction.Modify.prototype.handleDragStart = function(evt) { original.setSymbolizers(feature.getSymbolizers()); feature.setOriginal(original); } - if (renderIntent == ol.layer.VectorLayerRenderIntent.TEMPORARY) { + if (renderIntent == ol.FeatureRenderIntent.TEMPORARY) { if (ol.coordinate.equals(segment[0], vertex)) { dragSegments.push([node, 0]); } else if (ol.coordinate.equals(segment[1], vertex)) { @@ -455,7 +470,7 @@ ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) { var vertexFeature = this.vertexFeature_; var rBush = this.rBush_; var nodes = rBush.getAllInExtent(box); - var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN; + var renderIntent = ol.FeatureRenderIntent.HIDDEN; if (nodes.length > 0) { nodes.sort(sortByDistance); var node = nodes[0]; @@ -469,10 +484,10 @@ ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) { var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1); var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2); var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); - renderIntent = ol.layer.VectorLayerRenderIntent.FUTURE; + renderIntent = ol.FeatureRenderIntent.FUTURE; if (dist <= 10) { vertex = squaredDist1 > squaredDist2 ? segment[1] : segment[0]; - renderIntent = ol.layer.VectorLayerRenderIntent.TEMPORARY; + renderIntent = ol.FeatureRenderIntent.TEMPORARY; } vertexFeature = this.createOrUpdateVertexFeature_(node.style, vertex); this.modifiable_ = true; diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 41ecf96085..6442cd9d5b 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -3,11 +3,11 @@ goog.provide('ol.interaction.Select'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('ol.Feature'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.events.ConditionType'); goog.require('ol.events.condition'); goog.require('ol.interaction.Interaction'); goog.require('ol.layer.Vector'); -goog.require('ol.layer.VectorLayerRenderIntent'); @@ -97,21 +97,21 @@ ol.interaction.Select.prototype.select = } var featuresToSelect = featuresByLayer[i]; - var selectedFeatures = layer.getFeatures( + var selectedFeatures = layer.getVectorSource().getFeatures( ol.layer.Vector.selectedFeaturesFilter); if (clear) { for (var j = selectedFeatures.length - 1; j >= 0; --j) { selectedFeatures[j].setRenderIntent( - ol.layer.VectorLayerRenderIntent.DEFAULT); + ol.FeatureRenderIntent.DEFAULT); } } for (var j = featuresToSelect.length - 1; j >= 0; --j) { var feature = featuresToSelect[j]; // TODO: Make toggle configurable feature.setRenderIntent(feature.getRenderIntent() == - ol.layer.VectorLayerRenderIntent.SELECTED ? - ol.layer.VectorLayerRenderIntent.DEFAULT : - ol.layer.VectorLayerRenderIntent.SELECTED); + ol.FeatureRenderIntent.SELECTED ? + ol.FeatureRenderIntent.DEFAULT : + ol.FeatureRenderIntent.SELECTED); } // TODO: Dispatch an event with selectedFeatures and unselectedFeatures } diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index 28d1df9d60..11feb64ecd 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -1,126 +1,14 @@ goog.provide('ol.layer.Vector'); -goog.provide('ol.layer.VectorEventType'); goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.events'); -goog.require('goog.events.Event'); goog.require('goog.object'); goog.require('ol.Feature'); -goog.require('ol.FeatureEventType'); -goog.require('ol.extent'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.layer.Layer'); -goog.require('ol.layer.VectorLayerRenderIntent'); -goog.require('ol.proj'); goog.require('ol.source.Vector'); -goog.require('ol.structs.RTree'); +goog.require('ol.source.VectorEventType'); goog.require('ol.style'); goog.require('ol.style.Style'); -goog.require('ol.style.TextLiteral'); - - - -/** - * @constructor - */ -ol.layer.FeatureCache = function() { - - /** - * @type {Object.} - * @private - */ - this.idLookup_; - - /** - * @type {ol.structs.RTree} - * @private - */ - this.rTree_; - - this.clear(); - -}; - - -/** - * Clear the cache. - */ -ol.layer.FeatureCache.prototype.clear = function() { - this.idLookup_ = {}; - this.rTree_ = new ol.structs.RTree(); -}; - - -/** - * Add a feature to the cache. - * @param {ol.Feature} feature Feature to be cached. - */ -ol.layer.FeatureCache.prototype.add = function(feature) { - var id = goog.getUid(feature).toString(), - geometry = feature.getGeometry(); - - this.idLookup_[id] = feature; - - // index by bounding box - if (!goog.isNull(geometry)) { - this.rTree_.insert(geometry.getBounds(), feature); - } -}; - - -/** - * @return {Object.} Object of features, keyed by id. - */ -ol.layer.FeatureCache.prototype.getFeaturesObject = function() { - return this.idLookup_; -}; - - -/** - * Get all features whose bounding box intersects the provided extent. - * - * @param {ol.Extent} extent Bounding extent. - * @return {Object.} Features. - */ -ol.layer.FeatureCache.prototype.getFeaturesObjectForExtent = function(extent) { - return this.rTree_.searchReturningObject(extent); -}; - - -/** - * Get features by ids. - * @param {Array.} ids Array of (internal) identifiers. - * @return {Array.} Array of features. - * @private - */ -ol.layer.FeatureCache.prototype.getFeaturesByIds_ = function(ids) { - var len = ids.length, - features = new Array(len), - i; - for (i = 0; i < len; ++i) { - features[i] = this.idLookup_[ids[i]]; - } - return features; -}; - - -/** - * Remove a feature from the cache. - * @param {ol.Feature} feature Feature. - * @param {ol.Extent=} opt_extent Optional extent (used when the current feature - * extent is different than the one in the index). - */ -ol.layer.FeatureCache.prototype.remove = function(feature, opt_extent) { - var id = goog.getUid(feature).toString(), - geometry = feature.getGeometry(); - - delete this.idLookup_[id]; - // index by bounding box - if (!goog.isNull(geometry)) { - var extent = goog.isDef(opt_extent) ? opt_extent : geometry.getBounds(); - this.rTree_.remove(extent, feature); - } -}; @@ -132,19 +20,15 @@ ol.layer.FeatureCache.prototype.remove = function(feature, opt_extent) { */ ol.layer.Vector = function(options) { - goog.base(this, /** @type {ol.layer.LayerOptions} */ (options)); + var baseOptions = /** @type {ol.layer.VectorLayerOptions} */ + (goog.object.clone(options)); /** * @private * @type {ol.style.Style} */ this.style_ = goog.isDef(options.style) ? options.style : null; - - /** - * @type {ol.layer.FeatureCache} - * @private - */ - this.featureCache_ = new ol.layer.FeatureCache(); + delete baseOptions.style; /** * @type {function(Array.):string} @@ -152,6 +36,7 @@ ol.layer.Vector = function(options) { */ this.transformFeatureInfo_ = goog.isDef(options.transformFeatureInfo) ? options.transformFeatureInfo : ol.layer.Vector.uidTransformFeatureInfo; + delete baseOptions.transformFeatureInfo; /** * True if this is a temporary layer. @@ -160,83 +45,11 @@ ol.layer.Vector = function(options) { */ this.temporary_ = false; + goog.base(this, /** @type {ol.layer.LayerOptions} */ (baseOptions)); }; goog.inherits(ol.layer.Vector, ol.layer.Layer); -/** - * @param {Array.} features Array of features. - */ -ol.layer.Vector.prototype.addFeatures = function(features) { - var extent = ol.extent.createEmpty(), - feature, geometry; - for (var i = 0, ii = features.length; i < ii; ++i) { - feature = features[i]; - this.featureCache_.add(feature); - geometry = feature.getGeometry(); - if (!goog.isNull(geometry)) { - ol.extent.extend(extent, geometry.getBounds()); - } - goog.events.listen(feature, ol.FeatureEventType.CHANGE, - this.handleFeatureChange_, false, this); - goog.events.listen(feature, ol.FeatureEventType.INTENTCHANGE, - this.handleIntentChange_, false, this); - } - this.dispatchEvent(new ol.layer.VectorEvent(ol.layer.VectorEventType.ADD, - features, [extent])); -}; - - -/** - * Listener for feature change events. - * @param {ol.FeatureEvent} evt The feature change event. - * @private - */ -ol.layer.Vector.prototype.handleFeatureChange_ = function(evt) { - goog.asserts.assertInstanceof(evt.target, ol.Feature); - var feature = /** @type {ol.Feature} */ (evt.target); - var extents = []; - if (!goog.isNull(evt.oldExtent)) { - extents.push(evt.oldExtent); - } - var geometry = feature.getGeometry(); - if (!goog.isNull(geometry)) { - this.featureCache_.remove(feature, evt.oldExtent); - this.featureCache_.add(feature); - extents.push(geometry.getBounds()); - } - this.dispatchEvent(new ol.layer.VectorEvent(ol.layer.VectorEventType.CHANGE, - [feature], extents)); -}; - - -/** - * Listener for render intent change events of features. - * @param {ol.FeatureEvent} evt The feature intent change event. - * @private - */ -ol.layer.Vector.prototype.handleIntentChange_ = function(evt) { - goog.asserts.assertInstanceof(evt.target, ol.Feature); - var feature = /** @type {ol.Feature} */ (evt.target); - var geometry = feature.getGeometry(); - if (!goog.isNull(geometry)) { - this.dispatchEvent(new ol.layer.VectorEvent( - ol.layer.VectorEventType.INTENTCHANGE, [feature], - [geometry.getBounds()])); - } -}; - - -/** - * Remove all features from the layer. - */ -ol.layer.Vector.prototype.clear = function() { - this.featureCache_.clear(); - this.dispatchEvent( - new ol.layer.VectorEvent(ol.layer.VectorEventType.REMOVE, [], [])); -}; - - /** * @return {boolean} Whether this layer is temporary. */ @@ -267,165 +80,10 @@ ol.layer.Vector.prototype.getStyle = function() { */ ol.layer.Vector.prototype.setStyle = function(style) { this.style_ = style; - this.dispatchEvent( - new ol.layer.VectorEvent(ol.layer.VectorEventType.CHANGE, [], [])); -}; - - -/** - * Returns an array of features that match a filter. This will not fetch data, - * it only considers features that are loaded already. - * @param {(function(ol.Feature):boolean)=} opt_filter Filter function. - * @return {Array.} Features that match the filter, or all features - * if no filter was provided. - */ -ol.layer.Vector.prototype.getFeatures = function(opt_filter) { - var result; - var features = this.featureCache_.getFeaturesObject(); - if (goog.isDef(opt_filter)) { - result = []; - for (var f in features) { - if (opt_filter(features[f]) === true) { - result.push(features[f]); - } - } - } else { - result = goog.object.getValues(features); - } - return result; -}; - - -/** - * Get all features whose bounding box intersects the provided extent. This - * method is intended for being called by the renderer. When null is returned, - * the renderer should not waste time rendering, and `opt_callback` is - * usually a function that requests a renderFrame, which will be called as soon - * as the data for `extent` is available. - * - * @param {ol.Extent} extent Bounding extent. - * @param {ol.proj.Projection} projection Target projection. - * @param {Function=} opt_callback Callback to call when data is parsed. - * @return {Object.} Features or null if source is loading - * data for `extent`. - */ -ol.layer.Vector.prototype.getFeaturesObjectForExtent = function(extent, - projection, opt_callback) { - var source = this.getSource(); - return source.prepareFeatures(this, extent, projection, opt_callback) == - ol.source.VectorLoadState.LOADING ? - null : - this.featureCache_.getFeaturesObjectForExtent(extent); -}; - - -/** - * @param {Object.} features Features. - * @param {number} resolution Map resolution. - * @return {Array.} symbolizers for features. Each array in this array - * contains 3 items: an array of features, the symbolizer literal, and - * an array with optional additional data for each feature. - */ -ol.layer.Vector.prototype.groupFeaturesBySymbolizerLiteral = - function(features, resolution) { - var uniqueLiterals = {}, - featuresBySymbolizer = [], - style = this.style_, - i, j, l, feature, symbolizers, literals, numLiterals, literal, - uniqueLiteral, key, item; - for (i in features) { - feature = features[i]; - // feature level symbolizers take precedence - symbolizers = feature.getSymbolizers(); - if (!goog.isNull(symbolizers)) { - literals = ol.style.Style.createLiterals(symbolizers, feature); - } else { - // layer style second - if (goog.isNull(style)) { - style = ol.style.getDefault(); - } - literals = style.createLiterals(feature, resolution); - } - numLiterals = literals.length; - for (j = 0; j < numLiterals; ++j) { - literal = literals[j]; - for (l in uniqueLiterals) { - uniqueLiteral = featuresBySymbolizer[uniqueLiterals[l]][1]; - if (literal.equals(uniqueLiteral)) { - literal = uniqueLiteral; - break; - } - } - key = goog.getUid(literal); - if (!goog.object.containsKey(uniqueLiterals, key)) { - uniqueLiterals[key] = featuresBySymbolizer.length; - featuresBySymbolizer.push([ - /** @type {Array.} */ ([]), - /** @type {ol.style.Literal} */ (literal), - /** @type {Array} */ ([]) - ]); - } - item = featuresBySymbolizer[uniqueLiterals[key]]; - item[0].push(feature); - if (literal instanceof ol.style.TextLiteral) { - item[2].push(literals[j].text); - } - } - } - featuresBySymbolizer.sort(this.sortByZIndex_); - return featuresBySymbolizer; -}; - - -/** - * @param {Object|Element|Document|string} data Feature data. - * @param {ol.parser.Parser} parser Feature parser. - * @param {ol.proj.Projection} projection This sucks. The layer should be a - * view in one projection. - */ -ol.layer.Vector.prototype.parseFeatures = function(data, parser, projection) { - - var addFeatures = function(data) { - var features = data.features; - var sourceProjection = this.getSource().getProjection(); - if (goog.isNull(sourceProjection)) { - sourceProjection = data.metadata.projection; - } - var transform = ol.proj.getTransform(sourceProjection, projection); - var geometry = null; - for (var i = 0, ii = features.length; i < ii; ++i) { - geometry = features[i].getGeometry(); - if (!goog.isNull(geometry)) { - geometry.transform(transform); - } - } - this.addFeatures(features); - }; - - var result; - if (goog.isString(data)) { - if (goog.isFunction(parser.readFeaturesFromStringAsync)) { - parser.readFeaturesFromStringAsync(data, goog.bind(addFeatures, this)); - } else { - goog.asserts.assert( - goog.isFunction(parser.readFeaturesFromString), - 'Expected parser with a readFeaturesFromString method.'); - result = parser.readFeaturesFromString(data); - addFeatures.call(this, result); - } - } else if (goog.isObject(data)) { - if (goog.isFunction(parser.readFeaturesFromObjectAsync)) { - parser.readFeaturesFromObjectAsync(data, goog.bind(addFeatures, this)); - } else { - goog.asserts.assert( - goog.isFunction(parser.readFeaturesFromObject), - 'Expected parser with a readFeaturesFromObject method.'); - result = parser.readFeaturesFromObject(data); - addFeatures.call(this, result); - } - } else { - // TODO: parse more data types - throw new Error('Data type not supported: ' + data); + var source = this.getVectorSource(); + if (source) { + source.dispatchEvent( + new ol.source.VectorEvent(ol.source.VectorEventType.CHANGE, [], [])); } }; @@ -438,30 +96,6 @@ ol.layer.Vector.prototype.getTransformFeatureInfo = function() { }; -/** - * Remove features from the layer. - * @param {Array.} features Features to remove. - */ -ol.layer.Vector.prototype.removeFeatures = function(features) { - var extent = ol.extent.createEmpty(), - feature, geometry; - for (var i = 0, ii = features.length; i < ii; ++i) { - feature = features[i]; - this.featureCache_.remove(feature); - geometry = feature.getGeometry(); - if (!goog.isNull(geometry)) { - ol.extent.extend(extent, geometry.getBounds()); - } - goog.events.unlisten(feature, ol.FeatureEventType.CHANGE, - this.handleFeatureChange_, false, this); - goog.events.unlisten(feature, ol.FeatureEventType.INTENTCHANGE, - this.handleIntentChange_, false, this); - } - this.dispatchEvent(new ol.layer.VectorEvent(ol.layer.VectorEventType.REMOVE, - features, [extent])); -}; - - /** * @param {boolean} temporary Whether this layer is temporary. */ @@ -470,18 +104,6 @@ ol.layer.Vector.prototype.setTemporary = function(temporary) { }; -/** - * Sort function for `groupFeaturesBySymbolizerLiteral`. - * @private - * @param {Array} a 1st item for the sort comparison. - * @param {Array} b 2nd item for the sort comparison. - * @return {number} Comparison result. - */ -ol.layer.Vector.prototype.sortByZIndex_ = function(a, b) { - return a[1].zIndex - b[1].zIndex; -}; - - /** * @param {Array.} features Features. * @return {string} Feature info. @@ -498,42 +120,5 @@ ol.layer.Vector.uidTransformFeatureInfo = function(features) { * @return {boolean} Whether the feature is selected. */ ol.layer.Vector.selectedFeaturesFilter = function(feature) { - return feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.SELECTED; -}; - - - -/** - * @constructor - * @extends {goog.events.Event} - * @param {string} type Event type. - * @param {Array.} features Features associated with the event. - * @param {Array.} extents Any extents associated with the event. - */ -ol.layer.VectorEvent = function(type, features, extents) { - - goog.base(this, type); - - /** - * @type {Array.} - */ - this.features = features; - - /** - * @type {Array.} - */ - this.extents = extents; - -}; -goog.inherits(ol.layer.VectorEvent, goog.events.Event); - - -/** - * @enum {string} - */ -ol.layer.VectorEventType = { - ADD: 'featureadd', - CHANGE: 'featurechange', - INTENTCHANGE: 'featureintentchange', - REMOVE: 'featureremove' + return feature.getRenderIntent() == ol.FeatureRenderIntent.SELECTED; }; diff --git a/src/ol/layer/vectorlayerrenderintent.js b/src/ol/layer/vectorlayerrenderintent.js deleted file mode 100644 index 6b84c28700..0000000000 --- a/src/ol/layer/vectorlayerrenderintent.js +++ /dev/null @@ -1,13 +0,0 @@ -goog.provide('ol.layer.VectorLayerRenderIntent'); - - -/** - * @enum {string} - */ -ol.layer.VectorLayerRenderIntent = { - DEFAULT: 'default', - FUTURE: 'future', - HIDDEN: 'hidden', - SELECTED: 'selected', - TEMPORARY: 'temporary' -}; diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index f47ca092ad..3874e0c8a0 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -1,11 +1,13 @@ goog.provide('ol.renderer.canvas.VectorLayer'); goog.require('goog.asserts'); +goog.require('goog.async.nextTick'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.object'); goog.require('goog.vec.Mat4'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.Pixel'); goog.require('ol.TileCache'); goog.require('ol.TileCoord'); @@ -14,10 +16,10 @@ goog.require('ol.ViewHint'); goog.require('ol.extent'); goog.require('ol.geom.GeometryType'); goog.require('ol.layer.Vector'); -goog.require('ol.layer.VectorEventType'); -goog.require('ol.layer.VectorLayerRenderIntent'); goog.require('ol.renderer.canvas.Layer'); goog.require('ol.renderer.canvas.Vector'); +goog.require('ol.source.VectorEventType'); +goog.require('ol.style'); goog.require('ol.tilegrid.TileGrid'); @@ -88,13 +90,16 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) { */ this.tileCache_ = new ol.TileCache( ol.renderer.canvas.VectorLayer.TILECACHE_SIZE); - goog.events.listen(layer, [ - ol.layer.VectorEventType.ADD, - ol.layer.VectorEventType.CHANGE, - ol.layer.VectorEventType.REMOVE, - ol.layer.VectorEventType.INTENTCHANGE + + var source = layer.getSource(); + goog.events.listen(source, [ + ol.source.VectorEventType.LOAD, + ol.source.VectorEventType.ADD, + ol.source.VectorEventType.CHANGE, + ol.source.VectorEventType.REMOVE, + ol.source.VectorEventType.INTENTCHANGE ], - this.handleLayerChange_, false, this); + this.handleSourceChange_, false, this); /** * @private @@ -243,12 +248,13 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = function(pixel, success, opt_error) { // TODO What do we want to pass to the error callback? var map = this.getMap(); - var result = []; + var features = []; - var layer = this.getLayer(); + var source = this.getVectorLayer().getSource(); var location = map.getCoordinateFromPixel(pixel); var tileCoord = this.tileGrid_.getTileCoordForCoordAndZ(location, 0); var key = tileCoord.toString(); + if (this.tileCache_.containsKey(key)) { var cachedTile = this.tileCache_.get(key); var symbolSizes = cachedTile[1]; @@ -258,24 +264,15 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = var halfMaxHeight = maxSymbolSize[1] / 2; var locationMin = [location[0] - halfMaxWidth, location[1] - halfMaxHeight]; var locationMax = [location[0] + halfMaxWidth, location[1] + halfMaxHeight]; - var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]); - var candidates = layer.getFeaturesObjectForExtent(locationBbox, - map.getView().getView2D().getProjection()); - if (goog.isNull(candidates)) { - // data is not loaded - if (goog.isDef(opt_error)) { - goog.global.setTimeout(function() { opt_error(); }, 0); - } - return; - } + var extent = ol.extent.boundingExtent([locationMin, locationMax]); + var projection = map.getView().getView2D().getProjection(); - var candidate, geom, type, symbolBounds, symbolSize, symbolOffset, - halfWidth, halfHeight, uid, coordinates, j; - for (var id in candidates) { - candidate = candidates[id]; - if (candidate.getRenderIntent() == - ol.layer.VectorLayerRenderIntent.HIDDEN) { - continue; + source.forEachFeatureInExtent(extent, projection, function(candidate) { + var geom, type, symbolBounds, symbolSize, symbolOffset, + halfWidth, halfHeight, uid, coordinates, j; + + if (candidate.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) { + return; } geom = candidate.getGeometry(); type = geom.getType(); @@ -300,34 +297,38 @@ ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = } for (j = coordinates.length - 1; j >= 0; --j) { if (ol.extent.containsCoordinate(symbolBounds, coordinates[j])) { - result.push(candidate); + features.push(candidate); break; } } } else if (goog.isFunction(geom.containsCoordinate)) { // For polygons, check if the pixel location is inside the polygon if (geom.containsCoordinate(location)) { - result.push(candidate); + features.push(candidate); } } else if (goog.isFunction(geom.distanceFromCoordinate)) { // For lines, check if the distance to the pixel location is // within the rendered line width if (2 * geom.distanceFromCoordinate(location) <= symbolSizes[goog.getUid(candidate)][0]) { - result.push(candidate); + features.push(candidate); } } - } + + }); } - goog.global.setTimeout(function() { success(result, layer); }, 0); + var layer = this.getLayer(); + goog.async.nextTick(function() { + success(features, layer); + }); }; /** - * @param {ol.layer.VectorEvent} event Vector layer event. + * @param {ol.source.VectorEvent} event Vector layer event. * @private */ -ol.renderer.canvas.VectorLayer.prototype.handleLayerChange_ = function(event) { +ol.renderer.canvas.VectorLayer.prototype.handleSourceChange_ = function(event) { if (goog.isDef(this.renderedResolution_)) { this.expireTiles_(event.extents); } @@ -348,7 +349,6 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = resolution = view2DState.resolution, projection = view2DState.projection, extent = frameState.extent, - layer = this.getVectorLayer(), tileGrid = this.tileGrid_, tileSize = [512, 512], idle = !frameState.viewHints[ol.ViewHint.ANIMATING] && @@ -424,6 +424,15 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = this.tileArchetype_.height = tileSize[1]; } + /** + * Let the source to know what data extent we want loaded. As there may + * already be features loaded, we continue with rendering after this request. + * If this results in loading new features, a new rendering will be triggered. + */ + var layer = this.getVectorLayer(); + var source = layer.getVectorSource(); + source.load(tileRangeExtent, projection); + /** * Prepare the sketch canvas. This covers the currently visible tile range * and will have rendered all newly visible features. @@ -469,10 +478,9 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = // TODO make gutter configurable? var tileGutter = 15 * tileResolution; var tile, tileCoord, key, x, y, i, type; - var deferred = false; var dirty = false; - var tileExtent, groups, group, j, numGroups, featuresObject, tileHasFeatures; - fetchTileData: + var tileExtent, featuresObject, tileHasFeatures; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { tileCoord = new ol.TileCoord(0, x, y); @@ -486,15 +494,11 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = tileExtent[1] -= tileGutter; tileExtent[3] += tileGutter; tileHasFeatures = false; - featuresObject = layer.getFeaturesObjectForExtent(tileExtent, - projection, this.requestMapRenderFrame_); - if (goog.isNull(featuresObject)) { - deferred = true; - break fetchTileData; - } - tileHasFeatures = tileHasFeatures || - !goog.object.isEmpty(featuresObject); - goog.object.extend(featuresToRender, featuresObject); + source.forEachFeatureInExtent( + tileExtent, projection, function(feature) { + featuresToRender[goog.getUid(feature)] = feature; + tileHasFeatures = true; + }); if (tileHasFeatures) { tilesOnSketchCanvas[key] = tileCoord; } @@ -505,10 +509,16 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame = } this.dirty_ = dirty; - groups = layer.groupFeaturesBySymbolizerLiteral(featuresToRender, - tileResolution); - numGroups = groups.length; - for (j = 0; j < numGroups; ++j) { + var style = layer.getStyle(); + if (goog.isNull(style)) { + style = ol.style.getDefault(); + } + var groups = style.groupFeaturesBySymbolizerLiteral( + featuresToRender, tileResolution); + var numGroups = groups.length; + var deferred = false; + var group; + for (var j = 0; j < numGroups; ++j) { group = groups[j]; deferred = sketchCanvasRenderer.renderFeatures(group[0], group[1], group[2]); diff --git a/src/ol/renderer/canvas/canvasvectorrenderer.js b/src/ol/renderer/canvas/canvasvectorrenderer.js index 30d7a3ebca..213f0e6cbc 100644 --- a/src/ol/renderer/canvas/canvasvectorrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorrenderer.js @@ -8,6 +8,7 @@ goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.vec.Mat4'); goog.require('ol.Feature'); +goog.require('ol.FeatureRenderIntent'); goog.require('ol.geom.AbstractCollection'); goog.require('ol.geom.Geometry'); goog.require('ol.geom.GeometryType'); @@ -17,7 +18,6 @@ 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'); @@ -160,7 +160,7 @@ ol.renderer.canvas.Vector.prototype.renderLineStringFeatures_ = context.beginPath(); for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) { continue; } id = goog.getUid(feature); @@ -249,7 +249,7 @@ ol.renderer.canvas.Vector.prototype.renderPointFeatures_ = context.globalAlpha = alpha; for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) { continue; } id = goog.getUid(feature); @@ -325,7 +325,7 @@ ol.renderer.canvas.Vector.prototype.renderText_ = for (var i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) { continue; } vecs = ol.renderer.canvas.Vector.getLabelVectors( @@ -393,7 +393,7 @@ ol.renderer.canvas.Vector.prototype.renderPolygonFeatures_ = context.beginPath(); for (i = 0, ii = features.length; i < ii; ++i) { feature = features[i]; - if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) { + if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) { continue; } geometry = feature.getGeometry(); diff --git a/src/ol/source/vectorsource.exports b/src/ol/source/vectorsource.exports index d7842f5b84..b864def335 100644 --- a/src/ol/source/vectorsource.exports +++ b/src/ol/source/vectorsource.exports @@ -1 +1,2 @@ @exportClass ol.source.Vector ol.source.VectorOptions +@exportProperty ol.source.Vector.prototype.addFeatures diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index 32afe70bfe..a2098845a4 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -1,8 +1,20 @@ +goog.provide('ol.source.FeatureCache'); goog.provide('ol.source.Vector'); +goog.provide('ol.source.VectorEventType'); +goog.provide('ol.source.VectorLoadState'); goog.require('goog.asserts'); +goog.require('goog.async.nextTick'); +goog.require('goog.events'); +goog.require('goog.events.Event'); goog.require('goog.net.XhrIo'); +goog.require('goog.object'); +goog.require('ol.Feature'); +goog.require('ol.FeatureEventType'); +goog.require('ol.extent'); +goog.require('ol.proj'); goog.require('ol.source.Source'); +goog.require('ol.structs.RTree'); /** @@ -20,22 +32,18 @@ ol.source.VectorLoadState = { /** * @constructor * @extends {ol.source.Source} - * @param {ol.source.VectorOptions} options Vector source options. + * @param {ol.source.VectorOptions=} opt_options Vector source options. * @todo stability experimental */ -ol.source.Vector = function(options) { +ol.source.Vector = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; - /** - * @private - * @type {Object|string} - */ - this.data_ = goog.isDef(options.data) ? options.data : null; - - /** - * @private - * @type {ol.source.VectorLoadState} - */ - this.loadState_ = ol.source.VectorLoadState.IDLE; + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + projection: options.projection + }); /** * @private @@ -49,48 +57,377 @@ ol.source.Vector = function(options) { */ this.url_ = options.url; - goog.base(this, { - attributions: options.attributions, - extent: options.extent, - logo: options.logo, - projection: options.projection - }); + /** + * @private + * @type {ol.source.VectorLoadState} + */ + this.loadState_ = goog.isDef(this.url_) ? + ol.source.VectorLoadState.IDLE : ol.source.VectorLoadState.LOADED; + + /** + * @type {ol.source.FeatureCache} + * @private + */ + this.featureCache_ = new ol.source.FeatureCache(); + + // add any user provided features + if (goog.isDef(options.features)) { + this.addFeatures(options.features); + } + }; goog.inherits(ol.source.Vector, ol.source.Source); /** - * @param {ol.layer.Vector} layer Layer that parses the data. - * @param {ol.Extent} extent Extent that needs to be fetched. - * @param {ol.proj.Projection} projection Projection of the view. - * @param {function()=} opt_callback Callback which is called when features are - * parsed after loading. - * @return {ol.source.VectorLoadState} The current load state. + * Request for new features to be loaded. + * @param {ol.Extent} extent Desired extent. + * @param {ol.proj.Projection} projection Desired projection. + * @return {boolean} New features will be loaded. */ -ol.source.Vector.prototype.prepareFeatures = function(layer, extent, projection, - opt_callback) { - // TODO: Implement strategies. BBOX aware strategies will need the extent. - if (goog.isDef(this.url_) && - this.loadState_ == ol.source.VectorLoadState.IDLE) { +ol.source.Vector.prototype.load = function(extent, projection) { + var requested = false; + if (this.loadState_ === ol.source.VectorLoadState.IDLE) { + goog.asserts.assertString(this.url_); this.loadState_ = ol.source.VectorLoadState.LOADING; goog.net.XhrIo.send(this.url_, goog.bind(function(event) { var xhr = event.target; if (xhr.isSuccess()) { - // TODO: Get source projection from data if supported by parser. - layer.parseFeatures(xhr.getResponseText(), this.parser_, projection); - this.loadState_ = ol.source.VectorLoadState.LOADED; - if (goog.isDef(opt_callback)) { - opt_callback(); - } + // parsing may be asynchronous, so we don't set load state here + this.parseFeaturesString_(xhr.getResponseText(), projection); } else { - // TODO: Error handling. this.loadState_ = ol.source.VectorLoadState.ERROR; } }, this)); - } else if (!goog.isNull(this.data_)) { - layer.parseFeatures(this.data_, this.parser_, projection); - this.data_ = null; - this.loadState_ = ol.source.VectorLoadState.LOADED; + requested = true; + } + return requested; +}; + + +/** + * Parse features from a string. + * @param {string} data Feature data. + * @param {ol.proj.Projection} projection The target projection. + * @private + */ +ol.source.Vector.prototype.parseFeaturesString_ = function(data, projection) { + if (goog.isFunction(this.parser_.readFeaturesFromStringAsync)) { + this.parser_.readFeaturesFromStringAsync(data, goog.bind(function(result) { + this.handleReadResult_(result, projection); + }, this)); + } else { + goog.asserts.assert( + goog.isFunction(this.parser_.readFeaturesFromString), + 'Expected parser with a readFeaturesFromString method.'); + this.handleReadResult_( + this.parser_.readFeaturesFromString(data), projection); + } +}; + + +/** + * Handle the read result from a parser. + * TODO: make parsers accept a target projection (see #1287) + * @param {ol.parser.ReadFeaturesResult} result Read features result. + * @param {ol.proj.Projection} projection The desired projection. + * @private + */ +ol.source.Vector.prototype.handleReadResult_ = function(result, projection) { + var features = result.features; + var sourceProjection = this.getProjection(); + if (goog.isNull(sourceProjection)) { + sourceProjection = result.metadata.projection; + } + var transform = ol.proj.getTransform(sourceProjection, projection); + var extent = ol.extent.createEmpty(); + var geometry = null; + var feature; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + geometry.transform(transform); + ol.extent.extend(extent, geometry.getBounds()); + } + this.loadFeature_(feature); + } + this.loadState_ = ol.source.VectorLoadState.LOADED; + // called in the next tick to normalize load event for sync/async parsing + goog.async.nextTick(function() { + this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.LOAD, + features, [extent])); + }, this); +}; + + +/** + * Load a feature. + * @param {ol.Feature} feature Feature to load. + * @private + */ +ol.source.Vector.prototype.loadFeature_ = function(feature) { + goog.events.listen(feature, ol.FeatureEventType.CHANGE, + this.handleFeatureChange_, false, this); + goog.events.listen(feature, ol.FeatureEventType.INTENTCHANGE, + this.handleIntentChange_, false, this); + this.featureCache_.add(feature); +}; + + +/** + * Add newly created features to the source. + * @param {Array.} features Array of features. + */ +ol.source.Vector.prototype.addFeatures = function(features) { + var extent = ol.extent.createEmpty(), + feature, geometry; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + this.loadFeature_(feature); + geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + ol.extent.extend(extent, geometry.getBounds()); + } + } + this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.ADD, + features, [extent])); +}; + + +/** + * Returns an array of features that match a filter. This will not fetch data, + * it only considers features that are loaded already. + * @param {(function(ol.Feature):boolean)=} opt_filter Filter function. + * @return {Array.} Features that match the filter, or all features + * if no filter was provided. + */ +ol.source.Vector.prototype.getFeatures = function(opt_filter) { + var result; + var features = this.featureCache_.getFeaturesObject(); + if (goog.isDef(opt_filter)) { + result = []; + for (var f in features) { + if (opt_filter(features[f]) === true) { + result.push(features[f]); + } + } + } else { + result = goog.object.getValues(features); + } + return result; +}; + + +/** + * Get all features whose bounding box intersects the provided extent. This + * method is intended for being called by the renderer. + * + * @param {ol.Extent} extent Bounding extent. + * @param {ol.proj.Projection} projection Target projection. + * @param {function(this: T, ol.Feature)} callback Callback called with each + * feature. + * @param {T=} opt_thisArg The object to be used as the value of 'this' for + * the callback. + * @template T + */ +ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, + projection, callback, opt_thisArg) { + // TODO: transform if requested project is different than loaded projection + this.featureCache_.forEach(extent, callback, opt_thisArg); +}; + + +/** + * Listener for feature change events. + * @param {ol.FeatureEvent} evt The feature change event. + * @private + */ +ol.source.Vector.prototype.handleFeatureChange_ = function(evt) { + goog.asserts.assertInstanceof(evt.target, ol.Feature); + var feature = /** @type {ol.Feature} */ (evt.target); + var extents = []; + if (!goog.isNull(evt.oldExtent)) { + extents.push(evt.oldExtent); + } + var geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + this.featureCache_.remove(feature, evt.oldExtent); + this.featureCache_.add(feature); + extents.push(geometry.getBounds()); + } + this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.CHANGE, + [feature], extents)); +}; + + +/** + * Listener for render intent change events of features. + * @param {ol.FeatureEvent} evt The feature intent change event. + * @private + */ +ol.source.Vector.prototype.handleIntentChange_ = function(evt) { + goog.asserts.assertInstanceof(evt.target, ol.Feature); + var feature = /** @type {ol.Feature} */ (evt.target); + var geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + this.dispatchEvent(new ol.source.VectorEvent( + ol.source.VectorEventType.INTENTCHANGE, [feature], + [geometry.getBounds()])); + } +}; + + +/** + * Remove features from the layer. + * @param {Array.} features Features to remove. + */ +ol.source.Vector.prototype.removeFeatures = function(features) { + var extent = ol.extent.createEmpty(), + feature, geometry; + for (var i = 0, ii = features.length; i < ii; ++i) { + feature = features[i]; + this.featureCache_.remove(feature); + geometry = feature.getGeometry(); + if (!goog.isNull(geometry)) { + ol.extent.extend(extent, geometry.getBounds()); + } + goog.events.unlisten(feature, ol.FeatureEventType.CHANGE, + this.handleFeatureChange_, false, this); + goog.events.unlisten(feature, ol.FeatureEventType.INTENTCHANGE, + this.handleIntentChange_, false, this); + } + this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.REMOVE, + features, [extent])); +}; + + + +/** + * @constructor + * @extends {goog.events.Event} + * @param {string} type Event type. + * @param {Array.} features Features associated with the event. + * @param {Array.} extents Any extents associated with the event. + */ +ol.source.VectorEvent = function(type, features, extents) { + + goog.base(this, type); + + /** + * @type {Array.} + */ + this.features = features; + + /** + * @type {Array.} + */ + this.extents = extents; + +}; +goog.inherits(ol.source.VectorEvent, goog.events.Event); + + +/** + * @enum {string} + */ +ol.source.VectorEventType = { + LOAD: 'featureload', + ADD: 'featureadd', + CHANGE: 'featurechange', + INTENTCHANGE: 'featureintentchange', + REMOVE: 'featureremove' +}; + + + +/** + * @constructor + */ +ol.source.FeatureCache = function() { + + /** + * @type {Object.} + * @private + */ + this.idLookup_; + + /** + * @type {ol.structs.RTree} + * @private + */ + this.rTree_; + + this.clear(); + +}; + + +/** + * Clear the cache. + */ +ol.source.FeatureCache.prototype.clear = function() { + this.idLookup_ = {}; + this.rTree_ = new ol.structs.RTree(); +}; + + +/** + * Add a feature to the cache. + * @param {ol.Feature} feature Feature to be cached. + */ +ol.source.FeatureCache.prototype.add = function(feature) { + var id = goog.getUid(feature).toString(), + geometry = feature.getGeometry(); + + this.idLookup_[id] = feature; + + // index by bounding box + if (!goog.isNull(geometry)) { + this.rTree_.insert(geometry.getBounds(), feature); + } +}; + + +/** + * @return {Object.} Object of features, keyed by id. + */ +ol.source.FeatureCache.prototype.getFeaturesObject = function() { + return this.idLookup_; +}; + + +/** + * Operate on each feature whose bounding box intersects the provided extent. + * + * @param {ol.Extent} extent Bounding extent. + * @param {function(this: T, ol.Feature)} callback Callback called with each + * feature. + * @param {T=} opt_thisArg The object to be used as the value of 'this' for + * the callback. + * @template T + */ +ol.source.FeatureCache.prototype.forEach = + function(extent, callback, opt_thisArg) { + this.rTree_.forEach( + extent, /** @type {function(Object)} */ (callback), opt_thisArg); +}; + + +/** + * Remove a feature from the cache. + * @param {ol.Feature} feature Feature. + * @param {ol.Extent=} opt_extent Optional extent (used when the current feature + * extent is different than the one in the index). + */ +ol.source.FeatureCache.prototype.remove = function(feature, opt_extent) { + var id = goog.getUid(feature).toString(), + geometry = feature.getGeometry(); + + delete this.idLookup_[id]; + // index by bounding box + if (!goog.isNull(geometry)) { + var extent = goog.isDef(opt_extent) ? opt_extent : geometry.getBounds(); + this.rTree_.remove(extent, feature); } - return this.loadState_; }; diff --git a/src/ol/structs/rtree.js b/src/ol/structs/rtree.js index 6057998bc6..fc314e79c3 100644 --- a/src/ol/structs/rtree.js +++ b/src/ol/structs/rtree.js @@ -499,8 +499,7 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { ol.structs.RTree.recalculateExtent_(tree); workingObject.target = undefined; if (tree.nodes.length < this.minWidth_) { // Underflow - workingObject.nodes = /** @type {Array} */ - (this.searchSubtree_(tree, true, [], tree)); + workingObject.nodes = this.searchSubtree_(tree, true, [], tree); } break; } else if (goog.isDef(lTree.nodes)) { @@ -528,15 +527,13 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { workingObject.nodes.length = 0; if (hitStack.length === 0 && tree.nodes.length <= 1) { // Underflow..on root! - workingObject.nodes = /** @type {Array} */ - (this.searchSubtree_(tree, true, workingObject.nodes, tree)); + this.searchSubtree_(tree, true, workingObject.nodes, tree); tree.nodes.length = 0; hitStack.push(tree); countStack.push(1); } else if (hitStack.length > 0 && tree.nodes.length < this.minWidth_) { // Underflow..AGAIN! - workingObject.nodes = /** @type {Array} */ - (this.searchSubtree_(tree, true, workingObject.nodes, tree)); + this.searchSubtree_(tree, true, workingObject.nodes, tree); tree.nodes.length = 0; } else { workingObject.nodes = undefined; // Just start resizing @@ -562,24 +559,24 @@ ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) { */ ol.structs.RTree.prototype.search = function(extent, opt_type) { var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Array} */ ( - this.searchSubtree_(rect, false, [], this.rootTree_, opt_type)); + return this.searchSubtree_(rect, false, [], this.rootTree_, opt_type); }; /** - * Non-recursive search function + * Search in the given extent and call the callback with each result. * - * @param {ol.Extent} extent Extent. - * @param {string|number=} opt_type Optional type of the objects we want to - * find. - * @return {Object} Result. Keys are UIDs of the values. + * @param {ol.Extent} extent Extent to search. + * @param {function(this: T, Object)} callback Callback called with each result. + * @param {T=} opt_thisArg The object to be used as the value of 'this' for + * the callback. * @this {ol.structs.RTree} + * @template T */ -ol.structs.RTree.prototype.searchReturningObject = function(extent, opt_type) { +ol.structs.RTree.prototype.forEach = function(extent, callback, opt_thisArg) { var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent}); - return /** @type {Object} */ ( - this.searchSubtree_(rect, false, [], this.rootTree_, opt_type, true)); + this.searchSubtree_( + rect, false, [], this.rootTree_, undefined, callback, opt_thisArg); }; @@ -588,17 +585,19 @@ ol.structs.RTree.prototype.searchReturningObject = function(extent, opt_type) { * * @param {ol.structs.RTreeNode} rect Rectangle. * @param {boolean} returnNode Do we return nodes? - * @param {Array|Object} result Result. + * @param {Array} result Result. * @param {ol.structs.RTreeNode} root Root. * @param {string|number=} opt_type Optional type to search for. - * @param {boolean=} opt_resultAsObject If set, result will be an object keyed - * by UID. + * @param {function(this: T, Object)=} opt_callback Callback called with each + * result. + * @param {T=} opt_thisArg The object to be used as the value of 'this' for + * the callback. * @private - * @return {Array|Object} Result. + * @template T + * @return {Array} Result. */ ol.structs.RTree.prototype.searchSubtree_ = function( - rect, returnNode, result, root, opt_type, opt_resultAsObject) { - var resultObject = {}; + rect, returnNode, result, root, opt_type, opt_callback, opt_thisArg) { var hitStack = []; // Contains the elements that overlap if (!ol.extent.intersects(rect.extent, root.extent)) { @@ -621,8 +620,8 @@ ol.structs.RTree.prototype.searchSubtree_ = function( // walk all the way in to the leaf to know that we don't need it if (!goog.isDef(opt_type) || lTree.type == opt_type) { var obj = lTree.leaf; - if (goog.isDef(opt_resultAsObject)) { - resultObject[goog.getUid(obj).toString()] = obj; + if (goog.isDef(opt_callback)) { + opt_callback.call(opt_thisArg, obj); } else { result.push(obj); } @@ -635,9 +634,5 @@ ol.structs.RTree.prototype.searchSubtree_ = function( } } while (hitStack.length > 0); - if (goog.isDef(opt_resultAsObject)) { - return resultObject; - } else { - return result; - } + return result; }; diff --git a/src/ol/style/style.js b/src/ol/style/style.js index c8d70ef635..e9bfee8657 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -15,6 +15,7 @@ goog.require('ol.style.Rule'); goog.require('ol.style.Shape'); goog.require('ol.style.Stroke'); goog.require('ol.style.Symbolizer'); +goog.require('ol.style.TextLiteral'); @@ -82,6 +83,71 @@ ol.style.Style.prototype.createLiterals = function(feature, resolution) { }; +/** + * @param {Object.} features Features. + * @param {number} resolution Map resolution. + * @return {Array.} symbolizers for features. Each array in this array + * contains 3 items: an array of features, the symbolizer literal, and + * an array with optional additional data for each feature. + */ +ol.style.Style.prototype.groupFeaturesBySymbolizerLiteral = + function(features, resolution) { + var uniqueLiterals = {}, + featuresBySymbolizer = [], + i, j, l, feature, symbolizers, literals, numLiterals, literal, + uniqueLiteral, key, item; + for (i in features) { + feature = features[i]; + // feature level symbolizers take precedence + symbolizers = feature.getSymbolizers(); + if (!goog.isNull(symbolizers)) { + literals = ol.style.Style.createLiterals(symbolizers, feature); + } else { + literals = this.createLiterals(feature, resolution); + } + numLiterals = literals.length; + for (j = 0; j < numLiterals; ++j) { + literal = literals[j]; + for (l in uniqueLiterals) { + uniqueLiteral = featuresBySymbolizer[uniqueLiterals[l]][1]; + if (literal.equals(uniqueLiteral)) { + literal = uniqueLiteral; + break; + } + } + key = goog.getUid(literal); + if (!goog.object.containsKey(uniqueLiterals, key)) { + uniqueLiterals[key] = featuresBySymbolizer.length; + featuresBySymbolizer.push([ + /** @type {Array.} */ ([]), + /** @type {ol.style.Literal} */ (literal), + /** @type {Array} */ ([]) + ]); + } + item = featuresBySymbolizer[uniqueLiterals[key]]; + item[0].push(feature); + if (literal instanceof ol.style.TextLiteral) { + item[2].push(literals[j].text); + } + } + } + featuresBySymbolizer.sort(this.sortByZIndex_); + return featuresBySymbolizer; +}; + + +/** + * Sort function for `groupFeaturesBySymbolizerLiteral`. + * @private + * @param {Array} a 1st item for the sort comparison. + * @param {Array} b 2nd item for the sort comparison. + * @return {number} Comparison result. + */ +ol.style.Style.prototype.sortByZIndex_ = function(a, b) { + return a[1].zIndex - b[1].zIndex; +}; + + /** * The default style. * @type {ol.style.Style} diff --git a/test/spec/ol/interaction/drawinteraction.test.js b/test/spec/ol/interaction/drawinteraction.test.js index f4c27f6b42..9f9867a7b2 100644 --- a/test/spec/ol/interaction/drawinteraction.test.js +++ b/test/spec/ol/interaction/drawinteraction.test.js @@ -1,7 +1,7 @@ goog.provide('ol.test.interaction.Draw'); describe('ol.interaction.Draw', function() { - var target, map, vector; + var target, map, source, layer; var width = 360; var height = 180; @@ -15,11 +15,12 @@ describe('ol.interaction.Draw', function() { style.width = width + 'px'; style.height = height + 'px'; document.body.appendChild(target); - vector = new ol.layer.Vector({source: new ol.source.Vector({})}); + source = new ol.source.Vector(); + layer = new ol.layer.Vector({source: source}); map = new ol.Map({ target: target, renderer: ol.RendererHint.CANVAS, - layers: [vector], + layers: [layer], view: new ol.View2D({ projection: 'EPSG:4326', center: [0, 0], @@ -56,7 +57,7 @@ describe('ol.interaction.Draw', function() { it('creates a new interaction', function() { var draw = new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.POINT }); expect(draw).to.be.a(ol.interaction.Draw); @@ -69,7 +70,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.POINT })); }); @@ -79,7 +80,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mousedown', 10, 20); simulateEvent('mouseup', 10, 20); simulateEvent('click', 10, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.Point); @@ -92,7 +93,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mousemove', 15, 20); simulateEvent('mouseup', 15, 20); simulateEvent('click', 15, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(0); }); @@ -102,7 +103,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.MULTIPOINT })); }); @@ -112,7 +113,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mousedown', 30, 15); simulateEvent('mouseup', 30, 15); simulateEvent('click', 30, 15); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.MultiPoint); @@ -125,7 +126,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.LINESTRING })); }); @@ -148,7 +149,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mouseup', 30, 20); simulateEvent('click', 30, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.LineString); @@ -181,7 +182,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mouseup', 30, 20); simulateEvent('click', 30, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.LineString); @@ -194,7 +195,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.MULTILINESTRING })); }); @@ -217,7 +218,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mouseup', 30, 20); simulateEvent('click', 30, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.MultiLineString); @@ -230,7 +231,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.POLYGON })); }); @@ -260,7 +261,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mouseup', 10, 20); simulateEvent('click', 10, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.Polygon); @@ -277,7 +278,7 @@ describe('ol.interaction.Draw', function() { beforeEach(function() { map.addInteraction(new ol.interaction.Draw({ - layer: vector, + layer: layer, type: ol.geom.GeometryType.MULTIPOLYGON })); }); @@ -307,7 +308,7 @@ describe('ol.interaction.Draw', function() { simulateEvent('mouseup', 10, 20); simulateEvent('click', 10, 20); - var features = vector.getFeatures(); + var features = source.getFeatures(); expect(features).to.have.length(1); var geometry = features[0].getGeometry(); expect(geometry).to.be.a(ol.geom.MultiPolygon); diff --git a/test/spec/ol/interaction/selectinteraction.test.js b/test/spec/ol/interaction/selectinteraction.test.js index 5c412af9dd..473653d352 100644 --- a/test/spec/ol/interaction/selectinteraction.test.js +++ b/test/spec/ol/interaction/selectinteraction.test.js @@ -1,7 +1,7 @@ goog.provide('ol.test.interaction.Select'); describe('ol.interaction.Select', function() { - var map, target, select, vector, features; + var map, target, select, source, vector, features; beforeEach(function() { target = document.createElement('div'); @@ -11,24 +11,19 @@ describe('ol.interaction.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] - } - }] - })); - vector = new ol.layer.Vector({source: new ol.source.Vector({})}); - vector.addFeatures(features); + + features = [ + new ol.Feature({ + geometry: new ol.geom.Point([-1, 1]) + }), + new ol.Feature({ + geometry: new ol.geom.Point([1, -1]) + }) + ]; + + source = new ol.source.Vector({}); + source.addFeatures(features); + vector = new ol.layer.Vector({source: source}); select = new ol.interaction.Select({ layers: [vector] }); @@ -52,21 +47,21 @@ describe('ol.interaction.Select', function() { it('toggles selection of features', function() { select.select(map, [features], [vector]); - expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(2); + expect(source.getFeatures(selectedFeaturesFilter).length).to.be(2); select.select(map, [features], [vector]); - expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(0); + expect(source.getFeatures(selectedFeaturesFilter).length).to.be(0); }); it('can append features to an existing selection', function() { select.select(map, [[features[0]]], [vector], true); select.select(map, [[features[1]]], [vector]); - expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(2); + expect(source.getFeatures(selectedFeaturesFilter).length).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); - expect(vector.getFeatures(selectedFeaturesFilter).length).to.be(1); + expect(source.getFeatures(selectedFeaturesFilter).length).to.be(1); }); }); @@ -74,8 +69,9 @@ describe('ol.interaction.Select', function() { }); goog.require('goog.dispose'); +goog.require('ol.Feature'); goog.require('ol.Map'); +goog.require('ol.geom.Point'); goog.require('ol.interaction.Select'); goog.require('ol.layer.Vector'); -goog.require('ol.parser.GeoJSON'); goog.require('ol.source.Vector'); diff --git a/test/spec/ol/layer/vectorlayer.test.js b/test/spec/ol/layer/vectorlayer.test.js index ea6977736e..f98cf65184 100644 --- a/test/spec/ol/layer/vectorlayer.test.js +++ b/test/spec/ol/layer/vectorlayer.test.js @@ -2,177 +2,15 @@ goog.provide('ol.test.layer.Vector'); describe('ol.layer.Vector', function() { - describe('#addFeatures()', function() { + describe('constructor', function() { + + it('creates a new layer', function() { - it('allows adding features', function() { var layer = new ol.layer.Vector({ - source: new ol.source.Vector({}) + source: new ol.source.Vector() }); - layer.addFeatures([new ol.Feature(), new ol.Feature()]); - expect(goog.object.getCount(layer.featureCache_.getFeaturesObject())) - .to.eql(2); - }); - }); - - describe('ol.layer.FeatureCache#getFeaturesObject()', function() { - - var layer, features; - - beforeEach(function() { - features = [ - new ol.Feature({ - g: new ol.geom.Point([16.0, 48.0]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) - }) - ]; - layer = new ol.layer.Vector({ - source: new ol.source.Vector({}) - }); - layer.addFeatures(features); - }); - - it('returns the features in an object', function() { - var featuresObject = layer.featureCache_.getFeaturesObject(); - expect(goog.object.getCount(featuresObject)).to.eql(features.length); - }); - - }); - - describe('#groupFeaturesBySymbolizerLiteral()', function() { - - var layer = new ol.layer.Vector({ - source: new ol.source.Vector({ - projection: ol.proj.get('EPSG:4326') - }), - style: new ol.style.Style({ - rules: [ - new ol.style.Rule({ - symbolizers: [ - new ol.style.Stroke({ - width: 2, - color: ol.expr.parse('colorProperty'), - opacity: 1 - }) - ] - }) - ] - }) - }); - var features; - - it('groups equal symbolizers', function() { - features = [ - new ol.Feature({ - g: new ol.geom.LineString([[-10, -10], [10, 10]]), - colorProperty: '#BADA55' - }), - new ol.Feature({ - g: new ol.geom.LineString([[-10, 10], [10, -10]]), - colorProperty: '#013' - }), - new ol.Feature({ - g: new ol.geom.LineString([[10, -10], [-10, -10]]), - colorProperty: '#013' - }) - ]; - - var groups = layer.groupFeaturesBySymbolizerLiteral(features, 1); - expect(groups.length).to.be(2); - expect(groups[0][0].length).to.be(1); - expect(groups[0][1].color).to.be('#BADA55'); - expect(groups[1][0].length).to.be(2); - expect(groups[1][1].color).to.be('#013'); - }); - - it('groups equal symbolizers also when defined on features', function() { - var symbolizer = new ol.style.Stroke({ - width: 3, - color: ol.expr.parse('colorProperty'), - opacity: 1 - }); - var anotherSymbolizer = new ol.style.Stroke({ - width: 3, - color: '#BADA55', - opacity: 1 - }); - var featureWithSymbolizers = new ol.Feature({ - g: new ol.geom.LineString([[-10, -10], [-10, 10]]), - colorProperty: '#BADA55' - }); - featureWithSymbolizers.setSymbolizers([symbolizer]); - var anotherFeatureWithSymbolizers = new ol.Feature({ - g: new ol.geom.LineString([[-10, 10], [-10, -10]]) - }); - anotherFeatureWithSymbolizers.setSymbolizers([anotherSymbolizer]); - features.push(featureWithSymbolizers, anotherFeatureWithSymbolizers); - - var groups = layer.groupFeaturesBySymbolizerLiteral(features, 1); - expect(groups).to.have.length(3); - expect(groups[2][0].length).to.be(2); - expect(groups[2][1].width).to.be(3); - - }); - - it('sorts groups by zIndex', function() { - var symbolizer = new ol.style.Stroke({ - width: 3, - color: '#BADA55', - opacity: 1, - zIndex: 1 - }); - var anotherSymbolizer = new ol.style.Stroke({ - width: 3, - color: '#BADA55', - opacity: 1 - }); - var featureWithSymbolizers = new ol.Feature({ - g: new ol.geom.LineString([[-10, -10], [-10, 10]]) - }); - featureWithSymbolizers.setSymbolizers([symbolizer]); - var anotherFeatureWithSymbolizers = new ol.Feature({ - g: new ol.geom.LineString([[-10, 10], [-10, -10]]) - }); - anotherFeatureWithSymbolizers.setSymbolizers([anotherSymbolizer]); - features = [featureWithSymbolizers, anotherFeatureWithSymbolizers]; - - var groups = layer.groupFeaturesBySymbolizerLiteral(features, 1); - expect(groups).to.have.length(2); - expect(groups[0][1].zIndex).to.be(0); - expect(groups[1][1].zIndex).to.be(1); - }); - - goog.dispose(layer); - - }); - - describe('ol.layer.VectorEvent', function() { - - var layer, features; - - beforeEach(function() { - features = [ - new ol.Feature({ - g: new ol.geom.Point([16.0, 48.0]) - }), - new ol.Feature({ - g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) - }) - ]; - layer = new ol.layer.Vector({ - source: new ol.source.Vector({}) - }); - layer.addFeatures(features); - }); - - it('dispatches events on feature change', function(done) { - layer.on('featurechange', function(evt) { - expect(evt.features[0]).to.be(features[0]); - expect(evt.extents[0]).to.eql(features[0].getGeometry().getBounds()); - done(); - }); - features[0].set('foo', 'bar'); + expect(layer).to.be.a(ol.layer.Vector); + expect(layer).to.be.a(ol.layer.Layer); }); @@ -180,15 +18,6 @@ describe('ol.layer.Vector', function() { }); -goog.require('goog.dispose'); -goog.require('goog.object'); -goog.require('ol.Feature'); -goog.require('ol.expr'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.Point'); -goog.require('ol.proj'); +goog.require('ol.layer.Layer'); goog.require('ol.layer.Vector'); goog.require('ol.source.Vector'); -goog.require('ol.style.Rule'); -goog.require('ol.style.Stroke'); -goog.require('ol.style.Style'); diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index 8029a492aa..860543fe85 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -3,85 +3,334 @@ goog.provide('ol.test.source.Vector'); describe('ol.source.Vector', function() { + var url = 'spec/ol/source/vectorsource/single-feature.json'; + describe('constructor', function() { it('creates an instance', function() { - var source = new ol.source.Vector({}); + var source = new ol.source.Vector(); expect(source).to.be.a(ol.source.Vector); expect(source).to.be.a(ol.source.Source); }); - }); - describe('#prepareFeatures', function() { - it('loads and parses data from a file', function(done) { + it('accepts features', function() { + var features = [new ol.Feature()]; var source = new ol.source.Vector({ - url: 'spec/ol/parser/geojson/countries.geojson', + features: features + }); + expect(source).to.be.a(ol.source.Vector); + expect(source.getFeatures()).to.eql(features); + }); + + it('accepts url and parser', function() { + var source = new ol.source.Vector({ + url: url, parser: new ol.parser.GeoJSON() }); - var layer = new ol.layer.Vector({ - source: source + expect(source).to.be.a(ol.source.Vector); + }); + }); + + describe('#load()', function() { + it('triggers loading of features', function() { + var source = new ol.source.Vector({ + url: url, + parser: new ol.parser.GeoJSON() }); - source.prepareFeatures(layer, [-180, -90, 180, 90], - ol.proj.get('EPSG:4326'), - function() { + expect(source.loadState_).to.be(ol.source.VectorLoadState.IDLE); + var triggered = source.load([-1, -1, 1, 1], ol.proj.get('EPSG:4326')); + expect(triggered).to.be(true); + expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADING); + }); + + it('returns false when already loading', function() { + var source = new ol.source.Vector({ + url: url, + parser: new ol.parser.GeoJSON() + }); + source.load([-1, -1, 1, 1], ol.proj.get('EPSG:4326')); + // second call with same extent + var triggered = source.load([-1, -1, 1, 1], ol.proj.get('EPSG:4326')); + expect(triggered).to.be(false); + expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADING); + }); + }); + + describe('#addFeatures()', function() { + + it('allows adding features', function() { + var source = new ol.source.Vector(); + var features = [new ol.Feature()]; + source.addFeatures(features); + expect(source.getFeatures()).to.eql(features); + }); + + }); + + describe('#getFeatures()', function() { + + it('gets features cached on the source', function() { + var source = new ol.source.Vector({ + features: [new ol.Feature()] + }); + source.addFeatures([new ol.Feature()]); + + var features = source.getFeatures(); + expect(features).to.be.an('array'); + expect(features).to.have.length(2); + }); + + it('accepts a filter function', function() { + var features = [ + new ol.Feature({name: 'a'}), + new ol.Feature({name: 'b'}), + new ol.Feature({name: 'c'}), + new ol.Feature({name: 'd'}) + ]; + var source = new ol.source.Vector({features: features}); + + var results = source.getFeatures(function(feature) { + return feature.get('name') > 'b'; + }); + + expect(results).to.be.an('array'); + expect(results).to.have.length(2); + expect(results).to.contain(features[2]); + expect(results).to.contain(features[3]); + }); + + }); + + describe('#removeFeatures()', function() { + + it('removes cached features', function() { + var features = [new ol.Feature(), new ol.Feature()]; + var source = new ol.source.Vector({features: features}); + + expect(source.getFeatures()).to.have.length(2); + source.removeFeatures(features); + expect(source.getFeatures()).to.have.length(0); + }); + + it('removes cached features', function() { + var features = [new ol.Feature(), new ol.Feature()]; + var source = new ol.source.Vector({features: features}); + + expect(source.getFeatures()).to.have.length(2); + source.removeFeatures([features[0]]); + expect(source.getFeatures()).to.eql([features[1]]); + }); + + }); + + describe('#forEachFeatureInExtent()', function() { + + var features = [ + new ol.Feature({geom: new ol.geom.Point([-100, 50])}), + new ol.Feature({geom: new ol.geom.Point([100, 50])}), + new ol.Feature({geom: new ol.geom.Point([100, -50])}), + new ol.Feature({geom: new ol.geom.Point([-100, -50])}) + ]; + var source = new ol.source.Vector({features: features}); + var gg = ol.proj.get('EPSG:4326'); + + it('calls callback with each feature in the extent', function() { + var callback = sinon.spy(); + source.forEachFeatureInExtent([-180, -90, 180, 90], gg, callback); + expect(callback.callCount).to.be(4); + expect(callback.calledWith(sinon.match.same(features[0]))).to.be(true); + expect(callback.calledWith(sinon.match.same(features[1]))).to.be(true); + expect(callback.calledWith(sinon.match.same(features[2]))).to.be(true); + expect(callback.calledWith(sinon.match.same(features[3]))).to.be(true); + }); + + it('accepts a this argument', function() { + var callback = sinon.spy(); + var thisArg = {}; + source.forEachFeatureInExtent( + [-180, -90, 180, 90], gg, callback, thisArg); + expect(callback.calledOn(thisArg)).to.be(true); + }); + + it('works with a subset of features', function() { + var callback = sinon.spy(); + source.forEachFeatureInExtent([-100, -50, -100, 50], gg, callback); + expect(callback.callCount).to.be(2); + expect(callback.calledWith(sinon.match.same(features[0]))).to.be(true); + expect(callback.calledWith(sinon.match.same(features[3]))).to.be(true); + }); + + it('works with no features', function() { + var callback = sinon.spy(); + source.forEachFeatureInExtent([-110, -50, -110, -50], gg, callback); + expect(callback.called).to.be(false); + }); + + }); + + describe('featureload event', function() { + + var gg = ol.proj.get('EPSG:4326'); + var world = [-180, -90, 180, 90]; + + it('is dispatched after features load', function(done) { + var source = new ol.source.Vector({ + url: url, + parser: new ol.parser.GeoJSON() + }); + expect(source.loadState_).to.be(ol.source.VectorLoadState.IDLE); + var triggered = source.load(world, gg); + expect(triggered).to.be(true); + expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADING); + goog.events.listen(source, ol.source.VectorEventType.LOAD, + function(evt) { + var features = evt.features; + expect(features).to.be.an('array'); + expect(features).to.have.length(1); + expect(features[0]).to.be.an(ol.Feature); + + var extents = evt.extents; + expect(extents).to.be.an('array'); + expect(extents).to.have.length(1); + expect(extents[0]).to.be.eql([1, 2, 1, 2]); + expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADED); - expect(goog.object.getCount( - layer.featureCache_.getFeaturesObject())).to.be(179); done(); }); }); - it('parses inline data', function() { - var source = new ol.source.Vector({ - data: { - 'type': 'FeatureCollection', - 'features': [{ - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [0, -6000000] - } - }, { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [-6000000, 0] - } - }, { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [0, 6000000] - } - }, { - 'type': 'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [6000000, 0] - } - }] - }, - parser: new ol.parser.GeoJSON(), - projection: ol.proj.get('EPSG:4326') - }); - var layer = new ol.layer.Vector({ - source: source - }); - source.prepareFeatures(layer, [-180, -90, 180, 90], - ol.proj.get('EPSG:4326'), - function() { - expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADED); - expect(goog.object.getCount( - layer.featureCache_.getFeaturesObject())).to.be(4); + }); + + describe('featureadd event', function() { + + it('is dispatched after features are added', function(done) { + var source = new ol.source.Vector(); + var features = [ + new ol.Feature({g: new ol.geom.Point([10, 5])}), + new ol.Feature({g: new ol.geom.Point([-10, -5])}) + ]; + + goog.events.listen(source, ol.source.VectorEventType.ADD, + function(evt) { + var features = evt.features; + expect(features).to.be.an('array'); + expect(features).to.have.length(2); + expect(features).to.contain(features[0]); + expect(features).to.contain(features[1]); + + var extents = evt.extents; + expect(extents).to.be.an('array'); + expect(extents).to.have.length(1); + expect(extents[0]).to.be.eql([-10, -5, 10, 5]); + done(); }); + + source.addFeatures(features); }); + + }); + + describe('featureremove event', function() { + + it('is dispatched after features are removed', function(done) { + var features = [ + new ol.Feature({g: new ol.geom.Point([10, 5])}), + new ol.Feature({g: new ol.geom.Point([-10, -5])}) + ]; + var source = new ol.source.Vector({features: features}); + + goog.events.listen(source, ol.source.VectorEventType.REMOVE, + function(evt) { + var features = evt.features; + expect(features).to.be.an('array'); + expect(features).to.have.length(2); + expect(features).to.contain(features[0]); + expect(features).to.contain(features[1]); + + var extents = evt.extents; + expect(extents).to.be.an('array'); + expect(extents).to.have.length(1); + expect(extents[0]).to.be.eql([-10, -5, 10, 5]); + + done(); + }); + + source.removeFeatures(features); + }); + + }); + + describe('featurechange event', function() { + + var source, features; + + beforeEach(function() { + features = [ + new ol.Feature({ + g: new ol.geom.Point([16.0, 48.0]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) + }) + ]; + source = new ol.source.Vector(); + source.addFeatures(features); + }); + + it('is dispatched on attribute changes', function(done) { + goog.events.listen(source, ol.source.VectorEventType.CHANGE, + function(evt) { + var expected = features[0]; + expect(evt.features[0]).to.be(expected); + expect(evt.extents[0]).to.eql(expected.getGeometry().getBounds()); + done(); + + }); + + features[0].set('foo', 'bar'); + }); + }); }); +describe('ol.source.FeatureCache', function() { + + describe('#getFeaturesObject()', function() { + var source, features; + + beforeEach(function() { + features = [ + new ol.Feature({ + g: new ol.geom.Point([16.0, 48.0]) + }), + new ol.Feature({ + g: new ol.geom.LineString([[17.0, 49.0], [17.1, 49.1]]) + }) + ]; + source = new ol.source.Vector(); + source.addFeatures(features); + }); + + it('returns the features in an object', function() { + var featuresObject = source.featureCache_.getFeaturesObject(); + expect(goog.object.getCount(featuresObject)).to.eql(features.length); + }); + + }); + +}); + +goog.require('goog.dispose'); +goog.require('goog.events'); goog.require('goog.object'); -goog.require('ol.layer.Vector'); +goog.require('ol.Feature'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.Point'); goog.require('ol.parser.GeoJSON'); goog.require('ol.proj'); +goog.require('ol.source.FeatureCache'); goog.require('ol.source.Source'); goog.require('ol.source.Vector'); +goog.require('ol.source.VectorEventType'); +goog.require('ol.source.VectorLoadState'); diff --git a/test/spec/ol/source/vectorsource/single-feature.json b/test/spec/ol/source/vectorsource/single-feature.json new file mode 100644 index 0000000000..5d7944efbe --- /dev/null +++ b/test/spec/ol/source/vectorsource/single-feature.json @@ -0,0 +1,16 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "point_1", + "geometry": { + "coordinates": [1, 2], + "type": "Point" + }, + "properties": { + "name": "point" + } + } + ] +} diff --git a/test/spec/ol/structs/rtree.test.js b/test/spec/ol/structs/rtree.test.js index 0a65afb978..08a15373d0 100644 --- a/test/spec/ol/structs/rtree.test.js +++ b/test/spec/ol/structs/rtree.test.js @@ -109,11 +109,38 @@ describe('ol.structs.RTree', function() { expect(result.length).to.be(3); }); - it('can return objects instead of arrays', function() { - var obj = {foo: 'bar'}; - rTree.insert([5, 5, 5, 5], obj); - var result = rTree.searchReturningObject([4, 4, 6, 6]); - expect(result[goog.getUid(obj)]).to.equal(obj); + }); + + describe('#forEach()', function() { + var tree; + beforeEach(function() { + tree = new ol.structs.RTree(); + }); + + it('calls a callback for each result in the search extent', function() { + var one = {}; + tree.insert([4.5, 4.5, 5, 5], one); + + var two = {}; + tree.insert([5, 5, 5.5, 5.5], two); + + var callback = sinon.spy(); + tree.forEach([4, 4, 6, 6], callback); + expect(callback.callCount).to.be(2); + expect(callback.calledWith(one)).to.be(true); + expect(callback.calledWith(two)).to.be(true); + }); + + it('accepts a this argument', function() { + var obj = {}; + tree.insert([5, 5, 5, 5], obj); + + var callback = sinon.spy(); + var thisArg = {}; + tree.forEach([4, 4, 6, 6], callback, thisArg); + expect(callback.callCount).to.be(1); + expect(callback.calledWith(obj)).to.be(true); + expect(callback.calledOn(thisArg)).to.be(true); }); }); diff --git a/test/spec/ol/style/style.test.js b/test/spec/ol/style/style.test.js index d282a06dcd..a58e68275d 100644 --- a/test/spec/ol/style/style.test.js +++ b/test/spec/ol/style/style.test.js @@ -122,6 +122,102 @@ describe('ol.style.Style', function() { }); + describe('#groupFeaturesBySymbolizerLiteral()', function() { + + var style = new ol.style.Style({ + symbolizers: [ + new ol.style.Stroke({ + width: 2, + color: ol.expr.parse('colorProperty'), + opacity: 1 + }) + ] + }); + var features; + + it('groups equal symbolizers', function() { + features = [ + new ol.Feature({ + g: new ol.geom.LineString([[-10, -10], [10, 10]]), + colorProperty: '#BADA55' + }), + new ol.Feature({ + g: new ol.geom.LineString([[-10, 10], [10, -10]]), + colorProperty: '#013' + }), + new ol.Feature({ + g: new ol.geom.LineString([[10, -10], [-10, -10]]), + colorProperty: '#013' + }) + ]; + + var groups = style.groupFeaturesBySymbolizerLiteral(features, 1); + expect(groups.length).to.be(2); + expect(groups[0][0].length).to.be(1); + expect(groups[0][1].color).to.be('#BADA55'); + expect(groups[1][0].length).to.be(2); + expect(groups[1][1].color).to.be('#013'); + }); + + it('groups equal symbolizers also when defined on features', function() { + var symbolizer = new ol.style.Stroke({ + width: 3, + color: ol.expr.parse('colorProperty'), + opacity: 1 + }); + var anotherSymbolizer = new ol.style.Stroke({ + width: 3, + color: '#BADA55', + opacity: 1 + }); + var featureWithSymbolizers = new ol.Feature({ + g: new ol.geom.LineString([[-10, -10], [-10, 10]]), + colorProperty: '#BADA55' + }); + featureWithSymbolizers.setSymbolizers([symbolizer]); + var anotherFeatureWithSymbolizers = new ol.Feature({ + g: new ol.geom.LineString([[-10, 10], [-10, -10]]) + }); + anotherFeatureWithSymbolizers.setSymbolizers([anotherSymbolizer]); + features.push(featureWithSymbolizers, anotherFeatureWithSymbolizers); + + var groups = style.groupFeaturesBySymbolizerLiteral(features, 1); + expect(groups).to.have.length(3); + expect(groups[2][0].length).to.be(2); + expect(groups[2][1].width).to.be(3); + + }); + + it('sorts groups by zIndex', function() { + var symbolizer = new ol.style.Stroke({ + width: 3, + color: '#BADA55', + opacity: 1, + zIndex: 1 + }); + var anotherSymbolizer = new ol.style.Stroke({ + width: 3, + color: '#BADA55', + opacity: 1 + }); + var featureWithSymbolizers = new ol.Feature({ + g: new ol.geom.LineString([[-10, -10], [-10, 10]]) + }); + featureWithSymbolizers.setSymbolizers([symbolizer]); + var anotherFeatureWithSymbolizers = new ol.Feature({ + g: new ol.geom.LineString([[-10, 10], [-10, -10]]) + }); + anotherFeatureWithSymbolizers.setSymbolizers([anotherSymbolizer]); + features = [featureWithSymbolizers, anotherFeatureWithSymbolizers]; + + var groups = style.groupFeaturesBySymbolizerLiteral(features, 1); + expect(groups).to.have.length(2); + expect(groups[0][1].zIndex).to.be(0); + expect(groups[1][1].zIndex).to.be(1); + }); + + }); + describe('ol.style.getDefault()', function() { var style = ol.style.getDefault();