diff --git a/examples/gpx.js b/examples/gpx.js index 15e23a9d69..9416eded87 100644 --- a/examples/gpx.js +++ b/examples/gpx.js @@ -15,7 +15,14 @@ var raster = new ol.layer.TileLayer({ var vector = new ol.layer.Vector({ source: new ol.source.Vector({ projection: ol.proj.get('EPSG:4326') - }) + }), + featureInfoFunction: function(features) { + var info = []; + for (var i = 0, ii = features.length; i < ii; ++i) { + info.push(features[i].get('name') + ': ' + features[i].get('type')); + } + return info.join(', '); + } }); var map = new ol.Map({ @@ -32,12 +39,8 @@ map.on(['click', 'mousemove'], function(evt) { map.getFeatureInfo({ pixel: evt.getPixel(), layers: [vector], - success: function(features) { - var info = []; - for (var i = 0, ii = features.length; i < ii; ++i) { - info.push(features[i].get('name') + ': ' + features[i].get('type')); - } - document.getElementById('info').innerHTML = info.join(', ') || ' '; + success: function(featureInfo) { + document.getElementById('info').innerHTML = featureInfo[0] || ' '; } }); }); diff --git a/examples/kml.js b/examples/kml.js index 0f7f48aa39..154fba8f52 100644 --- a/examples/kml.js +++ b/examples/kml.js @@ -25,7 +25,14 @@ var epsg4326 = ol.proj.get('EPSG:4326'); var vector = new ol.layer.Vector({ source: new ol.source.Vector({ projection: epsg4326 - }) + }), + featureInfoFunction: function(features) { + var info = []; + for (var i = 0, ii = features.length; i < ii; ++i) { + info.push(features[i].get('name')); + } + return info.join(', '); + } }); var map = new ol.Map({ @@ -46,12 +53,8 @@ map.on(['click', 'mousemove'], function(evt) { map.getFeatureInfo({ pixel: evt.getPixel(), layers: [vector], - success: function(features) { - var info = []; - for (var i = 0, ii = features.length; i < ii; ++i) { - info.push(features[i].get('name')); - } - document.getElementById('info').innerHTML = info.join(', ') || ' '; + success: function(featureInfo) { + document.getElementById('info').innerHTML = featureInfo[0] || ' '; } }); }); diff --git a/examples/vector-layer.js b/examples/vector-layer.js index 4ece93ef11..6b5602e98e 100644 --- a/examples/vector-layer.js +++ b/examples/vector-layer.js @@ -28,7 +28,11 @@ var vector = new ol.layer.Vector({ }) ] }) - ]}) + ]}), + featureInfoFunction: function(features) { + return features.length > 0 ? + features[0].getFeatureId() + ': ' + features[0].get('name') : ' '; + } }); var map = new ol.Map({ @@ -45,12 +49,8 @@ map.on(['click', 'mousemove'], function(evt) { map.getFeatureInfo({ pixel: evt.getPixel(), layers: [vector], - success: function(features) { - var info = []; - for (var i = 0, ii = features.length; i < ii; ++i) { - info.push(features[i].getFeatureId() + ': ' + features[i].get('name')); - } - document.getElementById('info').innerHTML = info.join(', ') || ' '; + success: function(featureInfo) { + document.getElementById('info').innerHTML = featureInfo[0]; } }); }); diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc index 02269b07e0..353613f91c 100644 --- a/src/objectliterals.jsdoc +++ b/src/objectliterals.jsdoc @@ -9,12 +9,26 @@ * @typedef {Object} ol.GetFeatureInfoOptions * @property {ol.Pixel} pixel Pixel coordinate relative to the map viewport. * @property {Array.|undefined} layers Layers to restrict the - * query to. All layers will be queried if not provided. - * @property {function(Array.)} success Callback for + * query to. All map layers will be queried if not provided. + * @property {function(Array.)} success Callback for * successful queries. The passed argument is the resulting feature - * information. Layers that are able to provide attribute data will put - * ol.Feature instances, other layers will put a string which can either - * be plain text or markup. + * information for each layer, with array indices being the same as in the + * passed `layers` array or in the layer collection as returned from + * `ol.Map#getLayers()` if no `layers` were provided. + * @property {function(Object)|undefined} error Callback for unsuccessful + * queries. + */ + +/** + * @typedef {Object} ol.GetFeaturesOptions + * @property {ol.Pixel} pixel Pixel coordinate relative to the map viewport. + * @property {Array.|undefined} layers Layers to restrict the + * query to. All layers will be queried if not provided. + * @property {function(Array.>)} success Callback + * for successful queries. The passed argument is the resulting features for + * each layer, with array indices being the same as in the passed `layers` + * array or in the layer collection as returned from `ol.Map#getLayers()` if + * no layers were provided. * @property {function(Object)|undefined} error Callback for unsuccessful * queries. */ @@ -287,6 +301,10 @@ /** * @typedef {Object} ol.layer.VectorLayerOptions + * @property {function(Array.):string| + * undefined} featureInfoFunction Function to render an array of features + * into feature info markup. If not provided, a comma separated list the + * unique ids of the resulting features will be returned. * @property {number|undefined} opacity Opacity. 0-1. Default is 1. * @property {ol.source.Source} source Source for this layer. * @property {ol.style.Style|undefined} style Style. diff --git a/src/ol/layer/vectorlayer.js b/src/ol/layer/vectorlayer.js index bbbdd4e6cc..82af46a521 100644 --- a/src/ol/layer/vectorlayer.js +++ b/src/ol/layer/vectorlayer.js @@ -1,5 +1,6 @@ goog.provide('ol.layer.Vector'); +goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.events.EventType'); goog.require('goog.object'); @@ -178,6 +179,13 @@ ol.layer.Vector = function(options) { */ this.featureCache_ = new ol.layer.FeatureCache(); + /** + * @type {function(Array.):string} + * @private + */ + this.featureInfoFunction_ = goog.isDef(options.featureInfoFunction) ? + options.featureInfoFunction : ol.layer.Vector.uidFeatureInfoFunction; + /** * TODO: this means we need to know dimension at construction * @type {ol.geom.SharedVertices} @@ -378,6 +386,25 @@ ol.layer.Vector.prototype.parseFeatures = function(data, parser, projection) { }; +/** + * @return {function(Array.):string} Feature info function. + */ +ol.layer.Vector.prototype.getFeatureInfoFunction = function() { + return this.featureInfoFunction_; +}; + + +/** + * @param {Array.} features Features. + * @return {string} Feature info. + */ +ol.layer.Vector.uidFeatureInfoFunction = function(features) { + var featureIds = goog.array.map(features, + function(feature) { return goog.getUid(feature); }); + return featureIds.join(', '); +}; + + goog.require('ol.filter.Extent'); goog.require('ol.filter.Geometry'); goog.require('ol.filter.Logical'); diff --git a/src/ol/map.exports b/src/ol/map.exports index 4bca2e1995..8523ff1658 100644 --- a/src/ol/map.exports +++ b/src/ol/map.exports @@ -3,6 +3,7 @@ @exportProperty ol.Map.prototype.addPreRenderFunction @exportProperty ol.Map.prototype.addPreRenderFunctions @exportProperty ol.Map.prototype.getFeatureInfo +@exportProperty ol.Map.prototype.getFeatures @exportProperty ol.Map.prototype.getInteractions @exportProperty ol.Map.prototype.getRenderer @exportProperty ol.Map.prototype.removeLayer diff --git a/src/ol/map.js b/src/ol/map.js index 79a67077fb..2f8966853f 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -440,6 +440,19 @@ ol.Map.prototype.getFeatureInfo = function(options) { }; +/** + * Get features for a pixel on the map. + * + * @param {ol.GetFeaturesOptions} options Options. + */ +ol.Map.prototype.getFeatures = function(options) { + var layers = goog.isDefAndNotNull(options.layers) ? + options.layers : this.getLayers().getArray(); + this.getRenderer().getFeaturesForPixel( + options.pixel, layers, options.success, options.error); +}; + + /** * @return {ol.Collection} Interactions. */ diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 2207b02d2b..7c56d15d19 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -193,17 +193,31 @@ ol.renderer.canvas.VectorLayer.prototype.getTransform = function() { /** * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport. - * @param {function(Array.)} success Callback for - * successful queries. The passed argument is the resulting feature - * information. Layers that are able to provide attribute data will put - * ol.Feature instances, other layers will put a string which can either - * be plain text or markup. + * @param {function(string, ol.layer.Layer)} success Callback for + * successful queries. The passed arguments are the resulting feature + * information and the layer. */ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = function(pixel, success) { + var callback = function(features, layer) { + success(layer.getFeatureInfoFunction()(features), layer); + }; + this.getFeaturesForPixel(pixel, callback); +}; + + +/** + * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport. + * @param {function(Array., ol.layer.Layer)} success Callback for + * successful queries. The passed arguments are the resulting features + * and the layer. + */ +ol.renderer.canvas.VectorLayer.prototype.getFeaturesForPixel = + function(pixel, success) { var map = this.getMap(); var result = []; + var layer = this.getLayer(); var location = map.getCoordinateFromPixel(pixel); var tileCoord = this.tileGrid_.getTileCoordForCoordAndResolution( location, this.getMap().getView().getView2D().getResolution()); @@ -218,7 +232,7 @@ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = var locationMax = [location[0] + halfMaxWidth, location[1] + halfMaxHeight]; var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]); var filter = new ol.filter.Extent(locationBbox); - var candidates = this.getLayer().getFeatures(filter); + var candidates = layer.getFeatures(filter); var candidate, geom, type, symbolBounds, symbolSize, halfWidth, halfHeight, coordinates, j; @@ -261,7 +275,7 @@ ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel = } } } - goog.global.setTimeout(function() { success(result); }, 0); + goog.global.setTimeout(function() { success(result, layer); }, 0); }; diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index cfc2fad47e..2623110027 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -1,6 +1,7 @@ goog.provide('ol.renderer.Map'); goog.require('goog.Disposable'); +goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.dispose'); goog.require('goog.functions'); @@ -110,22 +111,60 @@ ol.renderer.Map.prototype.getCanvas = goog.functions.NULL; */ ol.renderer.Map.prototype.getFeatureInfoForPixel = function(pixel, layers, success, opt_error) { - var layer, layerRenderer; - var featureInfo = []; var numLayers = layers.length; - var callback = function(layerFeatureInfo) { - featureInfo.push.apply(featureInfo, layerFeatureInfo); + var featureInfo = new Array(numLayers); + var callback = function(layerFeatureInfo, layer) { + featureInfo[goog.array.indexOf(layers, layer)] = layerFeatureInfo; --numLayers; if (!numLayers) { success(featureInfo); } }; + var layer, layerRenderer; for (var i = 0; i < numLayers; ++i) { layer = layers[i]; layerRenderer = this.getLayerRenderer(layer); if (goog.isFunction(layerRenderer.getFeatureInfoForPixel)) { layerRenderer.getFeatureInfoForPixel(pixel, callback, opt_error); + } else { + --numLayers; + } + } +}; + + +/** + * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport. + * @param {Array.} layers Layers to query. + * @param {function(Array.)} success Callback for + * successful queries. The passed argument is the resulting feature + * information. Layers that are able to provide attribute data will put + * ol.Feature instances, other layers will put a string which can either + * be plain text or markup. + * @param {function(Object)=} opt_error Callback for unsuccessful + * queries. + */ +ol.renderer.Map.prototype.getFeaturesForPixel = + function(pixel, layers, success, opt_error) { + var numLayers = layers.length; + var features = new Array(numLayers); + var callback = function(layerFeatures, layer) { + features[goog.array.indexOf(layers, layer)] = layerFeatures; + --numLayers; + if (!numLayers) { + success(features); + } + }; + + var layer, layerRenderer; + for (var i = 0; i < numLayers; ++i) { + layer = layers[i]; + layerRenderer = this.getLayerRenderer(layer); + if (goog.isFunction(layerRenderer.getFeaturesForPixel)) { + layerRenderer.getFeaturesForPixel(pixel, callback, opt_error); + } else { + --numLayers; } } };