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();