Merge pull request #1282 from tschaub/vector-source

Vector layer/source refactor.
This commit is contained in:
Tim Schaub
2013-11-27 13:27:30 -08:00
23 changed files with 1177 additions and 1008 deletions

View File

@@ -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({

View File

@@ -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
});

View File

@@ -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])
})
]
})
});

View File

@@ -718,16 +718,19 @@
/**
* @typedef {Object} ol.source.VectorOptions
* @property {Array.<ol.Attribution>|undefined} attributions Attributions.
* @property {Object|string|undefined} data Data to parse.
* @property {Array.<ol.Feature>|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
*/

View File

@@ -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.<ol.style.Symbolizer>}
@@ -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}
*/

View File

@@ -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;
};

View File

@@ -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.<number, ol.layer.Vector>}
* @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;

View File

@@ -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
}

View File

@@ -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.<string, ol.Feature>}
* @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.<string, ol.Feature>} 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.<string, ol.Feature>} Features.
*/
ol.layer.FeatureCache.prototype.getFeaturesObjectForExtent = function(extent) {
return this.rTree_.searchReturningObject(extent);
};
/**
* Get features by ids.
* @param {Array.<string>} ids Array of (internal) identifiers.
* @return {Array.<ol.Feature>} 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.<ol.Feature>):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.<ol.Feature>} 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.<ol.Feature>} 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.<string, ol.Feature>} 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.<string, ol.Feature>} features Features.
* @param {number} resolution Map resolution.
* @return {Array.<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.<ol.Feature>} */ ([]),
/** @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.<ol.Feature>} 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.<ol.Feature>} 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.<ol.Feature>} features Features associated with the event.
* @param {Array.<ol.Extent>} extents Any extents associated with the event.
*/
ol.layer.VectorEvent = function(type, features, extents) {
goog.base(this, type);
/**
* @type {Array.<ol.Feature>}
*/
this.features = features;
/**
* @type {Array.<ol.Extent>}
*/
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;
};

View File

@@ -1,13 +0,0 @@
goog.provide('ol.layer.VectorLayerRenderIntent');
/**
* @enum {string}
*/
ol.layer.VectorLayerRenderIntent = {
DEFAULT: 'default',
FUTURE: 'future',
HIDDEN: 'hidden',
SELECTED: 'selected',
TEMPORARY: 'temporary'
};

View File

@@ -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]);

View File

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

View File

@@ -1 +1,2 @@
@exportClass ol.source.Vector ol.source.VectorOptions
@exportProperty ol.source.Vector.prototype.addFeatures

View File

@@ -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.<ol.Feature>} 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.<ol.Feature>} 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.<ol.Feature>} 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.<ol.Feature>} features Features associated with the event.
* @param {Array.<ol.Extent>} extents Any extents associated with the event.
*/
ol.source.VectorEvent = function(type, features, extents) {
goog.base(this, type);
/**
* @type {Array.<ol.Feature>}
*/
this.features = features;
/**
* @type {Array.<ol.Extent>}
*/
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.<string, ol.Feature>}
* @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.<string, ol.Feature>} 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_;
};

View File

@@ -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;
};

View File

@@ -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.<string, ol.Feature>} features Features.
* @param {number} resolution Map resolution.
* @return {Array.<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.<ol.Feature>} */ ([]),
/** @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}

View File

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

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View File

@@ -0,0 +1,16 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "point_1",
"geometry": {
"coordinates": [1, 2],
"type": "Point"
},
"properties": {
"name": "point"
}
}
]
}

View File

@@ -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);
});
});

View File

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