ol
Version:
OpenLayers mapping library
460 lines • 16.8 kB
JavaScript
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 (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
/**
* @module ol/interaction/Extent
*/
import Feature from '../Feature.js';
import MapBrowserEventType from '../MapBrowserEventType.js';
import { squaredDistanceToSegment, closestOnSegment, distance as coordinateDistance, squaredDistance as squaredCoordinateDistance } from '../coordinate.js';
import Event from '../events/Event.js';
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 from './Pointer.js';
import VectorLayer from '../layer/Vector.js';
import VectorSource from '../source/Vector.js';
import { createEditingStyle } from '../style/Style.js';
import { toUserExtent } from '../proj.js';
/**
* @typedef {Object} Options
* @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
* {@link module:ol/style/Style~createEditing()['Polygon']}
* @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
* {@link module:ol/style/Style~createEditing()['Point']}
* @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));
/**
* @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;
/**
* 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<Point>}
* @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
* @returns {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
* @returns {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
* @returns {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;
};
/**
* @inheritDoc
*/
Extent.prototype.handleEvent = function (mapBrowserEvent) {
if (!( /** @type {import("../MapBrowserPointerEvent.js").default} */(mapBrowserEvent).pointerEvent)) {
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;
};
/**
* @inheritDoc
*/
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
};
/**
* @inheritDoc
*/
Extent.prototype.handleDragEvent = function (mapBrowserEvent) {
if (this.pointerHandler_) {
var pixelCoordinate = mapBrowserEvent.coordinate;
this.setExtent(this.pointerHandler_(pixelCoordinate));
this.createOrUpdatePointerFeature_(pixelCoordinate);
}
return true;
};
/**
* @inheritDoc
*/
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
};
/**
* @inheritDoc
*/
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[GeometryType.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[GeometryType.POINT];
};
}
/**
* @param {import("../coordinate.js").Coordinate} fixedPoint corner that will be unchanged in the new extent
* @returns {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
* @returns {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
* @returns {Array<Array<import("../coordinate.js").Coordinate>>} 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