Merge pull request #5995 from KlausBenndorf/hitTolerance
Add hit tolerance parameter to ol.Map#forEachFeatureAtPixel
This commit is contained in:
@@ -2,6 +2,32 @@
|
|||||||
|
|
||||||
### Next release
|
### Next release
|
||||||
|
|
||||||
|
#### `ol.Map#forEachFeatureAtPixel` and `ol.Map#hasFeatureAtPixel` parameters have changed
|
||||||
|
|
||||||
|
If you are using the layer filter of one of these methods, please note that you now have to pass in the layer filter via an `ol.AtPixelOptions` object. If you are not using the layer filter the usage has not changed.
|
||||||
|
|
||||||
|
Old syntax:
|
||||||
|
```
|
||||||
|
map.forEachFeatureAtPixel(pixel, callback, callbackThis, layerFilterFn, layerFilterThis);
|
||||||
|
|
||||||
|
map.hasFeatureAtPixel(pixel, layerFilterFn, layerFilterThis);
|
||||||
|
```
|
||||||
|
|
||||||
|
New syntax:
|
||||||
|
```
|
||||||
|
map.forEachFeatureAtPixel(pixel, callback, {
|
||||||
|
layerFilter: layerFilterFn
|
||||||
|
});
|
||||||
|
|
||||||
|
map.hasFeatureAtPixel(pixel, {
|
||||||
|
layerFilter: layerFilterFn
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
To bind a function to a this, please use the bind method of the function (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
|
||||||
|
|
||||||
|
This change is due to the introduction of the `hitTolerance` parameter which can be passed in via this `ol.AtPixelOptions` object, too.
|
||||||
|
|
||||||
#### Use `view.animate()` instead of `map.beforeRender()` and `ol.animation` functions
|
#### Use `view.animate()` instead of `map.beforeRender()` and `ol.animation` functions
|
||||||
|
|
||||||
The `map.beforeRender()` and `ol.animation` functions have been deprecated in favor of a new `view.animate()` function. Use of the deprecated functions will result in a warning during development. These functions are subject to removal in an upcoming release.
|
The `map.beforeRender()` and `ol.animation` functions have been deprecated in favor of a new `view.animate()` function. Use of the deprecated functions will result in a warning during development. These functions are subject to removal in an upcoming release.
|
||||||
|
|||||||
@@ -18,5 +18,13 @@ tags: "select, vector"
|
|||||||
<option value="altclick">Alt+Click</option>
|
<option value="altclick">Alt+Click</option>
|
||||||
<option value="none">None</option>
|
<option value="none">None</option>
|
||||||
</select>
|
</select>
|
||||||
<span id="status"> 0 selected features</span>
|
<span id="status"> 0 selected features</span>
|
||||||
|
<br />
|
||||||
|
<label>Hit tolerance for selecting features </label>
|
||||||
|
<select id="hitTolerance" class="form-control">
|
||||||
|
<option value="0" selected>0 Pixels</option>
|
||||||
|
<option value="5">5 Pixels</option>
|
||||||
|
<option value="10">10 Pixels</option>
|
||||||
|
</select>
|
||||||
|
<canvas id="circle" style="vertical-align: middle"/>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,23 +31,28 @@ var map = new ol.Map({
|
|||||||
var select = null; // ref to currently selected interaction
|
var select = null; // ref to currently selected interaction
|
||||||
|
|
||||||
// select interaction working on "singleclick"
|
// select interaction working on "singleclick"
|
||||||
var selectSingleClick = new ol.interaction.Select();
|
var selectSingleClick = new ol.interaction.Select({
|
||||||
|
multi: true // multi is used in this example if hitTolerance > 0
|
||||||
|
});
|
||||||
|
|
||||||
// select interaction working on "click"
|
// select interaction working on "click"
|
||||||
var selectClick = new ol.interaction.Select({
|
var selectClick = new ol.interaction.Select({
|
||||||
condition: ol.events.condition.click
|
condition: ol.events.condition.click,
|
||||||
|
multi: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// select interaction working on "pointermove"
|
// select interaction working on "pointermove"
|
||||||
var selectPointerMove = new ol.interaction.Select({
|
var selectPointerMove = new ol.interaction.Select({
|
||||||
condition: ol.events.condition.pointerMove
|
condition: ol.events.condition.pointerMove,
|
||||||
|
multi: true
|
||||||
});
|
});
|
||||||
|
|
||||||
var selectAltClick = new ol.interaction.Select({
|
var selectAltClick = new ol.interaction.Select({
|
||||||
condition: function(mapBrowserEvent) {
|
condition: function(mapBrowserEvent) {
|
||||||
return ol.events.condition.click(mapBrowserEvent) &&
|
return ol.events.condition.click(mapBrowserEvent) &&
|
||||||
ol.events.condition.altKeyOnly(mapBrowserEvent);
|
ol.events.condition.altKeyOnly(mapBrowserEvent);
|
||||||
}
|
},
|
||||||
|
multi: true
|
||||||
});
|
});
|
||||||
|
|
||||||
var selectElement = document.getElementById('type');
|
var selectElement = document.getElementById('type');
|
||||||
@@ -85,3 +90,27 @@ var changeInteraction = function() {
|
|||||||
*/
|
*/
|
||||||
selectElement.onchange = changeInteraction;
|
selectElement.onchange = changeInteraction;
|
||||||
changeInteraction();
|
changeInteraction();
|
||||||
|
|
||||||
|
var selectHitToleranceElement = document.getElementById('hitTolerance');
|
||||||
|
var circleCanvas = document.getElementById('circle');
|
||||||
|
|
||||||
|
var changeHitTolerance = function() {
|
||||||
|
var value = parseInt(selectHitToleranceElement.value, 10);
|
||||||
|
selectSingleClick.setHitTolerance(value);
|
||||||
|
selectClick.setHitTolerance(value);
|
||||||
|
selectPointerMove.setHitTolerance(value);
|
||||||
|
selectAltClick.setHitTolerance(value);
|
||||||
|
|
||||||
|
var size = 2 * value + 2;
|
||||||
|
circleCanvas.width = size;
|
||||||
|
circleCanvas.height = size;
|
||||||
|
var ctx = circleCanvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(value + 1, value + 1, value + 0.5, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
selectHitToleranceElement.onchange = changeHitTolerance;
|
||||||
|
changeHitTolerance();
|
||||||
|
|||||||
+55
-3
@@ -302,6 +302,36 @@ olx.MapOptions.prototype.target;
|
|||||||
olx.MapOptions.prototype.view;
|
olx.MapOptions.prototype.view;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object literal with options for the {@link ol.Map#forEachFeatureAtPixel} and
|
||||||
|
* {@link ol.Map#hasFeatureAtPixel} methods.
|
||||||
|
* @typedef {{layerFilter: ((function(ol.layer.Layer): boolean)|undefined),
|
||||||
|
* hitTolerance: (number|undefined)}}
|
||||||
|
*/
|
||||||
|
olx.AtPixelOptions;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layer filter function. The filter function will receive one argument, the
|
||||||
|
* {@link ol.layer.Layer layer-candidate} and it should return a boolean value.
|
||||||
|
* Only layers which are visible and for which this function returns `true`
|
||||||
|
* will be tested for features. By default, all visible layers will be tested.
|
||||||
|
* @type {((function(ol.layer.Layer): boolean)|undefined)}
|
||||||
|
* @api stable
|
||||||
|
*/
|
||||||
|
olx.AtPixelOptions.prototype.layerFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-detection tolerance in pixels. Pixels inside the radius around the given position
|
||||||
|
* will be checked for features. This only works for the canvas renderer and
|
||||||
|
* not for WebGL.
|
||||||
|
* @type {number|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.AtPixelOptions.prototype.hitTolerance;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object literal with config options for the overlay.
|
* Object literal with config options for the overlay.
|
||||||
* @typedef {{id: (number|string|undefined),
|
* @typedef {{id: (number|string|undefined),
|
||||||
@@ -2871,7 +2901,8 @@ olx.interaction.ExtentOptions.prototype.wrapX;
|
|||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* features: (ol.Collection.<ol.Feature>|undefined),
|
* features: (ol.Collection.<ol.Feature>|undefined),
|
||||||
* layers: (undefined|Array.<ol.layer.Layer>|function(ol.layer.Layer): boolean)
|
* layers: (undefined|Array.<ol.layer.Layer>|function(ol.layer.Layer): boolean),
|
||||||
|
* hitTolerance: (number|undefined)
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
olx.interaction.TranslateOptions;
|
olx.interaction.TranslateOptions;
|
||||||
@@ -2898,6 +2929,16 @@ olx.interaction.TranslateOptions.prototype.features;
|
|||||||
olx.interaction.TranslateOptions.prototype.layers;
|
olx.interaction.TranslateOptions.prototype.layers;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-detection tolerance. Pixels inside the radius around the given position
|
||||||
|
* will be checked for features. This only works for the canvas renderer and
|
||||||
|
* not for WebGL.
|
||||||
|
* @type {number|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.interaction.TranslateOptions.prototype.hitTolerance;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{condition: (ol.EventsConditionType|undefined),
|
* @typedef {{condition: (ol.EventsConditionType|undefined),
|
||||||
* duration: (number|undefined),
|
* duration: (number|undefined),
|
||||||
@@ -3173,7 +3214,8 @@ olx.interaction.PointerOptions.prototype.handleUpEvent;
|
|||||||
* multi: (boolean|undefined),
|
* multi: (boolean|undefined),
|
||||||
* features: (ol.Collection.<ol.Feature>|undefined),
|
* features: (ol.Collection.<ol.Feature>|undefined),
|
||||||
* filter: (ol.SelectFilterFunction|undefined),
|
* filter: (ol.SelectFilterFunction|undefined),
|
||||||
* wrapX: (boolean|undefined)}}
|
* wrapX: (boolean|undefined),
|
||||||
|
* hitTolerance: (number|undefined)}}
|
||||||
*/
|
*/
|
||||||
olx.interaction.SelectOptions;
|
olx.interaction.SelectOptions;
|
||||||
|
|
||||||
@@ -3287,6 +3329,16 @@ olx.interaction.SelectOptions.prototype.filter;
|
|||||||
olx.interaction.SelectOptions.prototype.wrapX;
|
olx.interaction.SelectOptions.prototype.wrapX;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-detection tolerance. Pixels inside the radius around the given position
|
||||||
|
* will be checked for features. This only works for the canvas renderer and
|
||||||
|
* not for WebGL.
|
||||||
|
* @type {number|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
olx.interaction.SelectOptions.prototype.hitTolerance;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for snap
|
* Options for snap
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@@ -7623,7 +7675,7 @@ olx.view.FitOptions.prototype.maxZoom;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The duration of the animation in milliseconds. By default, there is no
|
* The duration of the animation in milliseconds. By default, there is no
|
||||||
* animations.
|
* animations.
|
||||||
* @type {number|undefined}
|
* @type {number|undefined}
|
||||||
* @api
|
* @api
|
||||||
|
|||||||
@@ -82,6 +82,12 @@ ol.interaction.Select = function(opt_options) {
|
|||||||
this.filter_ = options.filter ? options.filter :
|
this.filter_ = options.filter ? options.filter :
|
||||||
ol.functions.TRUE;
|
ol.functions.TRUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
|
||||||
|
|
||||||
var featureOverlay = new ol.layer.Vector({
|
var featureOverlay = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
useSpatialIndex: false,
|
useSpatialIndex: false,
|
||||||
@@ -160,6 +166,16 @@ ol.interaction.Select.prototype.getFeatures = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Hit-detection tolerance.
|
||||||
|
* @returns {number} Hit tolerance in pixels.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.interaction.Select.prototype.getHitTolerance = function() {
|
||||||
|
return this.hitTolerance_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the associated {@link ol.layer.Vector vectorlayer} of
|
* Returns the associated {@link ol.layer.Vector vectorlayer} of
|
||||||
* the (last) selected feature. Note that this will not work with any
|
* the (last) selected feature. Note that this will not work with any
|
||||||
@@ -201,7 +217,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
|
|||||||
// the pixel.
|
// the pixel.
|
||||||
ol.obj.clear(this.featureLayerAssociation_);
|
ol.obj.clear(this.featureLayerAssociation_);
|
||||||
map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
|
map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
|
||||||
/**
|
(/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @param {ol.layer.Layer} layer Layer.
|
* @param {ol.layer.Layer} layer Layer.
|
||||||
* @return {boolean|undefined} Continue to iterate over the features.
|
* @return {boolean|undefined} Continue to iterate over the features.
|
||||||
@@ -212,7 +228,10 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
|
|||||||
this.addFeatureLayerAssociation_(feature, layer);
|
this.addFeatureLayerAssociation_(feature, layer);
|
||||||
return !this.multi_;
|
return !this.multi_;
|
||||||
}
|
}
|
||||||
}, this, this.layerFilter_);
|
}).bind(this), {
|
||||||
|
layerFilter: this.layerFilter_,
|
||||||
|
hitTolerance: this.hitTolerance_
|
||||||
|
});
|
||||||
var i;
|
var i;
|
||||||
for (i = features.getLength() - 1; i >= 0; --i) {
|
for (i = features.getLength() - 1; i >= 0; --i) {
|
||||||
var feature = features.item(i);
|
var feature = features.item(i);
|
||||||
@@ -231,7 +250,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
|
|||||||
} else {
|
} else {
|
||||||
// Modify the currently selected feature(s).
|
// Modify the currently selected feature(s).
|
||||||
map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
|
map.forEachFeatureAtPixel(mapBrowserEvent.pixel,
|
||||||
/**
|
(/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @param {ol.layer.Layer} layer Layer.
|
* @param {ol.layer.Layer} layer Layer.
|
||||||
* @return {boolean|undefined} Continue to iterate over the features.
|
* @return {boolean|undefined} Continue to iterate over the features.
|
||||||
@@ -249,7 +268,10 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
|
|||||||
}
|
}
|
||||||
return !this.multi_;
|
return !this.multi_;
|
||||||
}
|
}
|
||||||
}, this, this.layerFilter_);
|
}).bind(this), {
|
||||||
|
layerFilter: this.layerFilter_,
|
||||||
|
hitTolerance: this.hitTolerance_
|
||||||
|
});
|
||||||
var j;
|
var j;
|
||||||
for (j = deselected.length - 1; j >= 0; --j) {
|
for (j = deselected.length - 1; j >= 0; --j) {
|
||||||
features.remove(deselected[j]);
|
features.remove(deselected[j]);
|
||||||
@@ -265,6 +287,18 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-detection tolerance. Pixels inside the radius around the given position
|
||||||
|
* will be checked for features. This only works for the canvas renderer and
|
||||||
|
* not for WebGL.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.interaction.Select.prototype.setHitTolerance = function(hitTolerance) {
|
||||||
|
this.hitTolerance_ = hitTolerance;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the interaction from its current map, if any, and attach it to a new
|
* Remove the interaction from its current map, if any, and attach it to a new
|
||||||
* map, if any. Pass `null` to just remove the interaction from the current map.
|
* map, if any. Pass `null` to just remove the interaction from the current map.
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ ol.interaction.Translate = function(opt_options) {
|
|||||||
*/
|
*/
|
||||||
this.layerFilter_ = layerFilter;
|
this.layerFilter_ = layerFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.hitTolerance_ = options.hitTolerance ? options.hitTolerance : 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ol.Feature}
|
* @type {ol.Feature}
|
||||||
* @private
|
* @private
|
||||||
@@ -197,7 +203,32 @@ ol.interaction.Translate.prototype.featuresAtPixel_ = function(pixel, map) {
|
|||||||
ol.array.includes(this.features_.getArray(), feature)) {
|
ol.array.includes(this.features_.getArray(), feature)) {
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
}, this, this.layerFilter_);
|
}.bind(this), {
|
||||||
|
layerFilter: this.layerFilter_,
|
||||||
|
hitTolerance: this.hitTolerance_
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Hit-detection tolerance.
|
||||||
|
* @returns {number} Hit tolerance in pixels.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.interaction.Translate.prototype.getHitTolerance = function() {
|
||||||
|
return this.hitTolerance_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-detection tolerance. Pixels inside the radius around the given position
|
||||||
|
* will be checked for features. This only works for the canvas renderer and
|
||||||
|
* not for WebGL.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
ol.interaction.Translate.prototype.setHitTolerance = function(hitTolerance) {
|
||||||
|
this.hitTolerance_ = hitTolerance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+18
-28
@@ -567,31 +567,25 @@ ol.Map.prototype.disposeInternal = function() {
|
|||||||
* the {@link ol.layer.Layer layer} of the feature and will be null for
|
* the {@link ol.layer.Layer layer} of the feature and will be null for
|
||||||
* unmanaged layers. To stop detection, callback functions can return a
|
* unmanaged layers. To stop detection, callback functions can return a
|
||||||
* truthy value.
|
* truthy value.
|
||||||
* @param {S=} opt_this Value to use as `this` when executing `callback`.
|
* @param {olx.AtPixelOptions=} opt_options Optional options.
|
||||||
* @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
|
|
||||||
* filter function. The filter function will receive one argument, the
|
|
||||||
* {@link ol.layer.Layer layer-candidate} and it should return a boolean
|
|
||||||
* value. Only layers which are visible and for which this function returns
|
|
||||||
* `true` will be tested for features. By default, all visible layers will
|
|
||||||
* be tested.
|
|
||||||
* @param {U=} opt_this2 Value to use as `this` when executing `layerFilter`.
|
|
||||||
* @return {T|undefined} Callback result, i.e. the return value of last
|
* @return {T|undefined} Callback result, i.e. the return value of last
|
||||||
* callback execution, or the first truthy callback return value.
|
* callback execution, or the first truthy callback return value.
|
||||||
* @template S,T,U
|
* @template S,T
|
||||||
* @api stable
|
* @api stable
|
||||||
*/
|
*/
|
||||||
ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_this, opt_layerFilter, opt_this2) {
|
ol.Map.prototype.forEachFeatureAtPixel = function(pixel, callback, opt_options) {
|
||||||
if (!this.frameState_) {
|
if (!this.frameState_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var coordinate = this.getCoordinateFromPixel(pixel);
|
var coordinate = this.getCoordinateFromPixel(pixel);
|
||||||
var thisArg = opt_this !== undefined ? opt_this : null;
|
opt_options = opt_options !== undefined ? opt_options : {};
|
||||||
var layerFilter = opt_layerFilter !== undefined ?
|
var hitTolerance = opt_options.hitTolerance !== undefined ?
|
||||||
opt_layerFilter : ol.functions.TRUE;
|
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
||||||
var thisArg2 = opt_this2 !== undefined ? opt_this2 : null;
|
var layerFilter = opt_options.layerFilter !== undefined ?
|
||||||
|
opt_options.layerFilter : ol.functions.TRUE;
|
||||||
return this.renderer_.forEachFeatureAtCoordinate(
|
return this.renderer_.forEachFeatureAtCoordinate(
|
||||||
coordinate, this.frameState_, callback, thisArg,
|
coordinate, this.frameState_, hitTolerance, callback, null,
|
||||||
layerFilter, thisArg2);
|
layerFilter, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -637,27 +631,23 @@ ol.Map.prototype.forEachLayerAtPixel = function(pixel, callback, opt_this, opt_l
|
|||||||
* Detect if features intersect a pixel on the viewport. Layers included in the
|
* Detect if features intersect a pixel on the viewport. Layers included in the
|
||||||
* detection can be configured through `opt_layerFilter`.
|
* detection can be configured through `opt_layerFilter`.
|
||||||
* @param {ol.Pixel} pixel Pixel.
|
* @param {ol.Pixel} pixel Pixel.
|
||||||
* @param {(function(this: U, ol.layer.Layer): boolean)=} opt_layerFilter Layer
|
* @param {olx.AtPixelOptions=} opt_options Optional options.
|
||||||
* filter function. The filter function will receive one argument, the
|
|
||||||
* {@link ol.layer.Layer layer-candidate} and it should return a boolean
|
|
||||||
* value. Only layers which are visible and for which this function returns
|
|
||||||
* `true` will be tested for features. By default, all visible layers will
|
|
||||||
* be tested.
|
|
||||||
* @param {U=} opt_this Value to use as `this` when executing `layerFilter`.
|
|
||||||
* @return {boolean} Is there a feature at the given pixel?
|
* @return {boolean} Is there a feature at the given pixel?
|
||||||
* @template U
|
* @template U
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_layerFilter, opt_this) {
|
ol.Map.prototype.hasFeatureAtPixel = function(pixel, opt_options) {
|
||||||
if (!this.frameState_) {
|
if (!this.frameState_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var coordinate = this.getCoordinateFromPixel(pixel);
|
var coordinate = this.getCoordinateFromPixel(pixel);
|
||||||
var layerFilter = opt_layerFilter !== undefined ?
|
opt_options = opt_options !== undefined ? opt_options : {};
|
||||||
opt_layerFilter : ol.functions.TRUE;
|
var layerFilter = opt_options.layerFilter !== undefined ?
|
||||||
var thisArg = opt_this !== undefined ? opt_this : null;
|
opt_options.layerFilter : ol.functions.TRUE;
|
||||||
|
var hitTolerance = opt_options.hitTolerance !== undefined ?
|
||||||
|
opt_options.hitTolerance * this.frameState_.pixelRatio : 0;
|
||||||
return this.renderer_.hasFeatureAtCoordinate(
|
return this.renderer_.hasFeatureAtCoordinate(
|
||||||
coordinate, this.frameState_, layerFilter, thisArg);
|
coordinate, this.frameState_, hitTolerance, layerFilter, null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -77,11 +77,90 @@ ol.render.canvas.ReplayGroup = function(
|
|||||||
* @type {ol.Transform}
|
* @type {ol.Transform}
|
||||||
*/
|
*/
|
||||||
this.hitDetectionTransform_ = ol.transform.create();
|
this.hitDetectionTransform_ = ol.transform.create();
|
||||||
|
|
||||||
};
|
};
|
||||||
ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
|
ol.inherits(ol.render.canvas.ReplayGroup, ol.render.ReplayGroup);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This cache is used for storing calculated pixel circles for increasing performance.
|
||||||
|
* It is a static property to allow each Replaygroup to access it.
|
||||||
|
* @type {Object.<number, Array.<Array.<(boolean|undefined)>>>}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.circleArrayCache_ = {
|
||||||
|
0: [[true]]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method fills a row in the array from the given coordinate to the
|
||||||
|
* middle with `true`.
|
||||||
|
* @param {Array.<Array.<(boolean|undefined)>>} array The array that will be altered.
|
||||||
|
* @param {number} x X coordinate.
|
||||||
|
* @param {number} y Y coordinate.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_ = function(array, x, y) {
|
||||||
|
var i;
|
||||||
|
var radius = Math.floor(array.length / 2);
|
||||||
|
if (x >= radius) {
|
||||||
|
for (i = radius; i < x; i++) {
|
||||||
|
array[i][y] = true;
|
||||||
|
}
|
||||||
|
} else if (x < radius) {
|
||||||
|
for (i = x + 1; i < radius; i++) {
|
||||||
|
array[i][y] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This methods creates a circle inside a fitting array. Points inside the
|
||||||
|
* circle are marked by true, points on the outside are undefined.
|
||||||
|
* It uses the midpoint circle algorithm.
|
||||||
|
* A cache is used to increase performance.
|
||||||
|
* @param {number} radius Radius.
|
||||||
|
* @returns {Array.<Array.<(boolean|undefined)>>} An array with marked circle points.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
ol.render.canvas.ReplayGroup.getCircleArray_ = function(radius) {
|
||||||
|
if (ol.render.canvas.ReplayGroup.circleArrayCache_[radius] !== undefined) {
|
||||||
|
return ol.render.canvas.ReplayGroup.circleArrayCache_[radius];
|
||||||
|
}
|
||||||
|
|
||||||
|
var arraySize = radius * 2 + 1;
|
||||||
|
var arr = new Array(arraySize);
|
||||||
|
for (var i = 0; i < arraySize; i++) {
|
||||||
|
arr[i] = new Array(arraySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
var x = radius;
|
||||||
|
var y = 0;
|
||||||
|
var error = 0;
|
||||||
|
|
||||||
|
while (x >= y) {
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius + y);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius + x);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius + x);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius + y);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - x, radius - y);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius - y, radius - x);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + y, radius - x);
|
||||||
|
ol.render.canvas.ReplayGroup.fillCircleArrayRowToMiddle_(arr, radius + x, radius - y);
|
||||||
|
|
||||||
|
y++;
|
||||||
|
error += 1 + 2 * y;
|
||||||
|
if (2 * (error - x) + 1 > 0) {
|
||||||
|
x -= 1;
|
||||||
|
error += 1 - 2 * x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol.render.canvas.ReplayGroup.circleArrayCache_[radius] = arr;
|
||||||
|
return arr;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME empty description for jsdoc
|
* FIXME empty description for jsdoc
|
||||||
*/
|
*/
|
||||||
@@ -101,6 +180,7 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() {
|
|||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} rotation Rotation.
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
|
* @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
|
||||||
* to skip.
|
* to skip.
|
||||||
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
||||||
@@ -109,16 +189,23 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() {
|
|||||||
* @template T
|
* @template T
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
||||||
coordinate, resolution, rotation, skippedFeaturesHash, callback) {
|
coordinate, resolution, rotation, hitTolerance, skippedFeaturesHash, callback) {
|
||||||
|
|
||||||
|
hitTolerance = Math.round(hitTolerance);
|
||||||
|
var contextSize = hitTolerance * 2 + 1;
|
||||||
var transform = ol.transform.compose(this.hitDetectionTransform_,
|
var transform = ol.transform.compose(this.hitDetectionTransform_,
|
||||||
0.5, 0.5,
|
hitTolerance + 0.5, hitTolerance + 0.5,
|
||||||
1 / resolution, -1 / resolution,
|
1 / resolution, -1 / resolution,
|
||||||
-rotation,
|
-rotation,
|
||||||
-coordinate[0], -coordinate[1]);
|
-coordinate[0], -coordinate[1]);
|
||||||
|
|
||||||
var context = this.hitDetectionContext_;
|
var context = this.hitDetectionContext_;
|
||||||
context.clearRect(0, 0, 1, 1);
|
|
||||||
|
if (context.canvas.width !== contextSize || context.canvas.height !== contextSize) {
|
||||||
|
context.canvas.width = contextSize;
|
||||||
|
context.canvas.height = contextSize;
|
||||||
|
} else {
|
||||||
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {ol.Extent}
|
* @type {ol.Extent}
|
||||||
@@ -127,9 +214,11 @@ ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
|||||||
if (this.renderBuffer_ !== undefined) {
|
if (this.renderBuffer_ !== undefined) {
|
||||||
hitExtent = ol.extent.createEmpty();
|
hitExtent = ol.extent.createEmpty();
|
||||||
ol.extent.extendCoordinate(hitExtent, coordinate);
|
ol.extent.extendCoordinate(hitExtent, coordinate);
|
||||||
ol.extent.buffer(hitExtent, resolution * this.renderBuffer_, hitExtent);
|
ol.extent.buffer(hitExtent, resolution * (this.renderBuffer_ + hitTolerance), hitExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mask = ol.render.canvas.ReplayGroup.getCircleArray_(hitTolerance);
|
||||||
|
|
||||||
return this.replayHitDetection_(context, transform, rotation,
|
return this.replayHitDetection_(context, transform, rotation,
|
||||||
skippedFeaturesHash,
|
skippedFeaturesHash,
|
||||||
/**
|
/**
|
||||||
@@ -137,13 +226,21 @@ ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function(
|
|||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
*/
|
*/
|
||||||
function(feature) {
|
function(feature) {
|
||||||
var imageData = context.getImageData(0, 0, 1, 1).data;
|
var imageData = context.getImageData(0, 0, contextSize, contextSize).data;
|
||||||
if (imageData[3] > 0) {
|
for (var i = 0; i < contextSize; i++) {
|
||||||
var result = callback(feature);
|
for (var j = 0; j < contextSize; j++) {
|
||||||
if (result) {
|
if (mask[i][j]) {
|
||||||
return result;
|
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
||||||
|
var result = callback(feature);
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
context.clearRect(0, 0, 1, 1);
|
|
||||||
}
|
}
|
||||||
}, hitExtent);
|
}, hitExtent);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,14 +98,14 @@ ol.renderer.canvas.IntermediateCanvas.prototype.getImageTransform = function() {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.canvas.IntermediateCanvas.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
var source = layer.getSource();
|
var source = layer.getSource();
|
||||||
var resolution = frameState.viewState.resolution;
|
var resolution = frameState.viewState.resolution;
|
||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
var skippedFeatureUids = frameState.skippedFeatureUids;
|
var skippedFeatureUids = frameState.skippedFeatureUids;
|
||||||
return source.forEachFeatureAtCoordinate(
|
return source.forEachFeatureAtCoordinate(
|
||||||
coordinate, resolution, rotation, skippedFeatureUids,
|
coordinate, resolution, rotation, hitTolerance, skippedFeatureUids,
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ ol.renderer.canvas.Layer.prototype.dispatchComposeEvent_ = function(type, contex
|
|||||||
*/
|
*/
|
||||||
ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.canvas.Layer.prototype.forEachLayerAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
||||||
var hasFeature = this.forEachFeatureAtCoordinate(
|
var hasFeature = this.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, ol.functions.TRUE, this);
|
coordinate, frameState, 0, ol.functions.TRUE, this);
|
||||||
|
|
||||||
if (hasFeature) {
|
if (hasFeature) {
|
||||||
return callback.call(thisArg, this.getLayer(), null);
|
return callback.call(thisArg, this.getLayer(), null);
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ ol.renderer.canvas.VectorLayer.prototype.composeFrame = function(frameState, lay
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
|
||||||
if (!this.replayGroup_) {
|
if (!this.replayGroup_) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
@@ -187,7 +187,7 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = function(c
|
|||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
|
return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution,
|
||||||
rotation, {},
|
rotation, hitTolerance, {},
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
|
|||||||
@@ -176,9 +176,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
|
||||||
var resolution = frameState.viewState.resolution;
|
var resolution = frameState.viewState.resolution;
|
||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
|
hitTolerance = hitTolerance == undefined ? 0 : hitTolerance;
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
@@ -195,7 +196,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
|
|||||||
tile = replayables[i];
|
tile = replayables[i];
|
||||||
tileCoord = tile.tileCoord;
|
tileCoord = tile.tileCoord;
|
||||||
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent);
|
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent);
|
||||||
if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
|
if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
|
if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
|
||||||
@@ -212,7 +213,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
|
|||||||
}
|
}
|
||||||
replayGroup = tile.getReplayState().replayGroup;
|
replayGroup = tile.getReplayState().replayGroup;
|
||||||
found = found || replayGroup.forEachFeatureAtCoordinate(
|
found = found || replayGroup.forEachFeatureAtCoordinate(
|
||||||
tileSpaceCoordinate, resolution, rotation, {},
|
tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ ol.inherits(ol.renderer.Layer, ol.Observable);
|
|||||||
/**
|
/**
|
||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
* @param {olx.FrameState} frameState Frame state.
|
* @param {olx.FrameState} frameState Frame state.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T}
|
* @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T}
|
||||||
* callback Feature callback.
|
* callback Feature callback.
|
||||||
* @param {S} thisArg Value to use as `this` when executing `callback`.
|
* @param {S} thisArg Value to use as `this` when executing `callback`.
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ ol.renderer.Map.expireIconCache_ = function(map, frameState) {
|
|||||||
/**
|
/**
|
||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
* @param {olx.FrameState} frameState FrameState.
|
* @param {olx.FrameState} frameState FrameState.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {function(this: S, (ol.Feature|ol.render.Feature),
|
* @param {function(this: S, (ol.Feature|ol.render.Feature),
|
||||||
* ol.layer.Layer): T} callback Feature callback.
|
* ol.layer.Layer): T} callback Feature callback.
|
||||||
* @param {S} thisArg Value to use as `this` when executing `callback`.
|
* @param {S} thisArg Value to use as `this` when executing `callback`.
|
||||||
@@ -111,7 +112,7 @@ ol.renderer.Map.expireIconCache_ = function(map, frameState) {
|
|||||||
* @return {T|undefined} Callback result.
|
* @return {T|undefined} Callback result.
|
||||||
* @template S,T,U
|
* @template S,T,U
|
||||||
*/
|
*/
|
||||||
ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
|
ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
|
||||||
layerFilter, thisArg2) {
|
layerFilter, thisArg2) {
|
||||||
var result;
|
var result;
|
||||||
var viewState = frameState.viewState;
|
var viewState = frameState.viewState;
|
||||||
@@ -155,7 +156,7 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, fram
|
|||||||
if (layer.getSource()) {
|
if (layer.getSource()) {
|
||||||
result = layerRenderer.forEachFeatureAtCoordinate(
|
result = layerRenderer.forEachFeatureAtCoordinate(
|
||||||
layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
|
layer.getSource().getWrapX() ? translatedCoordinate : coordinate,
|
||||||
frameState, forEachFeatureAtCoordinate, thisArg);
|
frameState, hitTolerance, forEachFeatureAtCoordinate, thisArg);
|
||||||
}
|
}
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
@@ -188,6 +189,7 @@ ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, call
|
|||||||
/**
|
/**
|
||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
* @param {olx.FrameState} frameState FrameState.
|
* @param {olx.FrameState} frameState FrameState.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
|
* @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter
|
||||||
* function, only layers which are visible and for which this function
|
* function, only layers which are visible and for which this function
|
||||||
* returns `true` will be tested for features. By default, all visible
|
* returns `true` will be tested for features. By default, all visible
|
||||||
@@ -196,9 +198,9 @@ ol.renderer.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, call
|
|||||||
* @return {boolean} Is there a feature at the given coordinate?
|
* @return {boolean} Is there a feature at the given coordinate?
|
||||||
* @template U
|
* @template U
|
||||||
*/
|
*/
|
||||||
ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
|
ol.renderer.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
|
||||||
var hasFeature = this.forEachFeatureAtCoordinate(
|
var hasFeature = this.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, ol.functions.TRUE, this, layerFilter, thisArg);
|
coordinate, frameState, hitTolerance, ol.functions.TRUE, this, layerFilter, thisArg);
|
||||||
|
|
||||||
return hasFeature !== undefined;
|
return hasFeature !== undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,14 +68,14 @@ ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
|
||||||
var layer = this.getLayer();
|
var layer = this.getLayer();
|
||||||
var source = layer.getSource();
|
var source = layer.getSource();
|
||||||
var resolution = frameState.viewState.resolution;
|
var resolution = frameState.viewState.resolution;
|
||||||
var rotation = frameState.viewState.rotation;
|
var rotation = frameState.viewState.rotation;
|
||||||
var skippedFeatureUids = frameState.skippedFeatureUids;
|
var skippedFeatureUids = frameState.skippedFeatureUids;
|
||||||
return source.forEachFeatureAtCoordinate(
|
return source.forEachFeatureAtCoordinate(
|
||||||
coordinate, resolution, rotation, skippedFeatureUids,
|
coordinate, resolution, rotation, hitTolerance, skippedFeatureUids,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
@@ -213,7 +213,7 @@ ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvas
|
|||||||
*/
|
*/
|
||||||
ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
|
ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) {
|
||||||
var hasFeature = this.forEachFeatureAtCoordinate(
|
var hasFeature = this.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, ol.functions.TRUE, this);
|
coordinate, frameState, 0, ol.functions.TRUE, this);
|
||||||
return hasFeature !== undefined;
|
return hasFeature !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, fra
|
|||||||
var coordinate = ol.transform.apply(
|
var coordinate = ol.transform.apply(
|
||||||
frameState.pixelToCoordinateTransform, pixel.slice());
|
frameState.pixelToCoordinateTransform, pixel.slice());
|
||||||
var hasFeature = this.forEachFeatureAtCoordinate(
|
var hasFeature = this.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, ol.functions.TRUE, this);
|
coordinate, frameState, 0, ol.functions.TRUE, this);
|
||||||
|
|
||||||
if (hasFeature) {
|
if (hasFeature) {
|
||||||
return callback.call(thisArg, this.getLayer(), null);
|
return callback.call(thisArg, this.getLayer(), null);
|
||||||
|
|||||||
@@ -502,7 +502,7 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg,
|
ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg,
|
||||||
layerFilter, thisArg2) {
|
layerFilter, thisArg2) {
|
||||||
var result;
|
var result;
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate
|
|||||||
layerFilter.call(thisArg2, layer)) {
|
layerFilter.call(thisArg2, layer)) {
|
||||||
var layerRenderer = this.getLayerRenderer(layer);
|
var layerRenderer = this.getLayerRenderer(layer);
|
||||||
result = layerRenderer.forEachFeatureAtCoordinate(
|
result = layerRenderer.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, callback, thisArg);
|
coordinate, frameState, hitTolerance, callback, thisArg);
|
||||||
if (result) {
|
if (result) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -535,7 +535,7 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, layerFilter, thisArg) {
|
ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) {
|
||||||
var hasFeature = false;
|
var hasFeature = false;
|
||||||
|
|
||||||
if (this.getGL().isContextLost()) {
|
if (this.getGL().isContextLost()) {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, callback, thisArg) {
|
ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) {
|
||||||
if (!this.replayGroup_ || !this.layerState_) {
|
if (!this.replayGroup_ || !this.layerState_) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -155,14 +155,14 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = function(extent, resol
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
|
ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function(
|
||||||
coordinate, resolution, rotation, skippedFeatureUids, callback) {
|
coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, callback) {
|
||||||
if (!this.replayGroup_) {
|
if (!this.replayGroup_) {
|
||||||
return undefined;
|
return undefined;
|
||||||
} else {
|
} else {
|
||||||
/** @type {Object.<string, boolean>} */
|
/** @type {Object.<string, boolean>} */
|
||||||
var features = {};
|
var features = {};
|
||||||
return this.replayGroup_.forEachFeatureAtCoordinate(
|
return this.replayGroup_.forEachFeatureAtCoordinate(
|
||||||
coordinate, resolution, 0, skippedFeatureUids,
|
coordinate, resolution, 0, hitTolerance, skippedFeatureUids,
|
||||||
/**
|
/**
|
||||||
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
* @param {ol.Feature|ol.render.Feature} feature Feature.
|
||||||
* @return {?} Callback result.
|
* @return {?} Callback result.
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ ol.source.Source.toAttributionsArray_ = function(attributionLike) {
|
|||||||
* @param {ol.Coordinate} coordinate Coordinate.
|
* @param {ol.Coordinate} coordinate Coordinate.
|
||||||
* @param {number} resolution Resolution.
|
* @param {number} resolution Resolution.
|
||||||
* @param {number} rotation Rotation.
|
* @param {number} rotation Rotation.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
* @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
|
* @param {Object.<string, boolean>} skippedFeatureUids Skipped feature uids.
|
||||||
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
* @param {function((ol.Feature|ol.render.Feature)): T} callback Feature
|
||||||
* callback.
|
* callback.
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
goog.provide('ol.test.render.canvas.ReplayGroup');
|
||||||
|
|
||||||
|
goog.require('ol.render.canvas.ReplayGroup');
|
||||||
|
|
||||||
|
|
||||||
|
describe('ol.render.canvas.ReplayGroup', function() {
|
||||||
|
|
||||||
|
describe('#getCircleArray_', function() {
|
||||||
|
it('creates an array with a pixelated circle marked with true', function() {
|
||||||
|
var radius = 10;
|
||||||
|
var minRadiusSq = Math.pow(radius - Math.SQRT2, 2);
|
||||||
|
var maxRadiusSq = Math.pow(radius + Math.SQRT2, 2);
|
||||||
|
var circleArray = ol.render.canvas.ReplayGroup.getCircleArray_(radius);
|
||||||
|
var size = radius * 2 + 1;
|
||||||
|
expect(circleArray.length).to.be(size);
|
||||||
|
|
||||||
|
for (var i = 0; i < size; i++) {
|
||||||
|
expect(circleArray[i].length).to.be(size);
|
||||||
|
for (var j = 0; j < size; j++) {
|
||||||
|
var dx = Math.abs(radius - i);
|
||||||
|
var dy = Math.abs(radius - j);
|
||||||
|
var distanceSq = Math.pow(dx, 2) + Math.pow(dy, 2);
|
||||||
|
if (circleArray[i][j] === true) {
|
||||||
|
expect(distanceSq).to.be.within(0, maxRadiusSq);
|
||||||
|
} else {
|
||||||
|
expect(distanceSq).to.be.within(minRadiusSq, Infinity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -30,7 +30,7 @@ describe('ol.renderer.canvas.Map', function() {
|
|||||||
|
|
||||||
var layer, map, target;
|
var layer, map, target;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function(done) {
|
||||||
target = document.createElement('div');
|
target = document.createElement('div');
|
||||||
target.style.width = '100px';
|
target.style.width = '100px';
|
||||||
target.style.height = '100px';
|
target.style.height = '100px';
|
||||||
@@ -42,6 +42,14 @@ describe('ol.renderer.canvas.Map', function() {
|
|||||||
zoom: 0
|
zoom: 0
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 1 x 1 pixel black icon
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.onload = function() {
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNiAAAABgADNjd8qAAAAABJRU5ErkJggg==';
|
||||||
|
|
||||||
layer = new ol.layer.Vector({
|
layer = new ol.layer.Vector({
|
||||||
source: new ol.source.Vector({
|
source: new ol.source.Vector({
|
||||||
features: [
|
features: [
|
||||||
@@ -49,6 +57,12 @@ describe('ol.renderer.canvas.Map', function() {
|
|||||||
geometry: new ol.geom.Point([0, 0])
|
geometry: new ol.geom.Point([0, 0])
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
}),
|
||||||
|
style: new ol.style.Style({
|
||||||
|
image: new ol.style.Icon({
|
||||||
|
img : img,
|
||||||
|
imgSize: [1, 1]
|
||||||
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -97,10 +111,11 @@ describe('ol.renderer.canvas.Map', function() {
|
|||||||
map.addLayer(layer);
|
map.addLayer(layer);
|
||||||
map.renderSync();
|
map.renderSync();
|
||||||
var cb = sinon.spy();
|
var cb = sinon.spy();
|
||||||
map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb, null,
|
map.forEachFeatureAtPixel(map.getPixelFromCoordinate([0, 0]), cb, {
|
||||||
function() {
|
layerFilter: function() {
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
expect(cb).to.not.be.called();
|
expect(cb).to.not.be.called();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,6 +128,39 @@ describe('ol.renderer.canvas.Map', function() {
|
|||||||
}).to.not.throwException();
|
}).to.not.throwException();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('calls callback for clicks inside of the hitTolerance', function() {
|
||||||
|
map.addLayer(layer);
|
||||||
|
map.renderSync();
|
||||||
|
var cb1 = sinon.spy();
|
||||||
|
var cb2 = sinon.spy();
|
||||||
|
|
||||||
|
var pixel = map.getPixelFromCoordinate([0, 0]);
|
||||||
|
|
||||||
|
var pixelsInside = [
|
||||||
|
[pixel[0] + 9, pixel[1]],
|
||||||
|
[pixel[0] - 9, pixel[1]],
|
||||||
|
[pixel[0], pixel[1] + 9],
|
||||||
|
[pixel[0], pixel[1] - 9]
|
||||||
|
];
|
||||||
|
|
||||||
|
var pixelsOutside = [
|
||||||
|
[pixel[0] + 9, pixel[1] + 9],
|
||||||
|
[pixel[0] - 9, pixel[1] + 9],
|
||||||
|
[pixel[0] + 9, pixel[1] - 9],
|
||||||
|
[pixel[0] - 9, pixel[1] - 9]
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i = 0; i < 4; i++) {
|
||||||
|
map.forEachFeatureAtPixel(pixelsInside[i], cb1, {hitTolerance:10});
|
||||||
|
}
|
||||||
|
expect(cb1.callCount).to.be(4);
|
||||||
|
expect(cb1.firstCall.args[1]).to.be(layer);
|
||||||
|
|
||||||
|
for (var j = 0; j < 4; j++) {
|
||||||
|
map.forEachFeatureAtPixel(pixelsOutside[j], cb2, {hitTolerance:10});
|
||||||
|
}
|
||||||
|
expect(cb2).not.to.be.called();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#renderFrame()', function() {
|
describe('#renderFrame()', function() {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
|
|||||||
var replayGroup = {};
|
var replayGroup = {};
|
||||||
renderer.replayGroup_ = replayGroup;
|
renderer.replayGroup_ = replayGroup;
|
||||||
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
|
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
|
||||||
resolution, rotation, skippedFeaturesUids, callback) {
|
resolution, rotation, hitTolerance, skippedFeaturesUids, callback) {
|
||||||
var feature = new ol.Feature();
|
var feature = new ol.Feature();
|
||||||
callback(feature);
|
callback(feature);
|
||||||
callback(feature);
|
callback(feature);
|
||||||
@@ -99,7 +99,7 @@ describe('ol.renderer.canvas.VectorLayer', function() {
|
|||||||
};
|
};
|
||||||
frameState.layerStates[ol.getUid(layer)] = {};
|
frameState.layerStates[ol.getUid(layer)] = {};
|
||||||
renderer.forEachFeatureAtCoordinate(
|
renderer.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, spy, undefined);
|
coordinate, frameState, 0, spy, undefined);
|
||||||
expect(spy.callCount).to.be(1);
|
expect(spy.callCount).to.be(1);
|
||||||
expect(spy.getCall(0).args[1]).to.equal(layer);
|
expect(spy.getCall(0).args[1]).to.equal(layer);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
});
|
});
|
||||||
renderer = new ol.renderer.canvas.VectorTileLayer(layer);
|
renderer = new ol.renderer.canvas.VectorTileLayer(layer);
|
||||||
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
|
replayGroup.forEachFeatureAtCoordinate = function(coordinate,
|
||||||
resolution, rotation, skippedFeaturesUids, callback) {
|
resolution, rotation, hitTolerance, skippedFeaturesUids, callback) {
|
||||||
var feature = new ol.Feature();
|
var feature = new ol.Feature();
|
||||||
callback(feature);
|
callback(feature);
|
||||||
callback(feature);
|
callback(feature);
|
||||||
@@ -190,7 +190,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
|||||||
frameState.layerStates[ol.getUid(layer)] = {};
|
frameState.layerStates[ol.getUid(layer)] = {};
|
||||||
renderer.renderedTiles = [new TileClass([0, 0, -1])];
|
renderer.renderedTiles = [new TileClass([0, 0, -1])];
|
||||||
renderer.forEachFeatureAtCoordinate(
|
renderer.forEachFeatureAtCoordinate(
|
||||||
coordinate, frameState, spy, undefined);
|
coordinate, frameState, 0, spy, undefined);
|
||||||
expect(spy.callCount).to.be(1);
|
expect(spy.callCount).to.be(1);
|
||||||
expect(spy.getCall(0).args[1]).to.equal(layer);
|
expect(spy.getCall(0).args[1]).to.equal(layer);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user