Merge pull request #710 from ahocevar/hit-detection
Accurate hit detection. r=@bartvde
This commit is contained in:
@@ -43,6 +43,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="tags">KML</div>
|
<div id="tags">KML</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="span4 pull-right">
|
||||||
|
<div id="info" class="alert alert-success">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,20 @@ var map = new ol.Map({
|
|||||||
var kml = new ol.parser.KML({
|
var kml = new ol.parser.KML({
|
||||||
maxDepth: 1, dimension: 2, extractStyles: true, extractAttributes: true});
|
maxDepth: 1, dimension: 2, extractStyles: true, extractAttributes: true});
|
||||||
|
|
||||||
|
map.on('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(', ') || ' ';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var url = 'data/kml/lines.kml';
|
var url = 'data/kml/lines.kml';
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', url, true);
|
xhr.open('GET', url, true);
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ ol.extent.boundingExtentXYs_ = function(xs, ys, opt_extent) {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the passed coordinate is contained or on the edge
|
* Checks if the passed coordinate is contained or on the edge of the extent.
|
||||||
* of the extent.
|
|
||||||
*
|
*
|
||||||
* @param {ol.Extent} extent Extent.
|
* @param {ol.Extent} extent Extent.
|
||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ ol.renderer.canvas.VectorLayer = function(mapRenderer, layer) {
|
|||||||
this.sketchTransform_ = goog.vec.Mat4.createNumber();
|
this.sketchTransform_ = goog.vec.Mat4.createNumber();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Tile cache entries are arrays. The first item in each array is the tile
|
||||||
|
* itself, the second are the symbol sizes, and the third is the maximum
|
||||||
|
* symbol size.
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.TileCache}
|
* @type {ol.TileCache}
|
||||||
*/
|
*/
|
||||||
@@ -198,40 +202,64 @@ ol.renderer.canvas.VectorLayer.prototype.getTransform = function() {
|
|||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel =
|
ol.renderer.canvas.VectorLayer.prototype.getFeatureInfoForPixel =
|
||||||
function(pixel, success) {
|
function(pixel, success) {
|
||||||
// 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 map = this.getMap();
|
||||||
|
var result = [];
|
||||||
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);
|
var location = map.getCoordinateFromPixel(pixel);
|
||||||
// TODO adjust tolerance for stroke width or use configurable tolerance
|
var tileCoord = this.tileGrid_.getTileCoordForCoordAndResolution(
|
||||||
var tolerance = map.getView().getView2D().getResolution() * 3;
|
location, this.getMap().getView().getView2D().getResolution());
|
||||||
var result = [];
|
var key = tileCoord.toString();
|
||||||
var candidate, geom;
|
if (this.tileCache_.containsKey(key)) {
|
||||||
for (var i = 0, ii = candidates.length; i < ii; ++i) {
|
var cachedTile = this.tileCache_.get(key);
|
||||||
candidate = candidates[i];
|
var symbolSizes = cachedTile[1];
|
||||||
geom = candidate.getGeometry();
|
var maxSymbolSize = cachedTile[2];
|
||||||
if (goog.isFunction(geom.containsCoordinate)) {
|
var halfMaxWidth = maxSymbolSize[0] / 2;
|
||||||
// For polygons, check if the pixel location is inside the polygon
|
var halfMaxHeight = maxSymbolSize[1] / 2;
|
||||||
if (geom.containsCoordinate(location)) {
|
var locationMin = [location[0] - halfMaxWidth, location[1] - halfMaxHeight];
|
||||||
result.push(candidate);
|
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 candidate, geom, type, symbolBounds, symbolSize, halfWidth, halfHeight,
|
||||||
|
coordinates, j;
|
||||||
|
for (var i = 0, ii = candidates.length; i < ii; ++i) {
|
||||||
|
candidate = candidates[i];
|
||||||
|
geom = candidate.getGeometry();
|
||||||
|
type = geom.getType();
|
||||||
|
if (type === ol.geom.GeometryType.POINT ||
|
||||||
|
type === ol.geom.GeometryType.MULTIPOINT) {
|
||||||
|
// For points, check if the pixel coordinate is inside the candidate's
|
||||||
|
// symbol
|
||||||
|
symbolSize = symbolSizes[goog.getUid(candidate)];
|
||||||
|
halfWidth = symbolSize[0] / 2;
|
||||||
|
halfHeight = symbolSize[1] / 2;
|
||||||
|
symbolBounds = ol.extent.boundingExtent(
|
||||||
|
[[location[0] - halfWidth, location[1] - halfHeight],
|
||||||
|
[location[0] + halfWidth, location[1] + halfHeight]]);
|
||||||
|
coordinates = geom.getCoordinates();
|
||||||
|
if (!goog.isArray(coordinates[0])) {
|
||||||
|
coordinates = [coordinates];
|
||||||
|
}
|
||||||
|
for (j = coordinates.length - 1; j >= 0; --j) {
|
||||||
|
if (ol.extent.containsCoordinate(symbolBounds, coordinates[j])) {
|
||||||
|
result.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);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goog.global.setTimeout(function() { success(result); }, 0);
|
goog.global.setTimeout(function() { success(result); }, 0);
|
||||||
@@ -416,17 +444,19 @@ ol.renderer.canvas.VectorLayer.prototype.renderFrame =
|
|||||||
goog.object.extend(tilesToRender, tilesOnSketchCanvas);
|
goog.object.extend(tilesToRender, tilesOnSketchCanvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var symbolSizes = sketchCanvasRenderer.getSymbolSizes(),
|
||||||
|
maxSymbolSize = sketchCanvasRenderer.getMaxSymbolSize();
|
||||||
for (key in tilesToRender) {
|
for (key in tilesToRender) {
|
||||||
tileCoord = tilesToRender[key];
|
tileCoord = tilesToRender[key];
|
||||||
if (this.tileCache_.containsKey(key)) {
|
if (this.tileCache_.containsKey(key)) {
|
||||||
tile = /** @type {HTMLCanvasElement} */ (this.tileCache_.get(key));
|
tile = /** @type {HTMLCanvasElement} */ (this.tileCache_.get(key)[0]);
|
||||||
} else {
|
} else {
|
||||||
tile = /** @type {HTMLCanvasElement} */
|
tile = /** @type {HTMLCanvasElement} */
|
||||||
(this.tileArchetype_.cloneNode(false));
|
(this.tileArchetype_.cloneNode(false));
|
||||||
tile.getContext('2d').drawImage(sketchCanvas,
|
tile.getContext('2d').drawImage(sketchCanvas,
|
||||||
(tileRange.minX - tileCoord.x) * tileSize.width,
|
(tileRange.minX - tileCoord.x) * tileSize.width,
|
||||||
(tileCoord.y - tileRange.maxY) * tileSize.height);
|
(tileCoord.y - tileRange.maxY) * tileSize.height);
|
||||||
this.tileCache_.set(key, tile);
|
this.tileCache_.set(key, [tile, symbolSizes, maxSymbolSize]);
|
||||||
}
|
}
|
||||||
finalContext.drawImage(tile,
|
finalContext.drawImage(tile,
|
||||||
tileSize.width * (tileCoord.x - tileRange.minX),
|
tileSize.width * (tileCoord.x - tileRange.minX),
|
||||||
|
|||||||
@@ -79,6 +79,34 @@ ol.renderer.canvas.VectorRenderer =
|
|||||||
*/
|
*/
|
||||||
this.iconLoadedCallback_ = opt_iconLoadedCallback;
|
this.iconLoadedCallback_ = opt_iconLoadedCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object.<number, Array.<number>>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.symbolSizes_ = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array.<number>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.maxSymbolSize_ = [0, 0];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Object.<number, Array.<number>>} Symbolizer sizes.
|
||||||
|
*/
|
||||||
|
ol.renderer.canvas.VectorRenderer.prototype.getSymbolSizes = function() {
|
||||||
|
return this.symbolSizes_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array.<number>} Maximum symbolizer size.
|
||||||
|
*/
|
||||||
|
ol.renderer.canvas.VectorRenderer.prototype.getMaxSymbolSize = function() {
|
||||||
|
return this.maxSymbolSize_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -129,7 +157,8 @@ ol.renderer.canvas.VectorRenderer.prototype.renderLineStringFeatures_ =
|
|||||||
function(features, symbolizer) {
|
function(features, symbolizer) {
|
||||||
|
|
||||||
var context = this.context_,
|
var context = this.context_,
|
||||||
i, ii, geometry, components, j, jj, line, dim, k, kk, x, y;
|
i, ii, feature, id, currentSize, geometry, components, j, jj, line, dim,
|
||||||
|
k, kk, x, y;
|
||||||
|
|
||||||
context.globalAlpha = symbolizer.opacity;
|
context.globalAlpha = symbolizer.opacity;
|
||||||
context.strokeStyle = symbolizer.strokeColor;
|
context.strokeStyle = symbolizer.strokeColor;
|
||||||
@@ -138,7 +167,15 @@ ol.renderer.canvas.VectorRenderer.prototype.renderLineStringFeatures_ =
|
|||||||
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
|
context.lineJoin = 'round'; // TODO: accept this as a symbolizer property
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
for (i = 0, ii = features.length; i < ii; ++i) {
|
for (i = 0, ii = features.length; i < ii; ++i) {
|
||||||
geometry = features[i].getGeometry();
|
feature = features[i];
|
||||||
|
id = goog.getUid(feature);
|
||||||
|
currentSize = goog.isDef(this.symbolSizes_[id]) ?
|
||||||
|
this.symbolSizes_[id] : [0];
|
||||||
|
currentSize[0] = Math.max(currentSize[0], context.lineWidth);
|
||||||
|
this.symbolSizes_[id] = currentSize;
|
||||||
|
this.maxSymbolSize_ = [Math.max(currentSize[0], this.maxSymbolSize_[0]),
|
||||||
|
Math.max(currentSize[0], this.maxSymbolSize_[1])];
|
||||||
|
geometry = feature.getGeometry();
|
||||||
if (geometry instanceof ol.geom.LineString) {
|
if (geometry instanceof ol.geom.LineString) {
|
||||||
components = [geometry];
|
components = [geometry];
|
||||||
} else {
|
} else {
|
||||||
@@ -175,7 +212,8 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
|
|||||||
function(features, symbolizer) {
|
function(features, symbolizer) {
|
||||||
|
|
||||||
var context = this.context_,
|
var context = this.context_,
|
||||||
content, alpha, i, ii, geometry, components, j, jj, point, vec;
|
content, alpha, i, ii, feature, id, size, geometry, components, j, jj,
|
||||||
|
point, vec;
|
||||||
|
|
||||||
if (symbolizer instanceof ol.style.ShapeLiteral) {
|
if (symbolizer instanceof ol.style.ShapeLiteral) {
|
||||||
content = ol.renderer.canvas.VectorRenderer.renderShape(symbolizer);
|
content = ol.renderer.canvas.VectorRenderer.renderShape(symbolizer);
|
||||||
@@ -198,7 +236,18 @@ ol.renderer.canvas.VectorRenderer.prototype.renderPointFeatures_ =
|
|||||||
context.setTransform(1, 0, 0, 1, -midWidth, -midHeight);
|
context.setTransform(1, 0, 0, 1, -midWidth, -midHeight);
|
||||||
context.globalAlpha = alpha;
|
context.globalAlpha = alpha;
|
||||||
for (i = 0, ii = features.length; i < ii; ++i) {
|
for (i = 0, ii = features.length; i < ii; ++i) {
|
||||||
geometry = features[i].getGeometry();
|
feature = features[i];
|
||||||
|
id = goog.getUid(feature);
|
||||||
|
size = this.symbolSizes_[id];
|
||||||
|
this.symbolSizes_[id] = goog.isDef(size) ?
|
||||||
|
[Math.max(size[0], content.width * this.inverseScale_),
|
||||||
|
Math.max(size[1], content.height * this.inverseScale_)] :
|
||||||
|
[content.width * this.inverseScale_,
|
||||||
|
content.height * this.inverseScale_];
|
||||||
|
this.maxSymbolSize_ =
|
||||||
|
[Math.max(this.maxSymbolSize_[0], this.symbolSizes_[id][0]),
|
||||||
|
Math.max(this.maxSymbolSize_[1], this.symbolSizes_[id][1])];
|
||||||
|
geometry = feature.getGeometry();
|
||||||
if (geometry instanceof ol.geom.Point) {
|
if (geometry instanceof ol.geom.Point) {
|
||||||
components = [geometry];
|
components = [geometry];
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user