var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); /** * @module ol/interaction/Extent */ import Event from '../events/Event.js'; import Feature from '../Feature.js'; import MapBrowserEventType from '../MapBrowserEventType.js'; import Point from '../geom/Point.js'; import PointerInteraction from './Pointer.js'; import VectorLayer from '../layer/Vector.js'; import VectorSource from '../source/Vector.js'; import { always } from '../events/condition.js'; import { boundingExtent, getArea } from '../extent.js'; import { closestOnSegment, distance as coordinateDistance, squaredDistance as squaredCoordinateDistance, squaredDistanceToSegment, } from '../coordinate.js'; import { createEditingStyle } from '../style/Style.js'; import { fromExtent as polygonFromExtent } from '../geom/Polygon.js'; import { toUserExtent } from '../proj.js'; /** * @typedef {Object} Options * @property {import("../events/condition.js").Condition} [condition] A function that * takes an {@link module:ol/MapBrowserEvent~MapBrowserEvent} and returns a * boolean to indicate whether that event should be handled. * Default is {@link module:ol/events/condition.always}. * @property {import("../extent.js").Extent} [extent] Initial extent. Defaults to no * initial extent. * @property {import("../style/Style.js").StyleLike} [boxStyle] * Style for the drawn extent box. Defaults to the `Polygon` editing style * documented in {@link module:ol/style/Style~Style} * @property {number} [pixelTolerance=10] Pixel tolerance for considering the * pointer close enough to a segment or vertex for editing. * @property {import("../style/Style.js").StyleLike} [pointerStyle] * Style for the cursor used to draw the extent. Defaults to the `Point` editing style * documented in {@link module:ol/style/Style~Style} * @property {boolean} [wrapX=false] Wrap the drawn extent across multiple maps * in the X direction? Only affects visuals, not functionality. */ /** * @enum {string} */ var ExtentEventType = { /** * Triggered after the extent is changed * @event ExtentEvent#extentchanged * @api */ EXTENTCHANGED: 'extentchanged', }; /** * @classdesc * Events emitted by {@link module:ol/interaction/Extent~Extent} instances are * instances of this type. */ var ExtentEvent = /** @class */ (function (_super) { __extends(ExtentEvent, _super); /** * @param {import("../extent.js").Extent} extent the new extent */ function ExtentEvent(extent) { var _this = _super.call(this, ExtentEventType.EXTENTCHANGED) || this; /** * The current extent. * @type {import("../extent.js").Extent} * @api */ _this.extent = extent; return _this; } return ExtentEvent; }(Event)); export { ExtentEvent }; /*** * @template Return * @typedef {import("../Observable").OnSignature & * import("../Observable").OnSignature & * import("../Observable").OnSignature<'extentchanged', ExtentEvent, Return> & * import("../Observable").CombinedOnSignature} ExtentOnSignature */ /** * @classdesc * Allows the user to draw a vector box by clicking and dragging on the map. * Once drawn, the vector box can be modified by dragging its vertices or edges. * This interaction is only supported for mouse devices. * * @fires ExtentEvent * @api */ var Extent = /** @class */ (function (_super) { __extends(Extent, _super); /** * @param {Options} [opt_options] Options. */ function Extent(opt_options) { var _this = this; var options = opt_options || {}; _this = _super.call(this, /** @type {import("./Pointer.js").Options} */ (options)) || this; /*** * @type {ExtentOnSignature} */ _this.on; /*** * @type {ExtentOnSignature} */ _this.once; /*** * @type {ExtentOnSignature} */ _this.un; /** * Condition * @type {import("../events/condition.js").Condition} * @private */ _this.condition_ = options.condition ? options.condition : always; /** * Extent of the drawn box * @type {import("../extent.js").Extent} * @private */ _this.extent_ = null; /** * Handler for pointer move events * @type {function (import("../coordinate.js").Coordinate): import("../extent.js").Extent|null} * @private */ _this.pointerHandler_ = null; /** * Pixel threshold to snap to extent * @type {number} * @private */ _this.pixelTolerance_ = options.pixelTolerance !== undefined ? options.pixelTolerance : 10; /** * Is the pointer snapped to an extent vertex * @type {boolean} * @private */ _this.snappedToVertex_ = false; /** * Feature for displaying the visible extent * @type {Feature} * @private */ _this.extentFeature_ = null; /** * Feature for displaying the visible pointer * @type {Feature} * @private */ _this.vertexFeature_ = null; if (!opt_options) { opt_options = {}; } /** * Layer for the extentFeature * @type {VectorLayer} * @private */ _this.extentOverlay_ = new VectorLayer({ source: new VectorSource({ useSpatialIndex: false, wrapX: !!opt_options.wrapX, }), style: opt_options.boxStyle ? opt_options.boxStyle : getDefaultExtentStyleFunction(), updateWhileAnimating: true, updateWhileInteracting: true, }); /** * Layer for the vertexFeature * @type {VectorLayer} * @private */ _this.vertexOverlay_ = new VectorLayer({ source: new VectorSource({ useSpatialIndex: false, wrapX: !!opt_options.wrapX, }), style: opt_options.pointerStyle ? opt_options.pointerStyle : getDefaultPointerStyleFunction(), updateWhileAnimating: true, updateWhileInteracting: true, }); if (opt_options.extent) { _this.setExtent(opt_options.extent); } return _this; } /** * @param {import("../pixel.js").Pixel} pixel cursor location * @param {import("../PluggableMap.js").default} map map * @return {import("../coordinate.js").Coordinate|null} snapped vertex on extent * @private */ Extent.prototype.snapToVertex_ = function (pixel, map) { var pixelCoordinate = map.getCoordinateFromPixelInternal(pixel); var sortByDistance = function (a, b) { return (squaredDistanceToSegment(pixelCoordinate, a) - squaredDistanceToSegment(pixelCoordinate, b)); }; var extent = this.getExtentInternal(); if (extent) { //convert extents to line segments and find the segment closest to pixelCoordinate var segments = getSegments(extent); segments.sort(sortByDistance); var closestSegment = segments[0]; var vertex = closestOnSegment(pixelCoordinate, closestSegment); var vertexPixel = map.getPixelFromCoordinateInternal(vertex); //if the distance is within tolerance, snap to the segment if (coordinateDistance(pixel, vertexPixel) <= this.pixelTolerance_) { //test if we should further snap to a vertex var pixel1 = map.getPixelFromCoordinateInternal(closestSegment[0]); var pixel2 = map.getPixelFromCoordinateInternal(closestSegment[1]); var squaredDist1 = squaredCoordinateDistance(vertexPixel, pixel1); var squaredDist2 = squaredCoordinateDistance(vertexPixel, pixel2); var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); this.snappedToVertex_ = dist <= this.pixelTolerance_; if (this.snappedToVertex_) { vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0]; } return vertex; } } return null; }; /** * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent pointer move event * @private */ Extent.prototype.handlePointerMove_ = function (mapBrowserEvent) { var pixel = mapBrowserEvent.pixel; var map = mapBrowserEvent.map; var vertex = this.snapToVertex_(pixel, map); if (!vertex) { vertex = map.getCoordinateFromPixelInternal(pixel); } this.createOrUpdatePointerFeature_(vertex); }; /** * @param {import("../extent.js").Extent} extent extent * @return {Feature} extent as featrue * @private */ Extent.prototype.createOrUpdateExtentFeature_ = function (extent) { var extentFeature = this.extentFeature_; if (!extentFeature) { if (!extent) { extentFeature = new Feature({}); } else { extentFeature = new Feature(polygonFromExtent(extent)); } this.extentFeature_ = extentFeature; this.extentOverlay_.getSource().addFeature(extentFeature); } else { if (!extent) { extentFeature.setGeometry(undefined); } else { extentFeature.setGeometry(polygonFromExtent(extent)); } } return extentFeature; }; /** * @param {import("../coordinate.js").Coordinate} vertex location of feature * @return {Feature} vertex as feature * @private */ Extent.prototype.createOrUpdatePointerFeature_ = function (vertex) { var vertexFeature = this.vertexFeature_; if (!vertexFeature) { vertexFeature = new Feature(new Point(vertex)); this.vertexFeature_ = vertexFeature; this.vertexOverlay_.getSource().addFeature(vertexFeature); } else { var geometry = vertexFeature.getGeometry(); geometry.setCoordinates(vertex); } return vertexFeature; }; /** * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Map browser event. * @return {boolean} `false` to stop event propagation. */ Extent.prototype.handleEvent = function (mapBrowserEvent) { if (!mapBrowserEvent.originalEvent || !this.condition_(mapBrowserEvent)) { 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.prototype.handleEvent.call(this, mapBrowserEvent); //return false to stop propagation return false; }; /** * Handle pointer down events. * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. * @return {boolean} If the event was consumed. */ Extent.prototype.handleDownEvent = function (mapBrowserEvent) { var pixel = mapBrowserEvent.pixel; var map = mapBrowserEvent.map; var extent = this.getExtentInternal(); var vertex = this.snapToVertex_(pixel, map); //find the extent corner opposite the passed corner var getOpposingPoint = function (point) { var x_ = null; var 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) { var x = vertex[0] == extent[0] || vertex[0] == extent[2] ? vertex[0] : null; var 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.getCoordinateFromPixelInternal(pixel); this.setExtent([vertex[0], vertex[1], vertex[0], vertex[1]]); this.pointerHandler_ = getPointHandler(vertex); } return true; //event handled; start downup sequence }; /** * Handle pointer drag events. * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. */ Extent.prototype.handleDragEvent = function (mapBrowserEvent) { if (this.pointerHandler_) { var pixelCoordinate = mapBrowserEvent.coordinate; this.setExtent(this.pointerHandler_(pixelCoordinate)); this.createOrUpdatePointerFeature_(pixelCoordinate); } }; /** * Handle pointer up events. * @param {import("../MapBrowserEvent.js").default} mapBrowserEvent Event. * @return {boolean} If the event was consumed. */ Extent.prototype.handleUpEvent = function (mapBrowserEvent) { this.pointerHandler_ = null; //If bbox is zero area, set to null; var extent = this.getExtentInternal(); if (!extent || getArea(extent) === 0) { this.setExtent(null); } return false; //Stop handling downup sequence }; /** * Remove the interaction from its current map and attach it to the new map. * Subclasses may set up event handlers to get notified about changes to * the map here. * @param {import("../PluggableMap.js").default} map Map. */ Extent.prototype.setMap = function (map) { this.extentOverlay_.setMap(map); this.vertexOverlay_.setMap(map); _super.prototype.setMap.call(this, map); }; /** * Returns the current drawn extent in the view projection (or user projection if set) * * @return {import("../extent.js").Extent} Drawn extent in the view projection. * @api */ Extent.prototype.getExtent = function () { return toUserExtent(this.getExtentInternal(), this.getMap().getView().getProjection()); }; /** * Returns the current drawn extent in the view projection * * @return {import("../extent.js").Extent} Drawn extent in the view projection. * @api */ Extent.prototype.getExtentInternal = function () { return this.extent_; }; /** * Manually sets the drawn extent, using the view projection. * * @param {import("../extent.js").Extent} extent Extent * @api */ Extent.prototype.setExtent = function (extent) { //Null extent means no bbox this.extent_ = extent ? extent : null; this.createOrUpdateExtentFeature_(extent); this.dispatchEvent(new ExtentEvent(this.extent_)); }; return Extent; }(PointerInteraction)); /** * Returns the default style for the drawn bbox * * @return {import("../style/Style.js").StyleFunction} Default Extent style */ function getDefaultExtentStyleFunction() { var style = createEditingStyle(); return function (feature, resolution) { return style['Polygon']; }; } /** * Returns the default style for the pointer * * @return {import("../style/Style.js").StyleFunction} Default pointer style */ function getDefaultPointerStyleFunction() { var style = createEditingStyle(); return function (feature, resolution) { return style['Point']; }; } /** * @param {import("../coordinate.js").Coordinate} fixedPoint corner that will be unchanged in the new extent * @return {function (import("../coordinate.js").Coordinate): import("../extent.js").Extent} event handler */ function getPointHandler(fixedPoint) { return function (point) { return boundingExtent([fixedPoint, point]); }; } /** * @param {import("../coordinate.js").Coordinate} fixedP1 first corner that will be unchanged in the new extent * @param {import("../coordinate.js").Coordinate} fixedP2 second corner that will be unchanged in the new extent * @return {function (import("../coordinate.js").Coordinate): import("../extent.js").Extent|null} event handler */ function getEdgeHandler(fixedP1, fixedP2) { if (fixedP1[0] == fixedP2[0]) { return function (point) { return boundingExtent([fixedP1, [point[0], fixedP2[1]]]); }; } else if (fixedP1[1] == fixedP2[1]) { return function (point) { return boundingExtent([fixedP1, [fixedP2[0], point[1]]]); }; } else { return null; } } /** * @param {import("../extent.js").Extent} extent extent * @return {Array>} extent line segments */ function getSegments(extent) { return [ [ [extent[0], extent[1]], [extent[0], extent[3]], ], [ [extent[0], extent[3]], [extent[2], extent[3]], ], [ [extent[2], extent[3]], [extent[2], extent[1]], ], [ [extent[2], extent[1]], [extent[0], extent[1]], ], ]; } export default Extent; //# sourceMappingURL=Extent.js.map