Simplify API and code

This commit is contained in:
Andreas Hocevar
2020-11-28 11:03:56 +01:00
parent db6eb040d2
commit 9b31deb38f
2 changed files with 99 additions and 61 deletions
+50 -61
View File
@@ -120,8 +120,10 @@ const ModifyEventType = {
* 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 layer or feature collection
* must be provided with the `layer` or `features` option. * must be provided with the `layer` or `features` option.
* @property {import("../layer/BaseVector").default} [layer] The layer with * @property {import("../layer/BaseVector").default} [layer] The layer with
* features to modify. If a layer is not provided, a vector source or feature collection * features to modify. When provided, point features will considered for modification based on
* must be provided with the `source` or `features` option. * their visual appearance on this layer, instead of being within the `pixelTolerance` from the
* pointer location. When no `source` or `features` are configured, this layer's source will
* 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 layer or vector source must be provided with the `layer` or `source` option.
@@ -170,15 +172,12 @@ export class ModifyEvent extends Event {
* 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`, `features` or `layer` option.
* *
* When configured with a `source` or `features`, Cartesian distance from the * Cartesian distance from the pointer is used to determine the features that
* pointer is used to determine all features that will be modified. This is the * will be modified. This means that geometries will only be considered for
* preferred mode for modifying polygons or linestrings with shared edges or * modification when they are within the configured `pixelTolerane`. For point
* vertices that have to be modified together to maintain topology. * geometries, hit detection can be used to match their visual appearance. To
* * enable hit detection, the interaction has to be configured with the `layer`
* When configured with a `layer`, pointer hit detection is used to determine the * that contains the points.
* topmost feature that will be modified. This is the preferred mode for modifying
* points when the visual representation is much different from
* their geometry (e.g. large icons or icons with an offset).
* *
* 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
@@ -339,21 +338,28 @@ class Modify extends PointerInteraction {
this.layer_ = null; this.layer_ = null;
let features; let features;
if (options.source) { if (options.features) {
this.source_ = options.source;
features = new Collection(this.source_.getFeatures());
this.source_.addEventListener(
VectorEventType.ADDFEATURE,
this.handleSourceAdd_.bind(this)
);
this.source_.addEventListener(
VectorEventType.REMOVEFEATURE,
this.handleSourceRemove_.bind(this)
);
} else if (options.features) {
features = options.features; features = options.features;
} else if (options.layer) { } else {
features = new Collection(); const source = options.source
? options.source
: options.layer
? options.layer.getSource()
: undefined;
if (source) {
this.source_ = source;
features = new Collection(this.source_.getFeatures());
this.source_.addEventListener(
VectorEventType.ADDFEATURE,
this.handleSourceAdd_.bind(this)
);
this.source_.addEventListener(
VectorEventType.REMOVEFEATURE,
this.handleSourceRemove_.bind(this)
);
}
}
if (options.layer) {
this.layer_ = options.layer; this.layer_ = options.layer;
} }
if (!features) { if (!features) {
@@ -1116,68 +1122,51 @@ class Modify extends PointerInteraction {
); );
}; };
const rBush = this.rBush_; let box, hitPointGeometry;
/** @type {import("../geom/SimpleGeometry").default} */
let geometry;
let point = false;
let nodes;
if (this.layer_) { if (this.layer_) {
const feature = map.forEachFeatureAtPixel( map.forEachFeatureAtPixel(
pixel, pixel,
(feature, layer, geom) => { (feature, layer, geometry) => {
geometry = geom || feature.getGeometry(); geometry = geometry || feature.getGeometry();
return feature; if (geometry.getType() === GeometryType.POINT) {
hitPointGeometry = geometry;
box = hitPointGeometry.getExtent();
}
return true;
}, },
{ {
layerFilter: (layer) => layer === this.layer_, layerFilter: (layer) => layer === this.layer_,
hitTolerance: this.pixelTolerance_,
} }
); );
if (feature && feature !== this.features_.item(0)) { }
this.features_.setAt(0, feature); if (!box) {
}
if (!feature) {
this.features_.clear();
}
if (geometry) {
nodes = rBush.getInExtent(geometry.getExtent());
const type = geometry.getType();
if (type === GeometryType.POINT || type === GeometryType.MULTI_POINT) {
point = true;
}
}
} else {
const viewExtent = fromUserExtent( const viewExtent = fromUserExtent(
createExtent(pixelCoordinate, tempExtent), createExtent(pixelCoordinate, tempExtent),
projection projection
); );
const buffer = map.getView().getResolution() * this.pixelTolerance_; const buffer = map.getView().getResolution() * this.pixelTolerance_;
const box = toUserExtent( box = toUserExtent(
bufferExtent(viewExtent, buffer, tempExtent), bufferExtent(viewExtent, buffer, tempExtent),
projection projection
); );
nodes = rBush.getInExtent(box);
} }
const nodes = this.rBush_.getInExtent(box);
if (nodes && nodes.length > 0) { if (nodes && nodes.length > 0) {
nodes.sort(sortByDistance); const node = nodes.sort(sortByDistance)[0];
const node = nodes[0];
if (!geometry) {
geometry = node.geometry;
}
const closestSegment = node.segment; const closestSegment = node.segment;
let vertex = closestOnSegmentData(pixelCoordinate, node, projection); let vertex = closestOnSegmentData(pixelCoordinate, node, projection);
const vertexPixel = map.getPixelFromCoordinate(vertex); const vertexPixel = map.getPixelFromCoordinate(vertex);
let dist = coordinateDistance(pixel, vertexPixel); let dist = coordinateDistance(pixel, vertexPixel);
if (point || dist <= this.pixelTolerance_) { if (hitPointGeometry || dist <= this.pixelTolerance_) {
/** @type {Object<string, boolean>} */ /** @type {Object<string, boolean>} */
const vertexSegments = {}; const vertexSegments = {};
vertexSegments[getUid(closestSegment)] = true; vertexSegments[getUid(closestSegment)] = true;
this.delta_[0] = point ? vertex[0] - pixelCoordinate[0] : 0; this.delta_[0] = hitPointGeometry ? vertex[0] - pixelCoordinate[0] : 0;
this.delta_[1] = point ? vertex[1] - pixelCoordinate[1] : 0; this.delta_[1] = hitPointGeometry ? vertex[1] - pixelCoordinate[1] : 0;
if ( if (
geometry.getType() === GeometryType.CIRCLE && node.geometry.getType() === GeometryType.CIRCLE &&
node.index === CIRCLE_CIRCUMFERENCE_INDEX node.index === CIRCLE_CIRCUMFERENCE_INDEX
) { ) {
this.snappedToVertex_ = true; this.snappedToVertex_ = true;
@@ -1205,7 +1194,7 @@ class Modify extends PointerInteraction {
[node.geometry] [node.geometry]
); );
const geometries = {}; const geometries = {};
geometries[getUid(geometry)] = true; geometries[getUid(node.geometry)] = true;
for (let i = 1, ii = nodes.length; i < ii; ++i) { for (let i = 1, ii = nodes.length; i < ii; ++i) {
const segment = nodes[i].segment; const segment = nodes[i].segment;
if ( if (
+49
View File
@@ -195,6 +195,55 @@ describe('ol.interaction.Modify', function () {
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);
}); });
it('accepts a layer for modification features', function () {
const feature = new Feature(new Point([0, 0]));
const source = new VectorSource({features: [feature]});
const layer = new VectorLayer({source: source});
const modify = new Modify({layer: layer});
const rbushEntries = modify.rBush_.getAll();
expect(rbushEntries.length).to.be(1);
expect(rbushEntries[0].feature).to.be(feature);
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 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);
});
}); });
describe('vertex deletion', function () { describe('vertex deletion', function () {