Separate load requests from feature requests
This separates the action of requesting an extent to be loaded from the action of requesting cached features. The renderer (or any other consumer of a vector source) calls load to request a data extent. A `featureload` event fires when new features are loaded. The renderer (or any other consumer) separately asks for cached features given an extent. This vector source only loads features once, but this separation will also work with sources that make multiple requests for data in different extents. This also removes the `data` option from the vector source in favor of a `features` option. Since we no longer have shared data structures for geometries, people can manually create features and pass them to a vector source. The `addFeatures` method is exported as well. This is used to add features to a source that don't have a representation on the "remote" (or server).
This commit is contained in:
@@ -18,7 +18,7 @@ var raster = new ol.layer.Tile({
|
|||||||
});
|
});
|
||||||
|
|
||||||
var vector = new ol.layer.Vector({
|
var vector = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({parser: null}),
|
source: new ol.source.Vector(),
|
||||||
style: new ol.style.Style({
|
style: new ol.style.Style({
|
||||||
rules: [
|
rules: [
|
||||||
new ol.style.Rule({
|
new ol.style.Rule({
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
goog.require('ol.Feature');
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.Overlay');
|
goog.require('ol.Overlay');
|
||||||
goog.require('ol.OverlayPositioning');
|
goog.require('ol.OverlayPositioning');
|
||||||
goog.require('ol.RendererHint');
|
goog.require('ol.RendererHint');
|
||||||
goog.require('ol.View2D');
|
goog.require('ol.View2D');
|
||||||
|
goog.require('ol.geom.Point');
|
||||||
goog.require('ol.layer.Tile');
|
goog.require('ol.layer.Tile');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.parser.GeoJSON');
|
|
||||||
goog.require('ol.source.TileJSON');
|
goog.require('ol.source.TileJSON');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
goog.require('ol.style.Icon');
|
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({
|
var style = new ol.style.Style({
|
||||||
symbolizers: [
|
symbolizers: [
|
||||||
new ol.style.Icon({
|
new ol.style.Icon({
|
||||||
@@ -45,8 +30,14 @@ var style = new ol.style.Style({
|
|||||||
|
|
||||||
var vector = new ol.layer.Vector({
|
var vector = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
parser: new ol.parser.GeoJSON(),
|
features: [
|
||||||
data: data
|
new ol.Feature({
|
||||||
|
name: 'Null Island',
|
||||||
|
population: 4000,
|
||||||
|
rainfall: 500,
|
||||||
|
geometry: new ol.geom.Point([0, 0])
|
||||||
|
})
|
||||||
|
]
|
||||||
}),
|
}),
|
||||||
style: style
|
style: style
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
|
goog.require('ol.Feature');
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.RendererHint');
|
goog.require('ol.RendererHint');
|
||||||
goog.require('ol.View2D');
|
goog.require('ol.View2D');
|
||||||
goog.require('ol.control');
|
goog.require('ol.control');
|
||||||
goog.require('ol.expr');
|
goog.require('ol.expr');
|
||||||
|
goog.require('ol.geom.LineString');
|
||||||
|
goog.require('ol.geom.Point');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.parser.GeoJSON');
|
|
||||||
goog.require('ol.proj');
|
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
goog.require('ol.style.Fill');
|
goog.require('ol.style.Fill');
|
||||||
goog.require('ol.style.Rule');
|
goog.require('ol.style.Rule');
|
||||||
@@ -62,108 +63,60 @@ var style = new ol.style.Style({rules: [
|
|||||||
var vector = new ol.layer.Vector({
|
var vector = new ol.layer.Vector({
|
||||||
style: style,
|
style: style,
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
data: {
|
features: [
|
||||||
'type': 'FeatureCollection',
|
new ol.Feature({
|
||||||
'features': [{
|
color: '#BADA55',
|
||||||
'type': 'Feature',
|
where: 'inner',
|
||||||
'properties': {
|
geometry: new ol.geom.LineString(
|
||||||
'color': '#BADA55',
|
[[-10000000, -10000000], [10000000, 10000000]])
|
||||||
'where': 'inner'
|
}),
|
||||||
},
|
new ol.Feature({
|
||||||
'geometry': {
|
color: '#BADA55',
|
||||||
'type': 'LineString',
|
where: 'inner',
|
||||||
'coordinates': [[-10000000, -10000000], [10000000, 10000000]]
|
geometry: new ol.geom.LineString(
|
||||||
}
|
[[-10000000, 10000000], [10000000, -10000000]])
|
||||||
}, {
|
}),
|
||||||
'type': 'Feature',
|
new ol.Feature({
|
||||||
'properties': {
|
color: '#013',
|
||||||
'color': '#BADA55',
|
where: 'outer',
|
||||||
'where': 'inner'
|
geometry: new ol.geom.LineString(
|
||||||
},
|
[[-10000000, -10000000], [-10000000, 10000000]])
|
||||||
'geometry': {
|
}),
|
||||||
'type': 'LineString',
|
new ol.Feature({
|
||||||
'coordinates': [[-10000000, 10000000], [10000000, -10000000]]
|
color: '#013',
|
||||||
}
|
where: 'outer',
|
||||||
}, {
|
geometry: new ol.geom.LineString(
|
||||||
'type': 'Feature',
|
[[-10000000, 10000000], [10000000, 10000000]])
|
||||||
'properties': {
|
}),
|
||||||
'color': '#013',
|
new ol.Feature({
|
||||||
'where': 'outer'
|
color: '#013',
|
||||||
},
|
where: 'outer',
|
||||||
'geometry': {
|
geometry: new ol.geom.LineString(
|
||||||
'type': 'LineString',
|
[[10000000, 10000000], [10000000, -10000000]])
|
||||||
'coordinates': [[-10000000, -10000000], [-10000000, 10000000]]
|
}),
|
||||||
}
|
new ol.Feature({
|
||||||
}, {
|
color: '#013',
|
||||||
'type': 'Feature',
|
where: 'outer',
|
||||||
'properties': {
|
geometry: new ol.geom.LineString(
|
||||||
'color': '#013',
|
[[10000000, -10000000], [-10000000, -10000000]])
|
||||||
'where': 'outer'
|
}),
|
||||||
},
|
new ol.Feature({
|
||||||
'geometry': {
|
label: 'South',
|
||||||
'type': 'LineString',
|
geometry: new ol.geom.Point([0, -6000000])
|
||||||
'coordinates': [[-10000000, 10000000], [10000000, 10000000]]
|
}),
|
||||||
}
|
new ol.Feature({
|
||||||
}, {
|
label: 'West',
|
||||||
'type': 'Feature',
|
geometry: new ol.geom.Point([-6000000, 0])
|
||||||
'properties': {
|
}),
|
||||||
'color': '#013',
|
new ol.Feature({
|
||||||
'where': 'outer'
|
label: 'North',
|
||||||
},
|
geometry: new ol.geom.Point([0, 6000000])
|
||||||
'geometry': {
|
}),
|
||||||
'type': 'LineString',
|
new ol.Feature({
|
||||||
'coordinates': [[10000000, 10000000], [10000000, -10000000]]
|
label: 'East',
|
||||||
}
|
geometry: new ol.geom.Point([6000000, 0])
|
||||||
}, {
|
})
|
||||||
'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')
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -718,16 +718,19 @@
|
|||||||
/**
|
/**
|
||||||
* @typedef {Object} ol.source.VectorOptions
|
* @typedef {Object} ol.source.VectorOptions
|
||||||
* @property {Array.<ol.Attribution>|undefined} attributions Attributions.
|
* @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 {ol.Extent|undefined} extent Extent.
|
||||||
* @property {string|undefined} logo Logo.
|
* @property {string|undefined} logo Logo.
|
||||||
* @property {ol.parser.Parser} parser Parser instance to parse data
|
* @property {ol.parser.Parser|undefined} parser Parser instance to parse data
|
||||||
* provided as `data` or fetched from `url`.
|
* fetched from `url`.
|
||||||
* @property {ol.proj.ProjectionLike|undefined} projection Projection. Usually the
|
* @property {ol.proj.ProjectionLike|undefined} projection Projection. Usually the
|
||||||
* projection is provided by the parser, so this only needs to be set if
|
* 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
|
* 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.
|
* 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
|
* @todo stability experimental
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ ol.interaction.Draw.prototype.setMap = function(map) {
|
|||||||
if (!goog.isNull(map)) {
|
if (!goog.isNull(map)) {
|
||||||
if (goog.isNull(this.sketchLayer_)) {
|
if (goog.isNull(this.sketchLayer_)) {
|
||||||
var layer = new ol.layer.Vector({
|
var layer = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({parser: null}),
|
source: new ol.source.Vector(),
|
||||||
style: this.layer_.getStyle()
|
style: this.layer_.getStyle()
|
||||||
});
|
});
|
||||||
layer.setTemporary(true);
|
layer.setTemporary(true);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ ol.interaction.Modify.prototype.setMap = function(map) {
|
|||||||
}
|
}
|
||||||
if (goog.isNull(this.sketchLayer_)) {
|
if (goog.isNull(this.sketchLayer_)) {
|
||||||
var sketchLayer = new ol.layer.Vector({
|
var sketchLayer = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({parser: null})
|
source: new ol.source.Vector()
|
||||||
});
|
});
|
||||||
this.sketchLayer_ = sketchLayer;
|
this.sketchLayer_ = sketchLayer;
|
||||||
sketchLayer.setTemporary(true);
|
sketchLayer.setTemporary(true);
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
|
|||||||
|
|
||||||
var source = layer.getSource();
|
var source = layer.getSource();
|
||||||
goog.events.listen(source, [
|
goog.events.listen(source, [
|
||||||
|
ol.source.VectorEventType.LOAD,
|
||||||
ol.source.VectorEventType.ADD,
|
ol.source.VectorEventType.ADD,
|
||||||
ol.source.VectorEventType.CHANGE,
|
ol.source.VectorEventType.CHANGE,
|
||||||
ol.source.VectorEventType.REMOVE,
|
ol.source.VectorEventType.REMOVE,
|
||||||
@@ -427,6 +428,15 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
this.tileArchetype_.height = tileSize[1];
|
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
|
* Prepare the sketch canvas. This covers the currently visible tile range
|
||||||
* and will have rendered all newly visible features.
|
* and will have rendered all newly visible features.
|
||||||
@@ -474,8 +484,6 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
var tile, tileCoord, key, x, y, i, type;
|
var tile, tileCoord, key, x, y, i, type;
|
||||||
var deferred = false;
|
var deferred = false;
|
||||||
var dirty = false;
|
var dirty = false;
|
||||||
var layer = this.getVectorLayer();
|
|
||||||
var source = layer.getSource();
|
|
||||||
var tileExtent, featuresObject, tileHasFeatures;
|
var tileExtent, featuresObject, tileHasFeatures;
|
||||||
fetchTileData:
|
fetchTileData:
|
||||||
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||||
@@ -491,8 +499,8 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
tileExtent[1] -= tileGutter;
|
tileExtent[1] -= tileGutter;
|
||||||
tileExtent[3] += tileGutter;
|
tileExtent[3] += tileGutter;
|
||||||
tileHasFeatures = false;
|
tileHasFeatures = false;
|
||||||
featuresObject = source.getFeaturesObjectForExtent(tileExtent,
|
featuresObject = source.getFeaturesObjectForExtent(
|
||||||
projection, this.requestMapRenderFrame_);
|
tileExtent, projection);
|
||||||
if (goog.isNull(featuresObject)) {
|
if (goog.isNull(featuresObject)) {
|
||||||
deferred = true;
|
deferred = true;
|
||||||
break fetchTileData;
|
break fetchTileData;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
@exportClass ol.source.Vector ol.source.VectorOptions
|
@exportClass ol.source.Vector ol.source.VectorOptions
|
||||||
|
@exportProperty ol.source.Vector.prototype.addFeatures
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ goog.provide('ol.source.Vector');
|
|||||||
goog.provide('ol.source.VectorEventType');
|
goog.provide('ol.source.VectorEventType');
|
||||||
|
|
||||||
goog.require('goog.asserts');
|
goog.require('goog.asserts');
|
||||||
|
goog.require('goog.async.nextTick');
|
||||||
goog.require('goog.events');
|
goog.require('goog.events');
|
||||||
goog.require('goog.events.Event');
|
goog.require('goog.events.Event');
|
||||||
goog.require('goog.net.XhrIo');
|
goog.require('goog.net.XhrIo');
|
||||||
@@ -36,17 +37,12 @@ ol.source.VectorLoadState = {
|
|||||||
ol.source.Vector = function(opt_options) {
|
ol.source.Vector = function(opt_options) {
|
||||||
var options = goog.isDef(opt_options) ? opt_options : {};
|
var options = goog.isDef(opt_options) ? opt_options : {};
|
||||||
|
|
||||||
/**
|
goog.base(this, {
|
||||||
* @private
|
attributions: options.attributions,
|
||||||
* @type {Object|string}
|
extent: options.extent,
|
||||||
*/
|
logo: options.logo,
|
||||||
this.data_ = goog.isDef(options.data) ? options.data : null;
|
projection: options.projection
|
||||||
|
});
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {ol.source.VectorLoadState}
|
|
||||||
*/
|
|
||||||
this.loadState_ = ol.source.VectorLoadState.IDLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -60,23 +56,126 @@ ol.source.Vector = function(opt_options) {
|
|||||||
*/
|
*/
|
||||||
this.url_ = options.url;
|
this.url_ = options.url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {ol.source.VectorLoadState}
|
||||||
|
*/
|
||||||
|
this.loadState_ = goog.isDef(this.url_) ?
|
||||||
|
ol.source.VectorLoadState.IDLE : ol.source.VectorLoadState.LOADED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ol.source.FeatureCache}
|
* @type {ol.source.FeatureCache}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.featureCache_ = new ol.source.FeatureCache();
|
this.featureCache_ = new ol.source.FeatureCache();
|
||||||
|
|
||||||
goog.base(this, {
|
// add any user provided features
|
||||||
attributions: options.attributions,
|
if (goog.isDef(options.features)) {
|
||||||
extent: options.extent,
|
this.addFeatures(options.features);
|
||||||
logo: options.logo,
|
}
|
||||||
projection: options.projection
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
goog.inherits(ol.source.Vector, ol.source.Source);
|
goog.inherits(ol.source.Vector, ol.source.Source);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 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.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()) {
|
||||||
|
// parsing may be asynchronous, so we don't set load state here
|
||||||
|
this.parseFeaturesString_(xhr.getResponseText(), projection);
|
||||||
|
} else {
|
||||||
|
this.loadState_ = ol.source.VectorLoadState.ERROR;
|
||||||
|
}
|
||||||
|
}, this));
|
||||||
|
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.
|
* @param {Array.<ol.Feature>} features Array of features.
|
||||||
*/
|
*/
|
||||||
ol.source.Vector.prototype.addFeatures = function(features) {
|
ol.source.Vector.prototype.addFeatures = function(features) {
|
||||||
@@ -84,15 +183,11 @@ ol.source.Vector.prototype.addFeatures = function(features) {
|
|||||||
feature, geometry;
|
feature, geometry;
|
||||||
for (var i = 0, ii = features.length; i < ii; ++i) {
|
for (var i = 0, ii = features.length; i < ii; ++i) {
|
||||||
feature = features[i];
|
feature = features[i];
|
||||||
this.featureCache_.add(feature);
|
this.loadFeature_(feature);
|
||||||
geometry = feature.getGeometry();
|
geometry = feature.getGeometry();
|
||||||
if (!goog.isNull(geometry)) {
|
if (!goog.isNull(geometry)) {
|
||||||
ol.extent.extend(extent, geometry.getBounds());
|
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.source.VectorEvent(ol.source.VectorEventType.ADD,
|
this.dispatchEvent(new ol.source.VectorEvent(ol.source.VectorEventType.ADD,
|
||||||
features, [extent]));
|
features, [extent]));
|
||||||
@@ -125,79 +220,17 @@ ol.source.Vector.prototype.getFeatures = function(opt_filter) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all features whose bounding box intersects the provided extent. This
|
* Get all features whose bounding box intersects the provided extent. This
|
||||||
* method is intended for being called by the renderer. When null is returned,
|
* method is intended for being called by the renderer.
|
||||||
* 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.Extent} extent Bounding extent.
|
||||||
* @param {ol.proj.Projection} projection Target projection.
|
* @param {ol.proj.Projection} projection Target projection.
|
||||||
* @param {function()=} opt_callback Callback to call when data is parsed.
|
* @return {Object.<string, ol.Feature>} Features lookup object.
|
||||||
* @return {Object.<string, ol.Feature>} Features or null if source is loading
|
|
||||||
* data for `extent`.
|
|
||||||
*/
|
*/
|
||||||
ol.source.Vector.prototype.getFeaturesObjectForExtent = function(extent,
|
ol.source.Vector.prototype.getFeaturesObjectForExtent = function(extent,
|
||||||
projection, opt_callback) {
|
projection) {
|
||||||
var state = this.prepareFeatures_(extent, projection, opt_callback);
|
// TODO: create forEachFeatureInExtent method instead
|
||||||
var lookup = null;
|
// TODO: transform if requested project is different than loaded projection
|
||||||
if (state !== ol.source.VectorLoadState.LOADING) {
|
return this.featureCache_.getFeaturesObjectForExtent(extent);
|
||||||
lookup = this.featureCache_.getFeaturesObjectForExtent(extent);
|
|
||||||
}
|
|
||||||
return lookup;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object|Element|Document|string} data Feature data.
|
|
||||||
* @param {ol.proj.Projection} projection This sucks. The layer should be a
|
|
||||||
* view in one projection.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ol.source.Vector.prototype.parseFeatures_ = function(data, projection) {
|
|
||||||
|
|
||||||
var addFeatures = function(data) {
|
|
||||||
var features = data.features;
|
|
||||||
var sourceProjection = this.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;
|
|
||||||
var parser = this.parser_;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -241,43 +274,6 @@ ol.source.Vector.prototype.handleIntentChange_ = function(evt) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
ol.source.Vector.prototype.prepareFeatures_ = function(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) {
|
|
||||||
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.
|
|
||||||
this.parseFeatures_(xhr.getResponseText(), projection);
|
|
||||||
this.loadState_ = ol.source.VectorLoadState.LOADED;
|
|
||||||
if (goog.isDef(opt_callback)) {
|
|
||||||
opt_callback();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Error handling.
|
|
||||||
this.loadState_ = ol.source.VectorLoadState.ERROR;
|
|
||||||
}
|
|
||||||
}, this));
|
|
||||||
} else if (!goog.isNull(this.data_)) {
|
|
||||||
this.parseFeatures_(this.data_, projection);
|
|
||||||
this.data_ = null;
|
|
||||||
this.loadState_ = ol.source.VectorLoadState.LOADED;
|
|
||||||
}
|
|
||||||
return this.loadState_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove features from the layer.
|
* Remove features from the layer.
|
||||||
* @param {Array.<ol.Feature>} features Features to remove.
|
* @param {Array.<ol.Feature>} features Features to remove.
|
||||||
@@ -332,6 +328,7 @@ goog.inherits(ol.source.VectorEvent, goog.events.Event);
|
|||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
ol.source.VectorEventType = {
|
ol.source.VectorEventType = {
|
||||||
|
LOAD: 'featureload',
|
||||||
ADD: 'featureadd',
|
ADD: 'featureadd',
|
||||||
CHANGE: 'featurechange',
|
CHANGE: 'featurechange',
|
||||||
INTENTCHANGE: 'featureintentchange',
|
INTENTCHANGE: 'featureintentchange',
|
||||||
|
|||||||
@@ -21,69 +21,6 @@ describe('ol.source.Vector', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#prepareFeatures_', function() {
|
|
||||||
it('loads and parses data from a file', function(done) {
|
|
||||||
var source = new ol.source.Vector({
|
|
||||||
url: 'spec/ol/parser/geojson/countries.geojson',
|
|
||||||
parser: new ol.parser.GeoJSON()
|
|
||||||
});
|
|
||||||
source.prepareFeatures_([-180, -90, 180, 90],
|
|
||||||
ol.proj.get('EPSG:4326'),
|
|
||||||
function() {
|
|
||||||
expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADED);
|
|
||||||
expect(goog.object.getCount(
|
|
||||||
source.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')
|
|
||||||
});
|
|
||||||
source.prepareFeatures_([-180, -90, 180, 90],
|
|
||||||
ol.proj.get('EPSG:4326'),
|
|
||||||
function() {
|
|
||||||
expect(source.loadState_).to.be(ol.source.VectorLoadState.LOADED);
|
|
||||||
expect(goog.object.getCount(
|
|
||||||
source.featureCache_.getFeaturesObject())).to.be(4);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('featurechange event', function() {
|
describe('featurechange event', function() {
|
||||||
|
|
||||||
var source, features;
|
var source, features;
|
||||||
@@ -151,8 +88,6 @@ goog.require('goog.object');
|
|||||||
goog.require('ol.Feature');
|
goog.require('ol.Feature');
|
||||||
goog.require('ol.geom.LineString');
|
goog.require('ol.geom.LineString');
|
||||||
goog.require('ol.geom.Point');
|
goog.require('ol.geom.Point');
|
||||||
goog.require('ol.parser.GeoJSON');
|
|
||||||
goog.require('ol.proj');
|
|
||||||
goog.require('ol.source.FeatureCache');
|
goog.require('ol.source.FeatureCache');
|
||||||
goog.require('ol.source.Source');
|
goog.require('ol.source.Source');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
|
|||||||
Reference in New Issue
Block a user