diff --git a/src/ol/interaction/DragRotateAndZoom.js b/src/ol/interaction/DragRotateAndZoom.js index c23ce41a74..7463ce9f1b 100644 --- a/src/ol/interaction/DragRotateAndZoom.js +++ b/src/ol/interaction/DragRotateAndZoom.js @@ -38,7 +38,7 @@ class DragRotateAndZoom extends PointerInteraction { const options = opt_options ? opt_options : {}; - super(options); + super(/** @type {import("./Pointer.js").Options} */ (options)); /** * @private diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 5337d2906c..3cb00728e2 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -21,7 +21,7 @@ import MultiPolygon from '../geom/MultiPolygon.js'; import {POINTER_TYPE} from '../pointer/MouseSource.js'; import Point from '../geom/Point.js'; import Polygon, {fromCircle, makeRegular} from '../geom/Polygon.js'; -import PointerInteraction, {handleEvent as handlePointerEvent} from '../interaction/Pointer.js'; +import PointerInteraction from '../interaction/Pointer.js'; import InteractionProperty from '../interaction/Property.js'; import VectorLayer from '../layer/Vector.js'; import VectorSource from '../source/Vector.js'; @@ -186,12 +186,12 @@ class Draw extends PointerInteraction { */ constructor(options) { - super({ - handleDownEvent: handleDownEvent, - handleEvent: handleEvent, - handleUpEvent: handleUpEvent, - stopDown: FALSE - }); + const pointerOptions = /** @type {import("./Pointer.js").Options} */ (options); + if (!pointerOptions.stopDown) { + pointerOptions.stopDown = FALSE; + } + + super(pointerOptions); /** * @type {boolean} @@ -472,6 +472,123 @@ class Draw extends PointerInteraction { return this.overlay_; } + /** + * Handles the {@link module:ol/MapBrowserEvent map browser event} and may actually draw or finish the drawing. + * @override + * @api + */ + handleEvent(event) { + if (event.originalEvent.type === EventType.CONTEXTMENU) { + // Avoid context menu for long taps when drawing on mobile + event.preventDefault(); + } + this.freehand_ = this.mode_ !== Mode.POINT && this.freehandCondition_(event); + let move = event.type === MapBrowserEventType.POINTERMOVE; + let pass = true; + if (!this.freehand_ && this.lastDragTime_ && event.type === MapBrowserEventType.POINTERDRAG) { + const now = Date.now(); + if (now - this.lastDragTime_ >= this.dragVertexDelay_) { + this.downPx_ = event.pixel; + this.shouldHandle_ = !this.freehand_; + move = true; + } else { + this.lastDragTime_ = undefined; + } + if (this.shouldHandle_ && this.downTimeout_ !== undefined) { + clearTimeout(this.downTimeout_); + this.downTimeout_ = undefined; + } + } + if (this.freehand_ && + event.type === MapBrowserEventType.POINTERDRAG && + this.sketchFeature_ !== null) { + this.addToDrawing_(event); + pass = false; + } else if (this.freehand_ && + event.type === MapBrowserEventType.POINTERDOWN) { + pass = false; + } else if (move) { + pass = event.type === MapBrowserEventType.POINTERMOVE; + if (pass && this.freehand_) { + pass = this.handlePointerMove_(event); + } else if (/** @type {MapBrowserPointerEvent} */ (event).pointerEvent.pointerType == POINTER_TYPE || + (event.type === MapBrowserEventType.POINTERDRAG && this.downTimeout_ === undefined)) { + this.handlePointerMove_(event); + } + } else if (event.type === MapBrowserEventType.DBLCLICK) { + pass = false; + } + + return super.handleEvent(event) && pass; + } + + /** + * @inheritDoc + */ + handleDownEvent(event) { + this.shouldHandle_ = !this.freehand_; + + if (this.freehand_) { + this.downPx_ = event.pixel; + if (!this.finishCoordinate_) { + this.startDrawing_(event); + } + return true; + } else if (this.condition_(event)) { + this.lastDragTime_ = Date.now(); + this.downTimeout_ = setTimeout(function() { + this.handlePointerMove_(new MapBrowserPointerEvent( + MapBrowserEventType.POINTERMOVE, event.map, event.pointerEvent, false, event.frameState)); + }.bind(this), this.dragVertexDelay_); + this.downPx_ = event.pixel; + return true; + } else { + return false; + } + } + + + /** + * @inheritDoc + */ + handleUpEvent(event) { + let pass = true; + + if (this.downTimeout_) { + clearTimeout(this.downTimeout_); + this.downTimeout_ = undefined; + } + + this.handlePointerMove_(event); + + const circleMode = this.mode_ === Mode.CIRCLE; + + if (this.shouldHandle_) { + if (!this.finishCoordinate_) { + this.startDrawing_(event); + if (this.mode_ === Mode.POINT) { + this.finishDrawing(); + } + } else if (this.freehand_ || circleMode) { + this.finishDrawing(); + } else if (this.atFinish_(event)) { + if (this.finishCondition_(event)) { + this.finishDrawing(); + } + } else { + this.addToDrawing_(event); + } + pass = false; + } else if (this.freehand_) { + this.finishCoordinate_ = null; + this.abortDrawing_(); + } + if (!pass && this.stopClick_) { + event.stopPropagation(); + } + return pass; + } + /** * Handle move events. * @param {import("../MapBrowserEvent.js").default} event A move event. @@ -842,132 +959,6 @@ function getDefaultStyleFunction() { } -/** - * Handles the {@link module:ol/MapBrowserEvent map browser event} and may actually - * draw or finish the drawing. - * @param {import("../MapBrowserEvent.js").default} event Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {Draw} - * @api - */ -export function handleEvent(event) { - if (event.originalEvent.type === EventType.CONTEXTMENU) { - // Avoid context menu for long taps when drawing on mobile - event.preventDefault(); - } - this.freehand_ = this.mode_ !== Mode.POINT && this.freehandCondition_(event); - let move = event.type === MapBrowserEventType.POINTERMOVE; - let pass = true; - if (!this.freehand_ && this.lastDragTime_ && event.type === MapBrowserEventType.POINTERDRAG) { - const now = Date.now(); - if (now - this.lastDragTime_ >= this.dragVertexDelay_) { - this.downPx_ = event.pixel; - this.shouldHandle_ = !this.freehand_; - move = true; - } else { - this.lastDragTime_ = undefined; - } - if (this.shouldHandle_ && this.downTimeout_ !== undefined) { - clearTimeout(this.downTimeout_); - this.downTimeout_ = undefined; - } - } - if (this.freehand_ && - event.type === MapBrowserEventType.POINTERDRAG && - this.sketchFeature_ !== null) { - this.addToDrawing_(event); - pass = false; - } else if (this.freehand_ && - event.type === MapBrowserEventType.POINTERDOWN) { - pass = false; - } else if (move) { - pass = event.type === MapBrowserEventType.POINTERMOVE; - if (pass && this.freehand_) { - pass = this.handlePointerMove_(event); - } else if (/** @type {MapBrowserPointerEvent} */ (event).pointerEvent.pointerType == POINTER_TYPE || - (event.type === MapBrowserEventType.POINTERDRAG && this.downTimeout_ === undefined)) { - this.handlePointerMove_(event); - } - } else if (event.type === MapBrowserEventType.DBLCLICK) { - pass = false; - } - - return handlePointerEvent.call(this, event) && pass; -} - - -/** - * @param {MapBrowserPointerEvent} event Event. - * @return {boolean} Start drag sequence? - * @this {Draw} - */ -function handleDownEvent(event) { - this.shouldHandle_ = !this.freehand_; - - if (this.freehand_) { - this.downPx_ = event.pixel; - if (!this.finishCoordinate_) { - this.startDrawing_(event); - } - return true; - } else if (this.condition_(event)) { - this.lastDragTime_ = Date.now(); - this.downTimeout_ = setTimeout(function() { - this.handlePointerMove_(new MapBrowserPointerEvent( - MapBrowserEventType.POINTERMOVE, event.map, event.pointerEvent, false, event.frameState)); - }.bind(this), this.dragVertexDelay_); - this.downPx_ = event.pixel; - return true; - } else { - return false; - } -} - - -/** - * @param {MapBrowserPointerEvent} event Event. - * @return {boolean} Stop drag sequence? - * @this {Draw} - */ -function handleUpEvent(event) { - let pass = true; - - if (this.downTimeout_) { - clearTimeout(this.downTimeout_); - this.downTimeout_ = undefined; - } - - this.handlePointerMove_(event); - - const circleMode = this.mode_ === Mode.CIRCLE; - - if (this.shouldHandle_) { - if (!this.finishCoordinate_) { - this.startDrawing_(event); - if (this.mode_ === Mode.POINT) { - this.finishDrawing(); - } - } else if (this.freehand_ || circleMode) { - this.finishDrawing(); - } else if (this.atFinish_(event)) { - if (this.finishCondition_(event)) { - this.finishDrawing(); - } - } else { - this.addToDrawing_(event); - } - pass = false; - } else if (this.freehand_) { - this.finishCoordinate_ = null; - this.abortDrawing_(); - } - if (!pass && this.stopClick_) { - event.stopPropagation(); - } - return pass; -} - - /** * Create a `geometryFunction` for `type: 'Circle'` that will create a regular * polygon with a user specified number of sides and start angle instead of an diff --git a/src/ol/interaction/Extent.js b/src/ol/interaction/Extent.js index 51b8f06cce..c388b65b8f 100644 --- a/src/ol/interaction/Extent.js +++ b/src/ol/interaction/Extent.js @@ -10,7 +10,7 @@ import {boundingExtent, getArea} from '../extent.js'; import GeometryType from '../geom/GeometryType.js'; import Point from '../geom/Point.js'; import {fromExtent as polygonFromExtent} from '../geom/Polygon.js'; -import PointerInteraction, {handleEvent as handlePointerEvent} from '../interaction/Pointer.js'; +import PointerInteraction from '../interaction/Pointer.js'; import VectorLayer from '../layer/Vector.js'; import VectorSource from '../source/Vector.js'; import {createEditingStyle} from '../style/Style.js'; @@ -85,15 +85,10 @@ class ExtentInteraction extends PointerInteraction { */ constructor(opt_options) { - super({ - handleDownEvent: handleDownEvent, - handleDragEvent: handleDragEvent, - handleEvent: handleEvent, - handleUpEvent: handleUpEvent - }); - const options = opt_options || {}; + super(/** @type {import("./Pointer.js").Options} */ (options)); + /** * Extent of the drawn box * @type {import("../extent.js").Extent} @@ -277,6 +272,105 @@ class ExtentInteraction extends PointerInteraction { return vertexFeature; } + /** + * @inheritDoc + */ + handleEvent(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { + return true; + } + //display pointer (if not dragging) + if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE && !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + //call pointer to determine up/down/drag + super.handleEvent(mapBrowserEvent); + //return false to stop propagation + return false; + } + + /** + * @inheritDoc + */ + handleDownEvent(mapBrowserEvent) { + const pixel = mapBrowserEvent.pixel; + const map = mapBrowserEvent.map; + + const extent = this.getExtent(); + let vertex = this.snapToVertex_(pixel, map); + + //find the extent corner opposite the passed corner + const getOpposingPoint = function(point) { + let x_ = null; + let y_ = null; + if (point[0] == extent[0]) { + x_ = extent[2]; + } else if (point[0] == extent[2]) { + x_ = extent[0]; + } + if (point[1] == extent[1]) { + y_ = extent[3]; + } else if (point[1] == extent[3]) { + y_ = extent[1]; + } + if (x_ !== null && y_ !== null) { + return [x_, y_]; + } + return null; + }; + if (vertex && extent) { + const x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null; + const y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null; + + //snap to point + if (x !== null && y !== null) { + this.pointerHandler_ = getPointHandler(getOpposingPoint(vertex)); + //snap to edge + } else if (x !== null) { + this.pointerHandler_ = getEdgeHandler( + getOpposingPoint([x, extent[1]]), + getOpposingPoint([x, extent[3]]) + ); + } else if (y !== null) { + this.pointerHandler_ = getEdgeHandler( + getOpposingPoint([extent[0], y]), + getOpposingPoint([extent[2], y]) + ); + } + //no snap - new bbox + } else { + vertex = map.getCoordinateFromPixel(pixel); + this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]); + this.pointerHandler_ = getPointHandler(vertex); + } + return true; //event handled; start downup sequence + } + + /** + * @inheritDoc + */ + handleDragEvent(mapBrowserEvent) { + if (this.pointerHandler_) { + const pixelCoordinate = mapBrowserEvent.coordinate; + this.setExtent(this.pointerHandler_(pixelCoordinate)); + this.createOrUpdatePointerFeature_(pixelCoordinate); + } + return true; + } + + /** + * @inheritDoc + */ + handleUpEvent(mapBrowserEvent) { + this.pointerHandler_ = null; + //If bbox is zero area, set to null; + const extent = this.getExtent(); + if (!extent || getArea(extent) === 0) { + this.setExtent(null); + } + return false; //Stop handling downup sequence + } + /** * @inheritDoc */ @@ -310,113 +404,6 @@ class ExtentInteraction extends PointerInteraction { } } -/** - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Propagate event? - * @this {ExtentInteraction} - */ -function handleEvent(mapBrowserEvent) { - if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { - return true; - } - //display pointer (if not dragging) - if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE && !this.handlingDownUpSequence) { - this.handlePointerMove_(mapBrowserEvent); - } - //call pointer to determine up/down/drag - handlePointerEvent.call(this, mapBrowserEvent); - //return false to stop propagation - return false; -} - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Event handled? - * @this {ExtentInteraction} - */ -function handleDownEvent(mapBrowserEvent) { - const pixel = mapBrowserEvent.pixel; - const map = mapBrowserEvent.map; - - const extent = this.getExtent(); - let vertex = this.snapToVertex_(pixel, map); - - //find the extent corner opposite the passed corner - const getOpposingPoint = function(point) { - let x_ = null; - let y_ = null; - if (point[0] == extent[0]) { - x_ = extent[2]; - } else if (point[0] == extent[2]) { - x_ = extent[0]; - } - if (point[1] == extent[1]) { - y_ = extent[3]; - } else if (point[1] == extent[3]) { - y_ = extent[1]; - } - if (x_ !== null && y_ !== null) { - return [x_, y_]; - } - return null; - }; - if (vertex && extent) { - const x = (vertex[0] == extent[0] || vertex[0] == extent[2]) ? vertex[0] : null; - const y = (vertex[1] == extent[1] || vertex[1] == extent[3]) ? vertex[1] : null; - - //snap to point - if (x !== null && y !== null) { - this.pointerHandler_ = getPointHandler(getOpposingPoint(vertex)); - //snap to edge - } else if (x !== null) { - this.pointerHandler_ = getEdgeHandler( - getOpposingPoint([x, extent[1]]), - getOpposingPoint([x, extent[3]]) - ); - } else if (y !== null) { - this.pointerHandler_ = getEdgeHandler( - getOpposingPoint([extent[0], y]), - getOpposingPoint([extent[2], y]) - ); - } - //no snap - new bbox - } else { - vertex = map.getCoordinateFromPixel(pixel); - this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]); - this.pointerHandler_ = getPointHandler(vertex); - } - return true; //event handled; start downup sequence -} - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Event handled? - * @this {ExtentInteraction} - */ -function handleDragEvent(mapBrowserEvent) { - if (this.pointerHandler_) { - const pixelCoordinate = mapBrowserEvent.coordinate; - this.setExtent(this.pointerHandler_(pixelCoordinate)); - this.createOrUpdatePointerFeature_(pixelCoordinate); - } - return true; -} - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {ExtentInteraction} - */ -function handleUpEvent(mapBrowserEvent) { - this.pointerHandler_ = null; - //If bbox is zero area, set to null; - const extent = this.getExtent(); - if (!extent || getArea(extent) === 0) { - this.setExtent(null); - } - return false; //Stop handling downup sequence -} - /** * Returns the default style for the drawn bbox * diff --git a/src/ol/interaction/Interaction.js b/src/ol/interaction/Interaction.js index 33b1798662..eeaba15d55 100644 --- a/src/ol/interaction/Interaction.js +++ b/src/ol/interaction/Interaction.js @@ -38,6 +38,10 @@ class Interaction extends BaseObject { constructor(options) { super(); + if (options.handleEvent) { + this.handleEvent = options.handleEvent; + } + /** * @private * @type {import("../PluggableMap.js").default} @@ -45,12 +49,6 @@ class Interaction extends BaseObject { this.map_ = null; this.setActive(true); - - /** - * @type {function(import("../MapBrowserEvent.js").default):boolean} - */ - this.handleEvent = options.handleEvent; - } /** @@ -72,6 +70,16 @@ class Interaction extends BaseObject { return this.map_; } + /** + * Handles the {@link module:ol/MapBrowserEvent map browser event}. + * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. + * @return {boolean} `false` to stop event propagation. + * @api + */ + handleEvent(mapBrowserEvent) { + return true; + } + /** * Activate or deactivate the interaction. * @param {boolean} active Active. diff --git a/src/ol/interaction/Modify.js b/src/ol/interaction/Modify.js index 84fc228786..11842ec099 100644 --- a/src/ol/interaction/Modify.js +++ b/src/ol/interaction/Modify.js @@ -16,7 +16,7 @@ import {always, primaryAction, altKeyOnly, singleClick} from '../events/conditio import {boundingExtent, buffer, createOrUpdateFromCoordinate} from '../extent.js'; import GeometryType from '../geom/GeometryType.js'; import Point from '../geom/Point.js'; -import PointerInteraction, {handleEvent as handlePointerEvent} from '../interaction/Pointer.js'; +import PointerInteraction from '../interaction/Pointer.js'; import VectorLayer from '../layer/Vector.js'; import VectorSource from '../source/Vector.js'; import VectorEventType from '../source/VectorEventType.js'; @@ -157,12 +157,7 @@ class Modify extends PointerInteraction { */ constructor(options) { - super({ - handleDownEvent: handleDownEvent, - handleDragEvent: handleDragEvent, - handleEvent: handleEvent, - handleUpEvent: handleUpEvent - }); + super(/** @type {import("./Pointer.js").Options} */ (options)); /** * @private @@ -170,7 +165,6 @@ class Modify extends PointerInteraction { */ this.condition_ = options.condition ? options.condition : primaryAction; - /** * @private * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Browser event. @@ -667,6 +661,211 @@ class Modify extends PointerInteraction { return vertexFeature; } + /** + * Handles the {@link module:ol/MapBrowserEvent map browser event} and may modify the geometry. + * @override + */ + handleEvent(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { + return true; + } + this.lastPointerEvent_ = mapBrowserEvent; + + let handled; + if (!mapBrowserEvent.map.getView().getInteracting() && + mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE && + !this.handlingDownUpSequence) { + this.handlePointerMove_(mapBrowserEvent); + } + if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) { + if (mapBrowserEvent.type != MapBrowserEventType.SINGLECLICK || !this.ignoreNextSingleClick_) { + handled = this.removePoint(); + } else { + handled = true; + } + } + + if (mapBrowserEvent.type == MapBrowserEventType.SINGLECLICK) { + this.ignoreNextSingleClick_ = false; + } + + return super.handleEvent(mapBrowserEvent) && !handled; + } + + /** + * @inheritDoc + */ + handleDragEvent(evt) { + this.ignoreNextSingleClick_ = false; + this.willModifyFeatures_(evt); + + const vertex = evt.coordinate; + for (let i = 0, ii = this.dragSegments_.length; i < ii; ++i) { + const dragSegment = this.dragSegments_[i]; + const segmentData = dragSegment[0]; + const depth = segmentData.depth; + const geometry = segmentData.geometry; + let coordinates; + const segment = segmentData.segment; + const index = dragSegment[1]; + + while (vertex.length < geometry.getStride()) { + vertex.push(segment[index][vertex.length]); + } + + switch (geometry.getType()) { + case GeometryType.POINT: + coordinates = vertex; + segment[0] = segment[1] = vertex; + break; + case GeometryType.MULTI_POINT: + coordinates = geometry.getCoordinates(); + coordinates[segmentData.index] = vertex; + segment[0] = segment[1] = vertex; + break; + case GeometryType.LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates[segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case GeometryType.MULTI_LINE_STRING: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case GeometryType.POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case GeometryType.MULTI_POLYGON: + coordinates = geometry.getCoordinates(); + coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; + segment[index] = vertex; + break; + case GeometryType.CIRCLE: + segment[0] = segment[1] = vertex; + if (segmentData.index === CIRCLE_CENTER_INDEX) { + this.changingFeature_ = true; + geometry.setCenter(vertex); + this.changingFeature_ = false; + } else { // We're dragging the circle's circumference: + this.changingFeature_ = true; + geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex)); + this.changingFeature_ = false; + } + break; + default: + // pass + } + + if (coordinates) { + this.setGeometryCoordinates_(geometry, coordinates); + } + } + this.createOrUpdateVertexFeature_(vertex); + } + + /** + * @inheritDoc + */ + handleDownEvent(evt) { + if (!this.condition_(evt)) { + return false; + } + this.handlePointerAtPixel_(evt.pixel, evt.map); + const pixelCoordinate = evt.map.getCoordinateFromPixel(evt.pixel); + this.dragSegments_.length = 0; + this.modified_ = false; + const vertexFeature = this.vertexFeature_; + if (vertexFeature) { + const insertVertices = []; + const geometry = /** @type {Point} */ (vertexFeature.getGeometry()); + const vertex = geometry.getCoordinates(); + const vertexExtent = boundingExtent([vertex]); + const segmentDataMatches = this.rBush_.getInExtent(vertexExtent); + const componentSegments = {}; + segmentDataMatches.sort(compareIndexes); + for (let i = 0, ii = segmentDataMatches.length; i < ii; ++i) { + const segmentDataMatch = segmentDataMatches[i]; + const segment = segmentDataMatch.segment; + let uid = String(getUid(segmentDataMatch.feature)); + const depth = segmentDataMatch.depth; + if (depth) { + uid += '-' + depth.join('-'); // separate feature components + } + if (!componentSegments[uid]) { + componentSegments[uid] = new Array(2); + } + if (segmentDataMatch.geometry.getType() === GeometryType.CIRCLE && + segmentDataMatch.index === CIRCLE_CIRCUMFERENCE_INDEX) { + + const closestVertex = closestOnSegmentData(pixelCoordinate, segmentDataMatch); + if (coordinatesEqual(closestVertex, vertex) && !componentSegments[uid][0]) { + this.dragSegments_.push([segmentDataMatch, 0]); + componentSegments[uid][0] = segmentDataMatch; + } + } else if (coordinatesEqual(segment[0], vertex) && + !componentSegments[uid][0]) { + this.dragSegments_.push([segmentDataMatch, 0]); + componentSegments[uid][0] = segmentDataMatch; + } else if (coordinatesEqual(segment[1], vertex) && + !componentSegments[uid][1]) { + + // prevent dragging closed linestrings by the connecting node + if ((segmentDataMatch.geometry.getType() === + GeometryType.LINE_STRING || + segmentDataMatch.geometry.getType() === + GeometryType.MULTI_LINE_STRING) && + componentSegments[uid][0] && + componentSegments[uid][0].index === 0) { + continue; + } + + this.dragSegments_.push([segmentDataMatch, 1]); + componentSegments[uid][1] = segmentDataMatch; + } else if (this.insertVertexCondition_(evt) && getUid(segment) in this.vertexSegments_ && + (!componentSegments[uid][0] && !componentSegments[uid][1])) { + insertVertices.push([segmentDataMatch, vertex]); + } + } + if (insertVertices.length) { + this.willModifyFeatures_(evt); + } + for (let j = insertVertices.length - 1; j >= 0; --j) { + this.insertVertex_.apply(this, insertVertices[j]); + } + } + return !!this.vertexFeature_; + } + + /** + * @inheritDoc + */ + handleUpEvent(evt) { + for (let i = this.dragSegments_.length - 1; i >= 0; --i) { + const segmentData = this.dragSegments_[i][0]; + const geometry = segmentData.geometry; + if (geometry.getType() === GeometryType.CIRCLE) { + // Update a circle object in the R* bush: + const coordinates = geometry.getCenter(); + const centerSegmentData = segmentData.featureSegments[0]; + const circumferenceSegmentData = segmentData.featureSegments[1]; + centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates; + circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates; + this.rBush_.update(createOrUpdateFromCoordinate(coordinates), centerSegmentData); + this.rBush_.update(geometry.getExtent(), circumferenceSegmentData); + } else { + this.rBush_.update(boundingExtent(segmentData.segment), segmentData); + } + } + if (this.modified_) { + this.dispatchEvent(new ModifyEvent(ModifyEventType.MODIFYEND, this.features_, evt)); + this.modified_ = false; + } + return false; + } + /** * @param {import("../MapBrowserEvent.js").default} evt Event. * @private @@ -985,223 +1184,6 @@ function compareIndexes(a, b) { } -/** - * @param {MapBrowserPointerEvent} evt Event. - * @return {boolean} Start drag sequence? - * @this {Modify} - */ -function handleDownEvent(evt) { - if (!this.condition_(evt)) { - return false; - } - this.handlePointerAtPixel_(evt.pixel, evt.map); - const pixelCoordinate = evt.map.getCoordinateFromPixel(evt.pixel); - this.dragSegments_.length = 0; - this.modified_ = false; - const vertexFeature = this.vertexFeature_; - if (vertexFeature) { - const insertVertices = []; - const geometry = /** @type {Point} */ (vertexFeature.getGeometry()); - const vertex = geometry.getCoordinates(); - const vertexExtent = boundingExtent([vertex]); - const segmentDataMatches = this.rBush_.getInExtent(vertexExtent); - const componentSegments = {}; - segmentDataMatches.sort(compareIndexes); - for (let i = 0, ii = segmentDataMatches.length; i < ii; ++i) { - const segmentDataMatch = segmentDataMatches[i]; - const segment = segmentDataMatch.segment; - let uid = String(getUid(segmentDataMatch.feature)); - const depth = segmentDataMatch.depth; - if (depth) { - uid += '-' + depth.join('-'); // separate feature components - } - if (!componentSegments[uid]) { - componentSegments[uid] = new Array(2); - } - if (segmentDataMatch.geometry.getType() === GeometryType.CIRCLE && - segmentDataMatch.index === CIRCLE_CIRCUMFERENCE_INDEX) { - - const closestVertex = closestOnSegmentData(pixelCoordinate, segmentDataMatch); - if (coordinatesEqual(closestVertex, vertex) && !componentSegments[uid][0]) { - this.dragSegments_.push([segmentDataMatch, 0]); - componentSegments[uid][0] = segmentDataMatch; - } - } else if (coordinatesEqual(segment[0], vertex) && - !componentSegments[uid][0]) { - this.dragSegments_.push([segmentDataMatch, 0]); - componentSegments[uid][0] = segmentDataMatch; - } else if (coordinatesEqual(segment[1], vertex) && - !componentSegments[uid][1]) { - - // prevent dragging closed linestrings by the connecting node - if ((segmentDataMatch.geometry.getType() === - GeometryType.LINE_STRING || - segmentDataMatch.geometry.getType() === - GeometryType.MULTI_LINE_STRING) && - componentSegments[uid][0] && - componentSegments[uid][0].index === 0) { - continue; - } - - this.dragSegments_.push([segmentDataMatch, 1]); - componentSegments[uid][1] = segmentDataMatch; - } else if (this.insertVertexCondition_(evt) && getUid(segment) in this.vertexSegments_ && - (!componentSegments[uid][0] && !componentSegments[uid][1])) { - insertVertices.push([segmentDataMatch, vertex]); - } - } - if (insertVertices.length) { - this.willModifyFeatures_(evt); - } - for (let j = insertVertices.length - 1; j >= 0; --j) { - this.insertVertex_.apply(this, insertVertices[j]); - } - } - return !!this.vertexFeature_; -} - - -/** - * @param {MapBrowserPointerEvent} evt Event. - * @this {Modify} - */ -function handleDragEvent(evt) { - this.ignoreNextSingleClick_ = false; - this.willModifyFeatures_(evt); - - const vertex = evt.coordinate; - for (let i = 0, ii = this.dragSegments_.length; i < ii; ++i) { - const dragSegment = this.dragSegments_[i]; - const segmentData = dragSegment[0]; - const depth = segmentData.depth; - const geometry = segmentData.geometry; - let coordinates; - const segment = segmentData.segment; - const index = dragSegment[1]; - - while (vertex.length < geometry.getStride()) { - vertex.push(segment[index][vertex.length]); - } - - switch (geometry.getType()) { - case GeometryType.POINT: - coordinates = vertex; - segment[0] = segment[1] = vertex; - break; - case GeometryType.MULTI_POINT: - coordinates = geometry.getCoordinates(); - coordinates[segmentData.index] = vertex; - segment[0] = segment[1] = vertex; - break; - case GeometryType.LINE_STRING: - coordinates = geometry.getCoordinates(); - coordinates[segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case GeometryType.MULTI_LINE_STRING: - coordinates = geometry.getCoordinates(); - coordinates[depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case GeometryType.POLYGON: - coordinates = geometry.getCoordinates(); - coordinates[depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case GeometryType.MULTI_POLYGON: - coordinates = geometry.getCoordinates(); - coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex; - segment[index] = vertex; - break; - case GeometryType.CIRCLE: - segment[0] = segment[1] = vertex; - if (segmentData.index === CIRCLE_CENTER_INDEX) { - this.changingFeature_ = true; - geometry.setCenter(vertex); - this.changingFeature_ = false; - } else { // We're dragging the circle's circumference: - this.changingFeature_ = true; - geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex)); - this.changingFeature_ = false; - } - break; - default: - // pass - } - - if (coordinates) { - this.setGeometryCoordinates_(geometry, coordinates); - } - } - this.createOrUpdateVertexFeature_(vertex); -} - - -/** - * @param {MapBrowserPointerEvent} evt Event. - * @return {boolean} Stop drag sequence? - * @this {Modify} - */ -function handleUpEvent(evt) { - for (let i = this.dragSegments_.length - 1; i >= 0; --i) { - const segmentData = this.dragSegments_[i][0]; - const geometry = segmentData.geometry; - if (geometry.getType() === GeometryType.CIRCLE) { - // Update a circle object in the R* bush: - const coordinates = geometry.getCenter(); - const centerSegmentData = segmentData.featureSegments[0]; - const circumferenceSegmentData = segmentData.featureSegments[1]; - centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates; - circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates; - this.rBush_.update(createOrUpdateFromCoordinate(coordinates), centerSegmentData); - this.rBush_.update(geometry.getExtent(), circumferenceSegmentData); - } else { - this.rBush_.update(boundingExtent(segmentData.segment), segmentData); - } - } - if (this.modified_) { - this.dispatchEvent(new ModifyEvent(ModifyEventType.MODIFYEND, this.features_, evt)); - this.modified_ = false; - } - return false; -} - - -/** - * Handles the {@link module:ol/MapBrowserEvent map browser event} and may modify the - * geometry. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {Modify} - */ -function handleEvent(mapBrowserEvent) { - if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { - return true; - } - this.lastPointerEvent_ = mapBrowserEvent; - - let handled; - if (!mapBrowserEvent.map.getView().getInteracting() && - mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE && - !this.handlingDownUpSequence) { - this.handlePointerMove_(mapBrowserEvent); - } - if (this.vertexFeature_ && this.deleteCondition_(mapBrowserEvent)) { - if (mapBrowserEvent.type != MapBrowserEventType.SINGLECLICK || !this.ignoreNextSingleClick_) { - handled = this.removePoint(); - } else { - handled = true; - } - } - - if (mapBrowserEvent.type == MapBrowserEventType.SINGLECLICK) { - this.ignoreNextSingleClick_ = false; - } - - return handlePointerEvent.call(this, mapBrowserEvent) && !handled; -} - - /** * Returns the distance from a point to a line segment. * diff --git a/src/ol/interaction/MouseWheelZoom.js b/src/ol/interaction/MouseWheelZoom.js index 4581c57d68..1f9580b647 100644 --- a/src/ol/interaction/MouseWheelZoom.js +++ b/src/ol/interaction/MouseWheelZoom.js @@ -54,11 +54,9 @@ class MouseWheelZoom extends Interaction { */ constructor(opt_options) { - super({ - handleEvent: handleEvent - }); + const options = opt_options ? opt_options : {}; - const options = opt_options || {}; + super(/** @type {import("./Interaction.js").InteractionOptions} */ (options)); /** * @private @@ -157,6 +155,127 @@ class MouseWheelZoom extends Interaction { view.setHint(ViewHint.INTERACTING, -1); } + /** + * Handles the {@link module:ol/MapBrowserEvent map browser event} (if it was a mousewheel-event) and eventually + * zooms the map. + * @override + */ + handleEvent(mapBrowserEvent) { + if (!this.condition_(mapBrowserEvent)) { + return true; + } + const type = mapBrowserEvent.type; + if (type !== EventType.WHEEL && type !== EventType.MOUSEWHEEL) { + return true; + } + + mapBrowserEvent.preventDefault(); + + const map = mapBrowserEvent.map; + const wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent); + + if (this.useAnchor_) { + this.lastAnchor_ = mapBrowserEvent.coordinate; + } + + // Delta normalisation inspired by + // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js + let delta; + if (mapBrowserEvent.type == EventType.WHEEL) { + delta = wheelEvent.deltaY; + if (FIREFOX && + wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { + delta /= DEVICE_PIXEL_RATIO; + } + if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) { + delta *= 40; + } + } else if (mapBrowserEvent.type == EventType.MOUSEWHEEL) { + delta = -wheelEvent.wheelDeltaY; + if (SAFARI) { + delta /= 3; + } + } + + if (delta === 0) { + return false; + } + + const now = Date.now(); + + if (this.startTime_ === undefined) { + this.startTime_ = now; + } + + if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) { + this.mode_ = Math.abs(delta) < 4 ? + Mode.TRACKPAD : + Mode.WHEEL; + } + + if (this.mode_ === Mode.TRACKPAD) { + const view = map.getView(); + if (this.trackpadTimeoutId_) { + clearTimeout(this.trackpadTimeoutId_); + } else { + view.setHint(ViewHint.INTERACTING, 1); + } + this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_); + let resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_); + const minResolution = view.getMinResolution(); + const maxResolution = view.getMaxResolution(); + let rebound = 0; + if (resolution < minResolution) { + resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_); + rebound = 1; + } else if (resolution > maxResolution) { + resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_); + rebound = -1; + } + if (this.lastAnchor_) { + const center = view.calculateCenterZoom(resolution, this.lastAnchor_); + view.setCenter(view.constrainCenter(center)); + } + view.setResolution(resolution); + + if (rebound === 0 && this.constrainResolution_) { + view.animate({ + resolution: view.constrainResolution(resolution, delta > 0 ? -1 : 1), + easing: easeOut, + anchor: this.lastAnchor_, + duration: this.duration_ + }); + } + + if (rebound > 0) { + view.animate({ + resolution: minResolution, + easing: easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } else if (rebound < 0) { + view.animate({ + resolution: maxResolution, + easing: easeOut, + anchor: this.lastAnchor_, + duration: 500 + }); + } + this.startTime_ = now; + return false; + } + + this.delta_ += delta; + + const timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0); + + clearTimeout(this.timeoutId_); + this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft); + + return false; + } + /** * @private * @param {import("../PluggableMap.js").default} map Map. @@ -190,129 +309,4 @@ class MouseWheelZoom extends Interaction { } } - -/** - * Handles the {@link module:ol/MapBrowserEvent map browser event} (if it was a - * mousewheel-event) and eventually zooms the map. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} Allow event propagation. - * @this {MouseWheelZoom} - */ -function handleEvent(mapBrowserEvent) { - if (!this.condition_(mapBrowserEvent)) { - return true; - } - const type = mapBrowserEvent.type; - if (type !== EventType.WHEEL && type !== EventType.MOUSEWHEEL) { - return true; - } - - mapBrowserEvent.preventDefault(); - - const map = mapBrowserEvent.map; - const wheelEvent = /** @type {WheelEvent} */ (mapBrowserEvent.originalEvent); - - if (this.useAnchor_) { - this.lastAnchor_ = mapBrowserEvent.coordinate; - } - - // Delta normalisation inspired by - // https://github.com/mapbox/mapbox-gl-js/blob/001c7b9/js/ui/handler/scroll_zoom.js - let delta; - if (mapBrowserEvent.type == EventType.WHEEL) { - delta = wheelEvent.deltaY; - if (FIREFOX && - wheelEvent.deltaMode === WheelEvent.DOM_DELTA_PIXEL) { - delta /= DEVICE_PIXEL_RATIO; - } - if (wheelEvent.deltaMode === WheelEvent.DOM_DELTA_LINE) { - delta *= 40; - } - } else if (mapBrowserEvent.type == EventType.MOUSEWHEEL) { - delta = -wheelEvent.wheelDeltaY; - if (SAFARI) { - delta /= 3; - } - } - - if (delta === 0) { - return false; - } - - const now = Date.now(); - - if (this.startTime_ === undefined) { - this.startTime_ = now; - } - - if (!this.mode_ || now - this.startTime_ > this.trackpadEventGap_) { - this.mode_ = Math.abs(delta) < 4 ? - Mode.TRACKPAD : - Mode.WHEEL; - } - - if (this.mode_ === Mode.TRACKPAD) { - const view = map.getView(); - if (this.trackpadTimeoutId_) { - clearTimeout(this.trackpadTimeoutId_); - } else { - view.setHint(ViewHint.INTERACTING, 1); - } - this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_); - let resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_); - const minResolution = view.getMinResolution(); - const maxResolution = view.getMaxResolution(); - let rebound = 0; - if (resolution < minResolution) { - resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_); - rebound = 1; - } else if (resolution > maxResolution) { - resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_); - rebound = -1; - } - if (this.lastAnchor_) { - const center = view.calculateCenterZoom(resolution, this.lastAnchor_); - view.setCenter(view.constrainCenter(center)); - } - view.setResolution(resolution); - - if (rebound === 0 && this.constrainResolution_) { - view.animate({ - resolution: view.constrainResolution(resolution, delta > 0 ? -1 : 1), - easing: easeOut, - anchor: this.lastAnchor_, - duration: this.duration_ - }); - } - - if (rebound > 0) { - view.animate({ - resolution: minResolution, - easing: easeOut, - anchor: this.lastAnchor_, - duration: 500 - }); - } else if (rebound < 0) { - view.animate({ - resolution: maxResolution, - easing: easeOut, - anchor: this.lastAnchor_, - duration: 500 - }); - } - this.startTime_ = now; - return false; - } - - this.delta_ += delta; - - const timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0); - - clearTimeout(this.timeoutId_); - this.timeoutId_ = setTimeout(this.handleWheelZoom_.bind(this, map), timeLeft); - - return false; -} - - export default MouseWheelZoom; diff --git a/src/ol/interaction/PinchRotate.js b/src/ol/interaction/PinchRotate.js index 1a6172be91..fbd99ecebb 100644 --- a/src/ol/interaction/PinchRotate.js +++ b/src/ol/interaction/PinchRotate.js @@ -28,14 +28,15 @@ class PinchRotate extends PointerInteraction { */ constructor(opt_options) { - super({ - handleDownEvent: handleDownEvent, - handleDragEvent: handleDragEvent, - handleUpEvent: handleUpEvent, - stopDown: FALSE - }); + const options = opt_options ? opt_options : {}; - const options = opt_options || {}; + const pointerOptions = /** @type {import("./Pointer.js").Options} */ (options); + + if (!pointerOptions.stopDown) { + pointerOptions.stopDown = FALSE; + } + + super(pointerOptions); /** * @private @@ -75,98 +76,89 @@ class PinchRotate extends PointerInteraction { } -} + /** + * @inheritDoc + */ + handleDragEvent(mapBrowserEvent) { + let rotationDelta = 0.0; + const touch0 = this.targetPointers[0]; + const touch1 = this.targetPointers[1]; -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @this {PinchRotate} - */ -function handleDragEvent(mapBrowserEvent) { - let rotationDelta = 0.0; + // angle between touches + const angle = Math.atan2( + touch1.clientY - touch0.clientY, + touch1.clientX - touch0.clientX); - const touch0 = this.targetPointers[0]; - const touch1 = this.targetPointers[1]; - - // angle between touches - const angle = Math.atan2( - touch1.clientY - touch0.clientY, - touch1.clientX - touch0.clientX); - - if (this.lastAngle_ !== undefined) { - const delta = angle - this.lastAngle_; - this.rotationDelta_ += delta; - if (!this.rotating_ && - Math.abs(this.rotationDelta_) > this.threshold_) { - this.rotating_ = true; + if (this.lastAngle_ !== undefined) { + const delta = angle - this.lastAngle_; + this.rotationDelta_ += delta; + if (!this.rotating_ && + Math.abs(this.rotationDelta_) > this.threshold_) { + this.rotating_ = true; + } + rotationDelta = delta; } - rotationDelta = delta; - } - this.lastAngle_ = angle; + this.lastAngle_ = angle; - const map = mapBrowserEvent.map; - const view = map.getView(); - if (view.getConstraints().rotation === disable) { - return; - } - - // rotate anchor point. - // FIXME: should be the intersection point between the lines: - // touch0,touch1 and previousTouch0,previousTouch1 - const viewportPosition = map.getViewport().getBoundingClientRect(); - const centroid = centroidFromPointers(this.targetPointers); - centroid[0] -= viewportPosition.left; - centroid[1] -= viewportPosition.top; - this.anchor_ = map.getCoordinateFromPixel(centroid); - - // rotate - if (this.rotating_) { - const rotation = view.getRotation(); - map.render(); - rotateWithoutConstraints(view, rotation + rotationDelta, this.anchor_); - } -} - - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {PinchRotate} - */ -function handleUpEvent(mapBrowserEvent) { - if (this.targetPointers.length < 2) { const map = mapBrowserEvent.map; const view = map.getView(); - view.setHint(ViewHint.INTERACTING, -1); + if (view.getConstraints().rotation === disable) { + return; + } + + // rotate anchor point. + // FIXME: should be the intersection point between the lines: + // touch0,touch1 and previousTouch0,previousTouch1 + const viewportPosition = map.getViewport().getBoundingClientRect(); + const centroid = centroidFromPointers(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // rotate if (this.rotating_) { const rotation = view.getRotation(); - rotate(view, rotation, this.anchor_, this.duration_); + map.render(); + rotateWithoutConstraints(view, rotation + rotationDelta, this.anchor_); } - return false; - } else { - return true; } -} - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {PinchRotate} - */ -function handleDownEvent(mapBrowserEvent) { - if (this.targetPointers.length >= 2) { - const map = mapBrowserEvent.map; - this.anchor_ = null; - this.lastAngle_ = undefined; - this.rotating_ = false; - this.rotationDelta_ = 0.0; - if (!this.handlingDownUpSequence) { - map.getView().setHint(ViewHint.INTERACTING, 1); + /** + * @inheritDoc + */ + handleUpEvent(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + const map = mapBrowserEvent.map; + const view = map.getView(); + view.setHint(ViewHint.INTERACTING, -1); + if (this.rotating_) { + const rotation = view.getRotation(); + rotate(view, rotation, this.anchor_, this.duration_); + } + return false; + } else { + return true; + } + } + + /** + * @inheritDoc + */ + handleDownEvent(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + const map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastAngle_ = undefined; + this.rotating_ = false; + this.rotationDelta_ = 0.0; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ViewHint.INTERACTING, 1); + } + return true; + } else { + return false; } - return true; - } else { - return false; } } diff --git a/src/ol/interaction/PinchZoom.js b/src/ol/interaction/PinchZoom.js index 63c130525a..6114870fdf 100644 --- a/src/ol/interaction/PinchZoom.js +++ b/src/ol/interaction/PinchZoom.js @@ -27,15 +27,16 @@ class PinchZoom extends PointerInteraction { */ constructor(opt_options) { - super({ - handleDownEvent: handleDownEvent, - handleDragEvent: handleDragEvent, - handleUpEvent: handleUpEvent, - stopDown: FALSE - }); - const options = opt_options ? opt_options : {}; + const pointerOptions = /** @type {import("./Pointer.js").Options} */ (options); + + if (!pointerOptions.stopDown) { + pointerOptions.stopDown = FALSE; + } + + super(pointerOptions); + /** * @private * @type {boolean} @@ -68,105 +69,96 @@ class PinchZoom extends PointerInteraction { } -} + /** + * @inheritDoc + */ + handleDragEvent(mapBrowserEvent) { + let scaleDelta = 1.0; + + const touch0 = this.targetPointers[0]; + const touch1 = this.targetPointers[1]; + const dx = touch0.clientX - touch1.clientX; + const dy = touch0.clientY - touch1.clientY; + + // distance between touches + const distance = Math.sqrt(dx * dx + dy * dy); + + if (this.lastDistance_ !== undefined) { + scaleDelta = this.lastDistance_ / distance; + } + this.lastDistance_ = distance; -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @this {PinchZoom} - */ -function handleDragEvent(mapBrowserEvent) { - let scaleDelta = 1.0; - - const touch0 = this.targetPointers[0]; - const touch1 = this.targetPointers[1]; - const dx = touch0.clientX - touch1.clientX; - const dy = touch0.clientY - touch1.clientY; - - // distance between touches - const distance = Math.sqrt(dx * dx + dy * dy); - - if (this.lastDistance_ !== undefined) { - scaleDelta = this.lastDistance_ / distance; - } - this.lastDistance_ = distance; - - - const map = mapBrowserEvent.map; - const view = map.getView(); - const resolution = view.getResolution(); - const maxResolution = view.getMaxResolution(); - const minResolution = view.getMinResolution(); - let newResolution = resolution * scaleDelta; - if (newResolution > maxResolution) { - scaleDelta = maxResolution / resolution; - newResolution = maxResolution; - } else if (newResolution < minResolution) { - scaleDelta = minResolution / resolution; - newResolution = minResolution; - } - - if (scaleDelta != 1.0) { - this.lastScaleDelta_ = scaleDelta; - } - - // scale anchor point. - const viewportPosition = map.getViewport().getBoundingClientRect(); - const centroid = centroidFromPointers(this.targetPointers); - centroid[0] -= viewportPosition.left; - centroid[1] -= viewportPosition.top; - this.anchor_ = map.getCoordinateFromPixel(centroid); - - // scale, bypass the resolution constraint - map.render(); - zoomWithoutConstraints(view, newResolution, this.anchor_); -} - - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Stop drag sequence? - * @this {PinchZoom} - */ -function handleUpEvent(mapBrowserEvent) { - if (this.targetPointers.length < 2) { const map = mapBrowserEvent.map; const view = map.getView(); - view.setHint(ViewHint.INTERACTING, -1); const resolution = view.getResolution(); - if (this.constrainResolution_ || - resolution < view.getMinResolution() || - resolution > view.getMaxResolution()) { - // Zoom to final resolution, with an animation, and provide a - // direction not to zoom out/in if user was pinching in/out. - // Direction is > 0 if pinching out, and < 0 if pinching in. - const direction = this.lastScaleDelta_ - 1; - zoom(view, resolution, this.anchor_, this.duration_, direction); + const maxResolution = view.getMaxResolution(); + const minResolution = view.getMinResolution(); + let newResolution = resolution * scaleDelta; + if (newResolution > maxResolution) { + scaleDelta = maxResolution / resolution; + newResolution = maxResolution; + } else if (newResolution < minResolution) { + scaleDelta = minResolution / resolution; + newResolution = minResolution; } - return false; - } else { - return true; + + if (scaleDelta != 1.0) { + this.lastScaleDelta_ = scaleDelta; + } + + // scale anchor point. + const viewportPosition = map.getViewport().getBoundingClientRect(); + const centroid = centroidFromPointers(this.targetPointers); + centroid[0] -= viewportPosition.left; + centroid[1] -= viewportPosition.top; + this.anchor_ = map.getCoordinateFromPixel(centroid); + + // scale, bypass the resolution constraint + map.render(); + zoomWithoutConstraints(view, newResolution, this.anchor_); } -} - -/** - * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. - * @return {boolean} Start drag sequence? - * @this {PinchZoom} - */ -function handleDownEvent(mapBrowserEvent) { - if (this.targetPointers.length >= 2) { - const map = mapBrowserEvent.map; - this.anchor_ = null; - this.lastDistance_ = undefined; - this.lastScaleDelta_ = 1; - if (!this.handlingDownUpSequence) { - map.getView().setHint(ViewHint.INTERACTING, 1); + /** + * @inheritDoc + */ + handleUpEvent(mapBrowserEvent) { + if (this.targetPointers.length < 2) { + const map = mapBrowserEvent.map; + const view = map.getView(); + view.setHint(ViewHint.INTERACTING, -1); + const resolution = view.getResolution(); + if (this.constrainResolution_ || + resolution < view.getMinResolution() || + resolution > view.getMaxResolution()) { + // Zoom to final resolution, with an animation, and provide a + // direction not to zoom out/in if user was pinching in/out. + // Direction is > 0 if pinching out, and < 0 if pinching in. + const direction = this.lastScaleDelta_ - 1; + zoom(view, resolution, this.anchor_, this.duration_, direction); + } + return false; + } else { + return true; + } + } + + /** + * @inheritDoc + */ + handleDownEvent(mapBrowserEvent) { + if (this.targetPointers.length >= 2) { + const map = mapBrowserEvent.map; + this.anchor_ = null; + this.lastDistance_ = undefined; + this.lastScaleDelta_ = 1; + if (!this.handlingDownUpSequence) { + map.getView().setHint(ViewHint.INTERACTING, 1); + } + return true; + } else { + return false; } - return true; - } else { - return false; } } diff --git a/src/ol/interaction/Pointer.js b/src/ol/interaction/Pointer.js index 4a20e7c06b..964ec690c2 100644 --- a/src/ol/interaction/Pointer.js +++ b/src/ol/interaction/Pointer.js @@ -52,9 +52,7 @@ class PointerInteraction extends Interaction { const options = opt_options ? opt_options : {}; - super({ - handleEvent: options.handleEvent || handleEvent - }); + super(/** @type {import("./Interaction.js").InteractionOptions} */ (options)); if (options.handleDownEvent) { this.handleDownEvent = options.handleDownEvent; @@ -72,20 +70,16 @@ class PointerInteraction extends Interaction { this.handleUpEvent = options.handleUpEvent; } + if (options.stopDown) { + this.stopDown = options.stopDown; + } + /** * @type {boolean} * @protected */ this.handlingDownUpSequence = false; - /** - * This function is used to determine if "down" events should be propagated - * to other interactions or should be stopped. - * @type {function(boolean):boolean} - * @protected - */ - this.stopDown = options.stopDown ? options.stopDown : stopDown; - /** * @type {!Object} * @private @@ -117,6 +111,42 @@ class PointerInteraction extends Interaction { */ handleDragEvent(mapBrowserEvent) {} + /** + * Handles the {@link module:ol/MapBrowserEvent map browser event} and may call into + * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are + * detected. + * @override + * @api + */ + handleEvent(mapBrowserEvent) { + if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { + return true; + } + + let stopEvent = false; + this.updateTrackedPointers_(mapBrowserEvent); + if (this.handlingDownUpSequence) { + if (mapBrowserEvent.type == MapBrowserEventType.POINTERDRAG) { + this.handleDragEvent(mapBrowserEvent); + } else if (mapBrowserEvent.type == MapBrowserEventType.POINTERUP) { + const handledUp = this.handleUpEvent(mapBrowserEvent); + this.handlingDownUpSequence = handledUp && this.targetPointers.length > 0; + } + } else { + if (mapBrowserEvent.type == MapBrowserEventType.POINTERDOWN) { + const handled = this.handleDownEvent(mapBrowserEvent); + if (handled) { + mapBrowserEvent.preventDefault(); + } + this.handlingDownUpSequence = handled; + stopEvent = this.stopDown(handled); + } else if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE) { + this.handleMoveEvent(mapBrowserEvent); + } + } + return !stopEvent; + } + /** * Handle pointer move events. * @param {MapBrowserPointerEvent} mapBrowserEvent Event. @@ -134,6 +164,16 @@ class PointerInteraction extends Interaction { return false; } + /** + * This function is used to determine if "down" events should be propagated + * to other interactions or should be stopped. + * @param {boolean} handled Was the event handled by the interaction? + * @return {boolean} Should the `down` event be stopped? + */ + stopDown(handled) { + return handled; + } + /** * @param {MapBrowserPointerEvent} mapBrowserEvent Event. * @private @@ -188,51 +228,4 @@ function isPointerDraggingEvent(mapBrowserEvent) { } -/** - * Handles the {@link module:ol/MapBrowserEvent map browser event} and may call into - * other functions, if event sequences like e.g. 'drag' or 'down-up' etc. are - * detected. - * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. - * @return {boolean} `false` to stop event propagation. - * @this {PointerInteraction} - * @api - */ -export function handleEvent(mapBrowserEvent) { - if (!(mapBrowserEvent instanceof MapBrowserPointerEvent)) { - return true; - } - - let stopEvent = false; - this.updateTrackedPointers_(mapBrowserEvent); - if (this.handlingDownUpSequence) { - if (mapBrowserEvent.type == MapBrowserEventType.POINTERDRAG) { - this.handleDragEvent(mapBrowserEvent); - } else if (mapBrowserEvent.type == MapBrowserEventType.POINTERUP) { - const handledUp = this.handleUpEvent(mapBrowserEvent); - this.handlingDownUpSequence = handledUp && this.targetPointers.length > 0; - } - } else { - if (mapBrowserEvent.type == MapBrowserEventType.POINTERDOWN) { - const handled = this.handleDownEvent(mapBrowserEvent); - if (handled) { - mapBrowserEvent.preventDefault(); - } - this.handlingDownUpSequence = handled; - stopEvent = this.stopDown(handled); - } else if (mapBrowserEvent.type == MapBrowserEventType.POINTERMOVE) { - this.handleMoveEvent(mapBrowserEvent); - } - } - return !stopEvent; -} - - export default PointerInteraction; - -/** - * @param {boolean} handled Was the event handled by the interaction? - * @return {boolean} Should the `down` event be stopped? - */ -function stopDown(handled) { - return handled; -} diff --git a/src/ol/interaction/Snap.js b/src/ol/interaction/Snap.js index 5e69318067..c31520c7a3 100644 --- a/src/ol/interaction/Snap.js +++ b/src/ol/interaction/Snap.js @@ -11,7 +11,7 @@ import {boundingExtent, createEmpty} from '../extent.js'; import {TRUE, FALSE} from '../functions.js'; import GeometryType from '../geom/GeometryType.js'; import {fromCircle} from '../geom/Polygon.js'; -import PointerInteraction, {handleEvent as handlePointerEvent} from '../interaction/Pointer.js'; +import PointerInteraction from '../interaction/Pointer.js'; import {getValues} from '../obj.js'; import {VectorSourceEvent} from '../source/Vector.js'; import VectorEventType from '../source/VectorEventType.js'; @@ -71,15 +71,20 @@ class Snap extends PointerInteraction { */ constructor(opt_options) { - super({ - handleEvent: handleEvent, - handleDownEvent: TRUE, - handleUpEvent: handleUpEvent, - stopDown: FALSE - }); - const options = opt_options ? opt_options : {}; + const pointerOptions = /** @type {import("./Pointer.js").Options} */ (options); + + if (!pointerOptions.handleDownEvent) { + pointerOptions.handleDownEvent = TRUE; + } + + if (!pointerOptions.stopDown) { + pointerOptions.stopDown = FALSE; + } + + super(pointerOptions); + /** * @type {import("../source/Vector.js").default} * @private @@ -239,6 +244,18 @@ class Snap extends PointerInteraction { ); } + /** + * @inheritDoc + */ + handleEvent(evt) { + const result = this.snapTo(evt.pixel, evt.coordinate, evt.map); + if (result.snapped) { + evt.coordinate = result.vertex.slice(0, 2); + evt.pixel = result.vertexPixel; + } + return super.handleEvent(evt); + } + /** * @param {import("../source/Vector.js").default|import("../Collection.js").CollectionEvent} evt Event. * @private @@ -283,6 +300,18 @@ class Snap extends PointerInteraction { } } + /** + * @inheritDoc + */ + handleUpEvent(evt) { + const featuresToUpdate = getValues(this.pendingFeatures_); + if (featuresToUpdate.length) { + featuresToUpdate.forEach(this.updateFeature_.bind(this)); + this.pendingFeatures_ = {}; + } + return false; + } + /** * Remove a feature from the collection of features that we may snap to. * @param {import("../Feature.js").default} feature Feature @@ -587,37 +616,6 @@ class Snap extends PointerInteraction { } -/** - * Handle all pointer events events. - * @param {import("../MapBrowserEvent.js").default} evt A move event. - * @return {boolean} Pass the event to other interactions. - * @this {Snap} - */ -export function handleEvent(evt) { - const result = this.snapTo(evt.pixel, evt.coordinate, evt.map); - if (result.snapped) { - evt.coordinate = result.vertex.slice(0, 2); - evt.pixel = result.vertexPixel; - } - return handlePointerEvent.call(this, evt); -} - - -/** - * @param {import("../MapBrowserPointerEvent.js").default} evt Event. - * @return {boolean} Stop drag sequence? - * @this {Snap} - */ -function handleUpEvent(evt) { - const featuresToUpdate = getValues(this.pendingFeatures_); - if (featuresToUpdate.length) { - featuresToUpdate.forEach(this.updateFeature_.bind(this)); - this.pendingFeatures_ = {}; - } - return false; -} - - /** * Sort segments by distance, helper function * @param {SegmentData} a The first segment data. diff --git a/src/ol/interaction/Translate.js b/src/ol/interaction/Translate.js index 34c3f534be..73b843ce02 100644 --- a/src/ol/interaction/Translate.js +++ b/src/ol/interaction/Translate.js @@ -98,15 +98,10 @@ class Translate extends PointerInteraction { * @param {Options=} opt_options Options. */ constructor(opt_options) { - super({ - handleDownEvent: handleDownEvent, - handleDragEvent: handleDragEvent, - handleMoveEvent: handleMoveEvent, - handleUpEvent: handleUpEvent - }); - const options = opt_options ? opt_options : {}; + super(/** @type {import("./Pointer.js").Options} */ (options)); + /** * The last position we translated to. * @type {import("../coordinate.js").Coordinate} @@ -160,6 +155,86 @@ class Translate extends PointerInteraction { } + /** + * @inheritDoc + */ + handleDownEvent(event) { + this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map); + if (!this.lastCoordinate_ && this.lastFeature_) { + this.lastCoordinate_ = event.coordinate; + this.handleMoveEvent(event); + + const features = this.features_ || new Collection([this.lastFeature_]); + + this.dispatchEvent( + new TranslateEvent( + TranslateEventType.TRANSLATESTART, features, + event.coordinate)); + return true; + } + return false; + } + + /** + * @inheritDoc + */ + handleUpEvent(event) { + if (this.lastCoordinate_) { + this.lastCoordinate_ = null; + this.handleMoveEvent(event); + + const features = this.features_ || new Collection([this.lastFeature_]); + + this.dispatchEvent( + new TranslateEvent( + TranslateEventType.TRANSLATEEND, features, + event.coordinate)); + return true; + } + return false; + } + + /** + * @inheritDoc + */ + handleDragEvent(event) { + if (this.lastCoordinate_) { + const newCoordinate = event.coordinate; + const deltaX = newCoordinate[0] - this.lastCoordinate_[0]; + const deltaY = newCoordinate[1] - this.lastCoordinate_[1]; + + const features = this.features_ || new Collection([this.lastFeature_]); + + features.forEach(function(feature) { + const geom = feature.getGeometry(); + geom.translate(deltaX, deltaY); + feature.setGeometry(geom); + }); + + this.lastCoordinate_ = newCoordinate; + this.dispatchEvent( + new TranslateEvent( + TranslateEventType.TRANSLATING, features, + newCoordinate)); + } + } + + /** + * @inheritDoc + */ + handleMoveEvent(event) { + const elem = event.map.getViewport(); + + // Change the cursor to grab/grabbing if hovering any of the features managed + // by the interaction + if (this.featuresAtPixel_(event.pixel, event.map)) { + elem.classList.remove(this.lastCoordinate_ ? 'ol-grab' : 'ol-grabbing'); + elem.classList.add(this.lastCoordinate_ ? 'ol-grabbing' : 'ol-grab'); + } else { + elem.classList.remove('ol-grab', 'ol-grabbing'); + } + } + /** * Tests to see if the given coordinates intersects any of our selected * features. @@ -234,95 +309,4 @@ class Translate extends PointerInteraction { } } - -/** - * @param {import("../MapBrowserPointerEvent.js").default} event Event. - * @return {boolean} Start drag sequence? - * @this {Translate} - */ -function handleDownEvent(event) { - this.lastFeature_ = this.featuresAtPixel_(event.pixel, event.map); - if (!this.lastCoordinate_ && this.lastFeature_) { - this.lastCoordinate_ = event.coordinate; - handleMoveEvent.call(this, event); - - const features = this.features_ || new Collection([this.lastFeature_]); - - this.dispatchEvent( - new TranslateEvent( - TranslateEventType.TRANSLATESTART, features, - event.coordinate)); - return true; - } - return false; -} - - -/** - * @param {import("../MapBrowserPointerEvent.js").default} event Event. - * @return {boolean} Stop drag sequence? - * @this {Translate} - */ -function handleUpEvent(event) { - if (this.lastCoordinate_) { - this.lastCoordinate_ = null; - handleMoveEvent.call(this, event); - - const features = this.features_ || new Collection([this.lastFeature_]); - - this.dispatchEvent( - new TranslateEvent( - TranslateEventType.TRANSLATEEND, features, - event.coordinate)); - return true; - } - return false; -} - - -/** - * @param {import("../MapBrowserPointerEvent.js").default} event Event. - * @this {Translate} - */ -function handleDragEvent(event) { - if (this.lastCoordinate_) { - const newCoordinate = event.coordinate; - const deltaX = newCoordinate[0] - this.lastCoordinate_[0]; - const deltaY = newCoordinate[1] - this.lastCoordinate_[1]; - - const features = this.features_ || new Collection([this.lastFeature_]); - - features.forEach(function(feature) { - const geom = feature.getGeometry(); - geom.translate(deltaX, deltaY); - feature.setGeometry(geom); - }); - - this.lastCoordinate_ = newCoordinate; - this.dispatchEvent( - new TranslateEvent( - TranslateEventType.TRANSLATING, features, - newCoordinate)); - } -} - - -/** - * @param {import("../MapBrowserEvent.js").default} event Event. - * @this {Translate} - */ -function handleMoveEvent(event) { - const elem = event.map.getViewport(); - - // Change the cursor to grab/grabbing if hovering any of the features managed - // by the interaction - if (this.featuresAtPixel_(event.pixel, event.map)) { - elem.classList.remove(this.lastCoordinate_ ? 'ol-grab' : 'ol-grabbing'); - elem.classList.add(this.lastCoordinate_ ? 'ol-grabbing' : 'ol-grab'); - } else { - elem.classList.remove('ol-grab', 'ol-grabbing'); - } -} - - export default Translate; diff --git a/test/spec/ol/interaction/snap.test.js b/test/spec/ol/interaction/snap.test.js index ad421ef3be..c1e73bed4d 100644 --- a/test/spec/ol/interaction/snap.test.js +++ b/test/spec/ol/interaction/snap.test.js @@ -5,7 +5,7 @@ import View from '../../../../src/ol/View.js'; import Circle from '../../../../src/ol/geom/Circle.js'; import Point from '../../../../src/ol/geom/Point.js'; import LineString from '../../../../src/ol/geom/LineString.js'; -import Snap, {handleEvent} from '../../../../src/ol/interaction/Snap.js'; +import Snap from '../../../../src/ol/interaction/Snap.js'; describe('ol.interaction.Snap', function() { @@ -67,7 +67,7 @@ describe('ol.interaction.Snap', function() { coordinate: [0, 0], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); // check that the coordinate is in XY and not XYZ expect(event.coordinate).to.eql([0, 0]); }); @@ -86,7 +86,7 @@ describe('ol.interaction.Snap', function() { coordinate: [7, 4], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate).to.eql([7, 0]); }); @@ -104,7 +104,7 @@ describe('ol.interaction.Snap', function() { coordinate: [7, 4], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate).to.eql([10, 0]); }); @@ -121,7 +121,7 @@ describe('ol.interaction.Snap', function() { coordinate: [5, 5], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate[0]).to.roughlyEqual(Math.sin(Math.PI / 4) * 10, 1e-10); expect(event.coordinate[1]).to.roughlyEqual(Math.sin(Math.PI / 4) * 10, 1e-10); @@ -143,7 +143,7 @@ describe('ol.interaction.Snap', function() { coordinate: [7, 4], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate).to.eql([10, 0]); }); @@ -163,7 +163,7 @@ describe('ol.interaction.Snap', function() { coordinate: [7, 4], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate).to.eql([10, 0]); }); @@ -186,7 +186,7 @@ describe('ol.interaction.Snap', function() { coordinate: [7, 4], map: map }; - handleEvent.call(snapInteraction, event); + snapInteraction.handleEvent(event); expect(event.coordinate).to.eql([10, 0]); });