Simpler API with hitDetection option

This commit is contained in:
Andreas Hocevar
2020-12-01 13:47:40 +01:00
parent d0a1c10cec
commit 128d20abf3
3 changed files with 68 additions and 84 deletions
+2 -2
View File
@@ -52,8 +52,8 @@ const map = new Map({
}); });
const modify = new Modify({ const modify = new Modify({
layer: vectorLayer, hitDetection: vectorLayer,
style: function () {}, // do not render the modification vertex source: vectorSource,
}); });
modify.on(['modifystart', 'modifyend'], function (evt) { modify.on(['modifystart', 'modifyend'], function (evt) {
target.style.cursor = evt.type === 'modifystart' ? 'grabbing' : 'pointer'; target.style.cursor = evt.type === 'modifystart' ? 'grabbing' : 'pointer';
+32 -39
View File
@@ -117,16 +117,15 @@ const ModifyEventType = {
* in the same order. The `geometries` are only useful when modifying geometry collections, where * in the same order. The `geometries` are only useful when modifying geometry collections, where
* the geometry will be the particular geometry from the collection that is being modified. * the geometry will be the particular geometry from the collection that is being modified.
* @property {VectorSource} [source] The vector source with * @property {VectorSource} [source] The vector source with
* features to modify. If a vector source is not provided, a layer or feature collection * features to modify. If a vector source is not provided, a feature collection
* must be provided with the `layer` or `features` option. * must be provided with the `features` option.
* @property {import("../layer/BaseVector").default} [layer] The layer with * @property {boolean|import("../layer/BaseVector").default} [hitDetection] When configured, point
* features to modify. When provided, point features will considered for modification based on * features will considered for modification based on their visual appearance, instead of being within
* their visual appearance on this layer, instead of being within the `pixelTolerance` from the * the `pixelTolerance` from the pointer location. When a {@link module:ol/layer/BaseVector} is
* pointer location. When no `source` or `features` are configured, this layer's source will * provided, only the rendered representation of the features on that layer will be considered.
* be used as source for modification candidates.
* @property {Collection<Feature>} [features] * @property {Collection<Feature>} [features]
* The features the interaction works on. If a feature collection is not * The features the interaction works on. If a feature collection is not
* provided, a layer or vector source must be provided with the `layer` or `source` option. * provided, a vector source must be provided with the `source` option.
* @property {boolean} [wrapX=false] Wrap the world horizontally on the sketch * @property {boolean} [wrapX=false] Wrap the world horizontally on the sketch
* overlay. * overlay.
*/ */
@@ -170,14 +169,13 @@ export class ModifyEvent extends Event {
* `source` option. If you want to modify features in a collection (for example, * `source` option. If you want to modify features in a collection (for example,
* the collection used by a select interaction), construct the interaction with * the collection used by a select interaction), construct the interaction with
* the `features` option. The interaction must be constructed with either a * the `features` option. The interaction must be constructed with either a
* `source`, `features` or `layer` option. * `source` or `features` option.
* *
* Cartesian distance from the pointer is used to determine the features that * Cartesian distance from the pointer is used to determine the features that
* will be modified. This means that geometries will only be considered for * will be modified. This means that geometries will only be considered for
* modification when they are within the configured `pixelTolerane`. For point * modification when they are within the configured `pixelTolerane`. For point
* geometries, hit detection can be used to match their visual appearance. To * geometries, the `hitDetection` option can be used to match their visual
* enable hit detection, the interaction has to be configured with the `layer` * appearance.
* that contains the points.
* *
* By default, the interaction will allow deletion of vertices when the `alt` * By default, the interaction will allow deletion of vertices when the `alt`
* key is pressed. To configure the interaction with a different condition * key is pressed. To configure the interaction with a different condition
@@ -333,40 +331,33 @@ class Modify extends PointerInteraction {
this.source_ = null; this.source_ = null;
/** /**
* @type {import("../layer/BaseVector").default} * @type {boolean|import("../layer/BaseVector").default}
*/ */
this.layer_ = null; this.hitDetection_ = null;
let features; let features;
if (options.features) { if (options.features) {
features = options.features; features = options.features;
} else { } else if (options.source) {
const source = options.source this.source_ = options.source;
? options.source features = new Collection(this.source_.getFeatures());
: options.layer this.source_.addEventListener(
? options.layer.getSource() VectorEventType.ADDFEATURE,
: undefined; this.handleSourceAdd_.bind(this)
if (source) { );
this.source_ = source; this.source_.addEventListener(
features = new Collection(this.source_.getFeatures()); VectorEventType.REMOVEFEATURE,
this.source_.addEventListener( this.handleSourceRemove_.bind(this)
VectorEventType.ADDFEATURE, );
this.handleSourceAdd_.bind(this)
);
this.source_.addEventListener(
VectorEventType.REMOVEFEATURE,
this.handleSourceRemove_.bind(this)
);
}
}
if (options.layer) {
this.layer_ = options.layer;
} }
if (!features) { if (!features) {
throw new Error( throw new Error(
'The modify interaction requires features, a source or a layer' 'The modify interaction requires features, a source or a layer'
); );
} }
if (options.hitDetection) {
this.hitDetection_ = options.hitDetection;
}
/** /**
* @type {Collection<import("../Feature.js").FeatureLike>} * @type {Collection<import("../Feature.js").FeatureLike>}
@@ -1123,7 +1114,11 @@ class Modify extends PointerInteraction {
}; };
let box, hitPointGeometry; let box, hitPointGeometry;
if (this.layer_) { if (this.hitDetection_) {
const layerFilter =
typeof this.hitDetection_ === 'object'
? (layer) => layer === this.hitDetection_
: undefined;
map.forEachFeatureAtPixel( map.forEachFeatureAtPixel(
pixel, pixel,
(feature, layer, geometry) => { (feature, layer, geometry) => {
@@ -1134,9 +1129,7 @@ class Modify extends PointerInteraction {
} }
return true; return true;
}, },
{ {layerFilter}
layerFilter: (layer) => layer === this.layer_,
}
); );
} }
if (!box) { if (!box) {
+34 -43
View File
@@ -1,4 +1,5 @@
import Circle from '../../../../src/ol/geom/Circle.js'; import Circle from '../../../../src/ol/geom/Circle.js';
import CircleStyle from '../../../../src/ol/style/Circle.js';
import Collection from '../../../../src/ol/Collection.js'; import Collection from '../../../../src/ol/Collection.js';
import Event from '../../../../src/ol/events/Event.js'; import Event from '../../../../src/ol/events/Event.js';
import Feature from '../../../../src/ol/Feature.js'; import Feature from '../../../../src/ol/Feature.js';
@@ -13,6 +14,7 @@ import Snap from '../../../../src/ol/interaction/Snap.js';
import VectorLayer from '../../../../src/ol/layer/Vector.js'; import VectorLayer from '../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../src/ol/source/Vector.js'; import VectorSource from '../../../../src/ol/source/Vector.js';
import View from '../../../../src/ol/View.js'; import View from '../../../../src/ol/View.js';
import {Fill, Style} from '../../../../src/ol/style.js';
import {MultiPoint} from '../../../../src/ol/geom.js'; import {MultiPoint} from '../../../../src/ol/geom.js';
import { import {
clearUserProjection, clearUserProjection,
@@ -22,7 +24,7 @@ import {doubleClick} from '../../../../src/ol/events/condition.js';
import {getValues} from '../../../../src/ol/obj.js'; import {getValues} from '../../../../src/ol/obj.js';
describe('ol.interaction.Modify', function () { describe('ol.interaction.Modify', function () {
let target, map, source, features; let target, map, layer, source, features;
const width = 360; const width = 360;
const height = 180; const height = 180;
@@ -56,7 +58,7 @@ describe('ol.interaction.Modify', function () {
features: features, features: features,
}); });
const layer = new VectorLayer({source: source}); layer = new VectorLayer({source: source});
map = new Map({ map = new Map({
target: target, target: target,
@@ -196,53 +198,15 @@ describe('ol.interaction.Modify', function () {
expect(rbushEntries[0].feature).to.be(feature); expect(rbushEntries[0].feature).to.be(feature);
}); });
it('accepts a layer for modification features', function () { it('accepts a hitDetection option', function () {
const feature = new Feature(new Point([0, 0])); const feature = new Feature(new Point([0, 0]));
const source = new VectorSource({features: [feature]}); const source = new VectorSource({features: [feature]});
const layer = new VectorLayer({source: source}); const layer = new VectorLayer({source: source});
const modify = new Modify({layer: layer}); const modify = new Modify({hitDetection: layer, source: source});
const rbushEntries = modify.rBush_.getAll(); const rbushEntries = modify.rBush_.getAll();
expect(rbushEntries.length).to.be(1); expect(rbushEntries.length).to.be(1);
expect(rbushEntries[0].feature).to.be(feature); expect(rbushEntries[0].feature).to.be(feature);
expect(modify.layer_).to.be(layer); expect(modify.hitDetection_).to.be(layer);
});
it('accepts a layer in addition to a features collection', function () {
const feature = new Feature(new Point([0, 0]));
const source = new VectorSource({features: [feature]});
const layer = new VectorLayer({source: source});
const features = new Collection([new Feature(new Point([1, 1]))]);
const modify = new Modify({layer: layer, features: features});
const rbushEntries = modify.rBush_.getAll();
expect(rbushEntries.length).to.be(1);
expect(rbushEntries[0].feature).to.be(features.item(0));
expect(modify.layer_).to.be(layer);
});
it('accepts a layer in addition to a features collection', function () {
const feature = new Feature(new Point([0, 0]));
const source = new VectorSource({features: [feature]});
const layer = new VectorLayer({source: source});
const features = new Collection([new Feature(new Point([1, 1]))]);
const modify = new Modify({layer: layer, features: features});
const rbushEntries = modify.rBush_.getAll();
expect(rbushEntries.length).to.be(1);
expect(rbushEntries[0].feature).to.be(features.item(0));
expect(modify.layer_).to.be(layer);
});
it('accepts a layer in addition to a source', function () {
const feature = new Feature(new Point([0, 0]));
const source = new VectorSource({features: [feature]});
const layer = new VectorLayer({source: source});
const candidateSource = new VectorSource({
features: [new Feature(new Point([1, 1]))],
});
const modify = new Modify({layer: layer, source: candidateSource});
const rbushEntries = modify.rBush_.getAll();
expect(rbushEntries.length).to.be(1);
expect(rbushEntries[0].feature).to.be(candidateSource.getFeatures()[0]);
expect(modify.layer_).to.be(layer);
}); });
}); });
@@ -1048,6 +1012,33 @@ describe('ol.interaction.Modify', function () {
feature.getGeometry().getGeometriesArray()[1] feature.getGeometry().getGeometriesArray()[1]
); );
}); });
it('works with hit detection of point features', function () {
const modify = new Modify({
hitDetection: layer,
source: source,
});
map.addInteraction(modify);
source.clear();
const pointFeature = new Feature(new Point([0, 0]));
source.addFeature(pointFeature);
layer.setStyle(
new Style({
image: new CircleStyle({
radius: 30,
fill: new Fill({
color: 'fuchsia',
}),
}),
})
);
map.renderSync();
simulateEvent('pointermove', 10, -10, null, 0);
expect(modify.vertexFeature_.get('features')[0]).to.eql(pointFeature);
expect(modify.vertexFeature_.get('geometries')[0]).to.eql(
pointFeature.getGeometry()
);
});
}); });
describe('#getOverlay', function () { describe('#getOverlay', function () {