diff --git a/examples/vector-layer.html b/examples/vector-layer.html
index d286840586..c34ff9cde5 100644
--- a/examples/vector-layer.html
+++ b/examples/vector-layer.html
@@ -43,6 +43,11 @@
vector, geojson, style
+
diff --git a/examples/vector-layer.js b/examples/vector-layer.js
index 1f23f67028..d830d654ee 100644
--- a/examples/vector-layer.js
+++ b/examples/vector-layer.js
@@ -42,6 +42,15 @@ var map = new ol.Map({
})
});
+map.on('mousemove', function(evt) {
+ var features = map.getFeatureInfoForPixel(evt.getPixel(), [vector]);
+ var info = [];
+ for (var i = 0, ii = features.length; i < ii; ++i) {
+ info.push(features[i].get('name'));
+ }
+ document.getElementById('info').innerHTML = info.join(', ') || ' ';
+});
+
var geojson = new ol.parser.GeoJSON();
var url = 'data/countries.json';
diff --git a/src/ol/coordinate.js b/src/ol/coordinate.js
index bf1d7c0ee9..ad60916902 100644
--- a/src/ol/coordinate.js
+++ b/src/ol/coordinate.js
@@ -12,7 +12,8 @@ ol.CoordinateFormatType;
/**
- * @typedef {Array.}
+ * An array representing a coordinate.
+ * @typedef {Array.} ol.Coordinate
*/
ol.Coordinate;
@@ -84,6 +85,18 @@ ol.coordinate.scale = function(coordinate, s) {
};
+/**
+ * @param {ol.Coordinate} coord1 First coordinate.
+ * @param {ol.Coordinate} coord2 Second coordinate.
+ * @return {number} Squared distance between coord1 and coord2.
+ */
+ol.coordinate.squaredDistance = function(coord1, coord2) {
+ var dx = coord1[0] - coord2[0];
+ var dy = coord1[1] - coord2[1];
+ return dx * dx + dy * dy;
+};
+
+
/**
* @param {ol.Coordinate|undefined} coordinate Coordinate.
* @return {string} Hemisphere, degrees, minutes and seconds.
diff --git a/src/ol/geom/base.js b/src/ol/geom/base.js
index 31ebb15f95..1b8d3e4a04 100644
--- a/src/ol/geom/base.js
+++ b/src/ol/geom/base.js
@@ -1,6 +1,8 @@
goog.provide('ol.geom.Vertex');
goog.provide('ol.geom.VertexArray');
+goog.require('ol.coordinate');
+
/**
* @typedef {Array.}
@@ -12,3 +14,59 @@ ol.geom.Vertex;
* @typedef {Array.}
*/
ol.geom.VertexArray;
+
+
+/**
+ * Calculate the squared distance from a point to a line segment.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate of the point.
+ * @param {Array.} segment Line segment (2 coordinates).
+ * @return {number} Squared distance from the point to the line segment.
+ */
+ol.geom.squaredDistanceToSegment = function(coordinate, segment) {
+ // http://de.softuses.com/103478, Kommentar #1
+ var v = segment[0];
+ var w = segment[1];
+ var l2 = ol.coordinate.squaredDistance(v, w);
+ if (l2 == 0) {
+ return ol.coordinate.squaredDistance(coordinate, v);
+ }
+ var t = ((coordinate[0] - v[0]) * (w[0] - v[0]) +
+ (coordinate[1] - v[1]) * (w[1] - v[1])) / l2;
+ if (t < 0) {
+ return ol.coordinate.squaredDistance(coordinate, v);
+ }
+ if (t > 1) {
+ return ol.coordinate.squaredDistance(coordinate, w);
+ }
+ return ol.coordinate.squaredDistance(coordinate,
+ [v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])]);
+};
+
+
+/**
+ * Calculate whether a point falls inside a polygon.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate of the point.
+ * @param {Array.} vertices Vertices of the polygon.
+ * @return {boolean} Whether the point falls inside the polygon.
+ */
+ol.geom.pointInPolygon = function(coordinate, vertices) {
+ // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
+ var x = coordinate[0], y = coordinate[1];
+ var inside = false;
+ var xi, yi, xj, yj, intersect;
+ var numVertices = vertices.length;
+ for (var i = 0, j = numVertices - 1; i < numVertices; j = i++) {
+ xi = vertices[i][0];
+ yi = vertices[i][1];
+ xj = vertices[j][0];
+ yj = vertices[j][1];
+ intersect = ((yi > y) != (yj > y)) &&
+ (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
+ if (intersect) {
+ inside = !inside;
+ }
+ }
+ return inside;
+};
diff --git a/src/ol/geom/linestring.js b/src/ol/geom/linestring.js
index 7f92785690..7a7f2321fd 100644
--- a/src/ol/geom/linestring.js
+++ b/src/ol/geom/linestring.js
@@ -146,3 +146,20 @@ ol.geom.LineString.prototype.getType = function() {
ol.geom.LineString.prototype.getSharedId = function() {
return this.sharedId_;
};
+
+
+/**
+ * Calculate the distance from a coordinate to this linestring.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {number} Distance from the coordinate to this linestring.
+ */
+ol.geom.LineString.prototype.distanceFromCoordinate = function(coordinate) {
+ var coordinates = this.getCoordinates();
+ var dist2 = Infinity;
+ for (var i = 0, j = 1, len = coordinates.length; j < len; i = j++) {
+ dist2 = Math.min(dist2, ol.geom.squaredDistanceToSegment(coordinate,
+ [coordinates[i], coordinates[j]]));
+ }
+ return Math.sqrt(dist2);
+};
diff --git a/src/ol/geom/multilinestring.js b/src/ol/geom/multilinestring.js
index 71f99afbe1..b656648992 100644
--- a/src/ol/geom/multilinestring.js
+++ b/src/ol/geom/multilinestring.js
@@ -55,6 +55,25 @@ ol.geom.MultiLineString.prototype.getType = function() {
};
+/**
+ * Calculate the distance from a coordinate to this multilinestring. This is
+ * the closest distance of the coordinate to one of this multilinestring's
+ * components.<
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {number} Distance from the coordinate to this multilinestring.
+ */
+ol.geom.MultiLineString.prototype.distanceFromCoordinate =
+ function(coordinate) {
+ var distance = Infinity;
+ for (var i = 0, ii = this.components.length; i < ii; ++i) {
+ distance = Math.min(distance,
+ this.components[i].distanceFromCoordinate(coordinate));
+ }
+ return distance;
+};
+
+
/**
* Create a multi-linestring geometry from an array of linestring geometries.
*
diff --git a/src/ol/geom/multipolygon.js b/src/ol/geom/multipolygon.js
index b3c87c6175..452f487386 100644
--- a/src/ol/geom/multipolygon.js
+++ b/src/ol/geom/multipolygon.js
@@ -56,6 +56,24 @@ ol.geom.MultiPolygon.prototype.getType = function() {
};
+/**
+ * Check whether a given coordinate is inside this multipolygon.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {boolean} Whether the coordinate is inside the multipolygon.
+ */
+ol.geom.MultiPolygon.prototype.containsCoordinate = function(coordinate) {
+ var containsCoordinate = false;
+ for (var i = 0, ii = this.components.length; i < ii; ++i) {
+ if (this.components[i].containsCoordinate(coordinate)) {
+ containsCoordinate = true;
+ break;
+ }
+ }
+ return containsCoordinate;
+};
+
+
/**
* Create a multi-polygon geometry from an array of polygon geometries.
*
diff --git a/src/ol/geom/polygon.js b/src/ol/geom/polygon.js
index c38b4aa57a..383cdebd98 100644
--- a/src/ol/geom/polygon.js
+++ b/src/ol/geom/polygon.js
@@ -88,3 +88,26 @@ ol.geom.Polygon.prototype.getCoordinates = function() {
ol.geom.Polygon.prototype.getType = function() {
return ol.geom.GeometryType.POLYGON;
};
+
+
+/**
+ * Check whether a given coordinate is inside this polygon.
+ *
+ * @param {ol.Coordinate} coordinate Coordinate.
+ * @return {boolean} Whether the coordinate is inside the polygon.
+ */
+ol.geom.Polygon.prototype.containsCoordinate = function(coordinate) {
+ var rings = this.rings;
+ var containsCoordinate = ol.geom.pointInPolygon(coordinate,
+ rings[0].getCoordinates());
+ if (containsCoordinate) {
+ // if inner ring contains point, polygon does not contain it
+ for (var i = 1, ii = rings.length; i < ii; ++i) {
+ if (ol.geom.pointInPolygon(coordinate, rings[i].getCoordinates())) {
+ containsCoordinate = false;
+ break;
+ }
+ }
+ }
+ return containsCoordinate;
+};
diff --git a/src/ol/map.exports b/src/ol/map.exports
index 6fa9bba48a..048d03366d 100644
--- a/src/ol/map.exports
+++ b/src/ol/map.exports
@@ -2,6 +2,7 @@
@exportProperty ol.Map.prototype.addLayer
@exportProperty ol.Map.prototype.addPreRenderFunction
@exportProperty ol.Map.prototype.addPreRenderFunctions
+@exportProperty ol.Map.prototype.getFeatureInfoForPixel
@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 616ed73c0c..e144fe7e68 100644
--- a/src/ol/map.js
+++ b/src/ol/map.js
@@ -427,6 +427,23 @@ ol.Map.prototype.getCoordinateFromPixel = function(pixel) {
};
+/**
+ * Get feature information for a pixel on the map.
+ *
+ * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport.
+ * @param {Array.=} opt_layers Layers to restrict the query to.
+ * All layers will be queried if not provided.
+ * @return {Array.} Feature information. Layers that are
+ * able to return attribute data will return ol.Feature instances, other
+ * layers will return a string which can either be plain text or markup.
+ */
+ol.Map.prototype.getFeatureInfoForPixel = function(pixel, opt_layers) {
+ var layers = goog.isDefAndNotNull(opt_layers) ?
+ opt_layers : this.getLayers().getArray();
+ return this.getRenderer().getFeatureInfoForPixel(pixel, layers);
+};
+
+
/**
* @return {ol.Collection} Interactions.
*/
diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js
index 108458b198..035178f538 100644
--- a/src/ol/mapbrowserevent.js
+++ b/src/ol/mapbrowserevent.js
@@ -147,6 +147,12 @@ ol.MapBrowserEventHandler = function(map) {
*/
this.downListenerKey_ = null;
+ /**
+ * @type {?number}
+ * @private
+ */
+ this.moveListenerKey_ = null;
+
/**
* @type {Array.}
* @private
@@ -172,6 +178,9 @@ ol.MapBrowserEventHandler = function(map) {
this.downListenerKey_ = goog.events.listen(element,
goog.events.EventType.MOUSEDOWN,
this.handleMouseDown_, false, this);
+ this.moveListenerKey_ = goog.events.listen(element,
+ goog.events.EventType.MOUSEMOVE,
+ this.relayMouseMove_, false, this);
// touch events
this.touchListenerKeys_ = [
goog.events.listen(element, [
@@ -281,6 +290,16 @@ ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) {
};
+/**
+ * @param {goog.events.BrowserEvent} browserEvent Browser event.
+ * @private
+ */
+ol.MapBrowserEventHandler.prototype.relayMouseMove_ = function(browserEvent) {
+ this.dispatchEvent(new ol.MapBrowserEvent(
+ ol.MapBrowserEvent.EventType.MOUSEMOVE, this.map_, browserEvent));
+};
+
+
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
@@ -335,6 +354,7 @@ ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) {
ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
goog.events.unlistenByKey(this.clickListenerKey_);
goog.events.unlistenByKey(this.downListenerKey_);
+ goog.events.unlistenByKey(this.moveListenerKey_);
if (!goog.isNull(this.dragListenerKeys_)) {
goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
this.dragListenerKeys_ = null;
@@ -360,5 +380,6 @@ ol.MapBrowserEvent.EventType = {
DRAGEND: 'dragend',
TOUCHSTART: goog.events.EventType.TOUCHSTART,
TOUCHMOVE: goog.events.EventType.TOUCHMOVE,
- TOUCHEND: goog.events.EventType.TOUCHEND
+ TOUCHEND: goog.events.EventType.TOUCHEND,
+ MOUSEMOVE: goog.events.EventType.MOUSEMOVE
};
diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
index b343880a36..83dcbdff06 100644
--- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js
@@ -6,6 +6,7 @@ goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
+goog.require('ol.Pixel');
goog.require('ol.Size');
goog.require('ol.TileCache');
goog.require('ol.TileCoord');
@@ -187,6 +188,52 @@ ol.renderer.canvas.VectorLayer.prototype.getTransform = function() {
};
+/**
+ * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport.
+ * @return {Array.} Features at the pixel location.
+ */
+ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel =
+ function(pixel) {
+ // TODO adjust pixel tolerance for applied styles
+ var minPixel = new ol.Pixel(pixel.x - 1, pixel.y - 1);
+ var maxPixel = new ol.Pixel(pixel.x + 1, pixel.y + 1);
+ var map = this.getMap();
+
+ var locationMin = map.getCoordinateFromPixel(minPixel);
+ var locationMax = map.getCoordinateFromPixel(maxPixel);
+ var locationBbox = ol.extent.boundingExtent([locationMin, locationMax]);
+ var filter = new ol.filter.Extent(locationBbox);
+ // TODO do a real intersect against the filtered result for exact matches
+ var candidates = this.getLayer().getFeatures(filter);
+
+ var location = map.getCoordinateFromPixel(pixel);
+ // TODO adjust tolerance for stroke width or use configurable tolerance
+ var tolerance = map.getView().getView2D().getResolution() * 3;
+ var result = [];
+ var candidate, geom;
+ for (var i = 0, ii = candidates.length; i < ii; ++i) {
+ candidate = candidates[i];
+ geom = candidate.getGeometry();
+ if (goog.isFunction(geom.containsCoordinate)) {
+ // For polygons, check if the pixel location is inside the polygon
+ if (geom.containsCoordinate(location)) {
+ result.push(candidate);
+ }
+ } else if (goog.isFunction(geom.distanceFromCoordinate)) {
+ // For lines, check if the ditance to the pixel location is within the
+ // tolerance threshold
+ if (geom.distanceFromCoordinate(location) < tolerance) {
+ result.push(candidate);
+ }
+ } else {
+ // For points, the bbox filter is all we need
+ result.push(candidate);
+ }
+ }
+ return result;
+};
+
+
/**
* @param {goog.events.Event} event Layer change event.
* @private
diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js
index 607bcebc6e..3197bdbe54 100644
--- a/src/ol/renderer/maprenderer.js
+++ b/src/ol/renderer/maprenderer.js
@@ -103,6 +103,29 @@ ol.renderer.Map.prototype.disposeInternal = function() {
ol.renderer.Map.prototype.getCanvas = goog.functions.NULL;
+/**
+ * @param {ol.Pixel} pixel Pixel coordinate relative to the map viewport.
+ * @param {Array.} layers Layers to query.
+ * @return {Array.} Feature information. Layers that are
+ * able to return attribute data will return ol.Feature instances, other
+ * layers will return a string which can either be plain text or markup.
+ */
+ol.renderer.Map.prototype.getFeatureInfoForPixel =
+ function(pixel, layers) {
+ var layer, layerRenderer;
+ var featureInfo = [];
+ for (var i = 0, ii = layers.length; i < ii; ++i) {
+ layer = layers[i];
+ layerRenderer = this.getLayerRenderer(layer);
+ if (goog.isFunction(layerRenderer.getFeatureInfoForPixel)) {
+ featureInfo.push.apply(featureInfo,
+ layerRenderer.getFeatureInfoForPixel(pixel));
+ }
+ }
+ return featureInfo;
+};
+
+
/**
* @param {ol.layer.Layer} layer Layer.
* @protected