426 lines
14 KiB
JavaScript
426 lines
14 KiB
JavaScript
/* Copyright (c) 2016 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
*/
|
|
|
|
import ol_interaction_Interaction from 'ol/interaction/Interaction.js'
|
|
import ol_style_Style_defaultStyle from '../style/defaultStyle.js'
|
|
import ol_Collection from 'ol/Collection.js'
|
|
import ol_layer_Vector from 'ol/layer/Vector.js'
|
|
import ol_source_Vector from 'ol/source/Vector.js'
|
|
import ol_geom_Circle from 'ol/geom/Circle.js'
|
|
import {fromCircle as ol_geom_Polygon_fromCircle} from 'ol/geom/Polygon.js'
|
|
import ol_geom_Point from 'ol/geom/Point.js'
|
|
import ol_geom_LineString from 'ol/geom/LineString.js'
|
|
import ol_geom_Polygon from 'ol/geom/Polygon.js'
|
|
import ol_Feature from 'ol/Feature.js'
|
|
|
|
/** Interaction rotate
|
|
* @constructor
|
|
* @extends {ol_interaction_Interaction}
|
|
* @fires drawstart, drawing, drawend, drawcancel
|
|
* @param {olx.interaction.TransformOptions} options
|
|
* @param {Array<ol.Layer>} options.source Destination source for the drawn features
|
|
* @param {ol.Collection<ol.Feature>} options.features Destination collection for the drawn features
|
|
* @param {ol.style.Style | Array.<ol.style.Style> | ol.style.StyleFunction | undefined} options.style style for the sketch
|
|
* @param {integer} options.sides number of sides, default 0 = circle
|
|
* @param { ol.events.ConditionType | undefined } options.condition A function that takes an ol.MapBrowserEvent and returns a boolean that event should be handled. By default module:ol/events/condition.always.
|
|
* @param { ol.events.ConditionType | undefined } options.squareCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw square features. Default test shift key
|
|
* @param { ol.events.ConditionType | undefined } options.centerCondition A function that takes an ol.MapBrowserEvent and returns a boolean to draw centered features. Default check Ctrl key
|
|
* @param { bool } options.canRotate Allow rotation when centered + square, default: true
|
|
* @param { string } [options.geometryName=geometry]
|
|
* @param { number } options.clickTolerance click tolerance on touch devices, default: 6
|
|
* @param { number } options.maxCircleCoordinates Maximum number of point on a circle, default: 100
|
|
*/
|
|
var ol_interaction_DrawRegular = class olinteractionDrawRegular extends ol_interaction_Interaction {
|
|
constructor(options) {
|
|
options = options || {}
|
|
|
|
super({
|
|
handleEvent: function(e) { return self.handleEvent_(e) }
|
|
})
|
|
|
|
var self = this;
|
|
|
|
this.squaredClickTolerance_ = options.clickTolerance ? options.clickTolerance * options.clickTolerance : 36
|
|
this.maxCircleCoordinates_ = options.maxCircleCoordinates || 100
|
|
|
|
// Collection of feature to transform
|
|
this.features_ = options.features
|
|
// List of layers to transform
|
|
this.source_ = options.source
|
|
// Square condition
|
|
this.conditionFn_ = options.condition
|
|
// Square condition
|
|
this.squareFn_ = options.squareCondition
|
|
// Centered condition
|
|
this.centeredFn_ = options.centerCondition
|
|
// Allow rotation when centered + square
|
|
this.canRotate_ = (options.canRotate !== false)
|
|
// Specify custom geometry name
|
|
this.geometryName_ = options.geometryName || 'geometry'
|
|
|
|
// Number of sides (default=0: circle)
|
|
this.setSides(options.sides)
|
|
|
|
// Style
|
|
var defaultStyle = ol_style_Style_defaultStyle(true)
|
|
|
|
// Create a new overlay layer for the sketch
|
|
this.sketch_ = new ol_Collection()
|
|
this.overlayLayer_ = new ol_layer_Vector({
|
|
source: new ol_source_Vector({
|
|
features: this.sketch_,
|
|
useSpatialIndex: false
|
|
}),
|
|
name: 'DrawRegular overlay',
|
|
displayInLayerSwitcher: false,
|
|
style: options.style || defaultStyle
|
|
})
|
|
}
|
|
/**
|
|
* Remove the interaction from its current map, if any, and attach it to a new
|
|
* map, if any. Pass `null` to just remove the interaction from the current map.
|
|
* @param {ol.Map} map Map.
|
|
* @api stable
|
|
*/
|
|
setMap(map) {
|
|
if (this.getMap()) this.getMap().removeLayer(this.overlayLayer_)
|
|
super.setMap(map)
|
|
this.overlayLayer_.setMap(map)
|
|
}
|
|
/**
|
|
* Activate/deactivate the interaction
|
|
* @param {boolean}
|
|
* @api stable
|
|
*/
|
|
setActive(b) {
|
|
this.reset()
|
|
super.setActive(b)
|
|
}
|
|
/**
|
|
* Reset the interaction
|
|
* @api stable
|
|
*/
|
|
reset() {
|
|
if (this.overlayLayer_) this.overlayLayer_.getSource().clear()
|
|
this.started_ = false
|
|
}
|
|
/**
|
|
* Set the number of sides.
|
|
* @param {int} number of sides.
|
|
* @api stable
|
|
*/
|
|
setSides(nb) {
|
|
nb = parseInt(nb)
|
|
this.sides_ = nb > 2 ? nb : 0
|
|
}
|
|
/**
|
|
* Allow rotation when centered + square
|
|
* @param {bool}
|
|
* @api stable
|
|
*/
|
|
canRotate(b) {
|
|
if (b === true || b === false)
|
|
this.canRotate_ = b
|
|
return this.canRotate_
|
|
}
|
|
/**
|
|
* Get the number of sides.
|
|
* @return {int} number of sides.
|
|
* @api stable
|
|
*/
|
|
getSides() {
|
|
return this.sides_
|
|
}
|
|
/** Get geom of the current drawing
|
|
* @return {ol.geom.Polygon | ol.geom.Point}
|
|
*/
|
|
getGeom_() {
|
|
this.overlayLayer_.getSource().clear()
|
|
if (!this.center_)
|
|
return false
|
|
|
|
var g
|
|
if (this.coord_) {
|
|
var center = this.center_
|
|
var coord = this.coord_
|
|
|
|
// Specific case: circle
|
|
var d, dmax, r, circle, centerPx
|
|
if (!this.sides_ && this.square_ && !this.centered_) {
|
|
center = [(coord[0] + center[0]) / 2, (coord[1] + center[1]) / 2]
|
|
d = [coord[0] - center[0], coord[1] - center[1]]
|
|
r = Math.sqrt(d[0] * d[0] + d[1] * d[1])
|
|
circle = new ol_geom_Circle(center, r, 'XY')
|
|
// Optimize points on the circle
|
|
centerPx = this.getMap().getPixelFromCoordinate(center)
|
|
dmax = Math.max(100, Math.abs(centerPx[0] - this.coordPx_[0]), Math.abs(centerPx[1] - this.coordPx_[1]))
|
|
dmax = Math.min(this.maxCircleCoordinates_, Math.round(dmax / 3))
|
|
return ol_geom_Polygon_fromCircle(circle, dmax, 0)
|
|
} else {
|
|
var hasrotation = this.canRotate_ && this.centered_ && this.square_
|
|
d = [coord[0] - center[0], coord[1] - center[1]]
|
|
if (this.square_ && !hasrotation) {
|
|
//var d = [coord[0] - center[0], coord[1] - center[1]];
|
|
var dm = Math.max(Math.abs(d[0]), Math.abs(d[1]))
|
|
coord = [
|
|
center[0] + (d[0] > 0 ? dm : -dm),
|
|
center[1] + (d[1] > 0 ? dm : -dm)
|
|
]
|
|
}
|
|
r = Math.sqrt(d[0] * d[0] + d[1] * d[1])
|
|
if (r > 0) {
|
|
circle = new ol_geom_Circle(center, r, 'XY')
|
|
var a
|
|
if (hasrotation)
|
|
a = Math.atan2(d[1], d[0])
|
|
else
|
|
a = this.startAngle[this.sides_] || this.startAngle['default']
|
|
|
|
if (this.sides_) {
|
|
g = ol_geom_Polygon_fromCircle(circle, this.sides_, a)
|
|
} else {
|
|
// Optimize points on the circle
|
|
centerPx = this.getMap().getPixelFromCoordinate(this.center_)
|
|
dmax = Math.max(100, Math.abs(centerPx[0] - this.coordPx_[0]), Math.abs(centerPx[1] - this.coordPx_[1]))
|
|
dmax = Math.min(this.maxCircleCoordinates_, Math.round(dmax / (this.centered_ ? 3 : 5)))
|
|
g = ol_geom_Polygon_fromCircle(circle, dmax, 0)
|
|
}
|
|
|
|
if (hasrotation)
|
|
return g
|
|
|
|
// Scale polygon to fit extent
|
|
var ext = g.getExtent()
|
|
if (!this.centered_)
|
|
center = this.center_
|
|
else
|
|
center = [2 * this.center_[0] - this.coord_[0], 2 * this.center_[1] - this.coord_[1]]
|
|
var scx = (center[0] - coord[0]) / (ext[0] - ext[2])
|
|
var scy = (center[1] - coord[1]) / (ext[1] - ext[3])
|
|
if (this.square_) {
|
|
var sc = Math.min(Math.abs(scx), Math.abs(scy))
|
|
scx = Math.sign(scx) * sc
|
|
scy = Math.sign(scy) * sc
|
|
}
|
|
var t = [center[0] - ext[0] * scx, center[1] - ext[1] * scy]
|
|
|
|
g.applyTransform(function (g1, g2, dim) {
|
|
for (var i = 0; i < g1.length; i += dim) {
|
|
g2[i] = g1[i] * scx + t[0]
|
|
g2[i + 1] = g1[i + 1] * scy + t[1]
|
|
}
|
|
return g2
|
|
})
|
|
return g
|
|
}
|
|
}
|
|
}
|
|
|
|
// No geom => return a point
|
|
return new ol_geom_Point(this.center_)
|
|
}
|
|
/** Draw sketch
|
|
* @return {ol.Feature} The feature being drawn.
|
|
*/
|
|
drawSketch_(evt) {
|
|
this.overlayLayer_.getSource().clear()
|
|
if (evt) {
|
|
this.square_ = this.squareFn_ ? this.squareFn_(evt) : evt.originalEvent.shiftKey
|
|
this.centered_ = this.centeredFn_ ? this.centeredFn_(evt) : evt.originalEvent.metaKey || evt.originalEvent.ctrlKey
|
|
var g = this.getGeom_()
|
|
if (g) {
|
|
var f = this.feature_
|
|
|
|
//f.setGeometry (g);
|
|
if (g.getType() === 'Polygon')
|
|
f.getGeometry().setCoordinates(g.getCoordinates())
|
|
this.overlayLayer_.getSource().addFeature(f)
|
|
if (this.coord_
|
|
&& this.square_
|
|
&& ((this.canRotate_ && this.centered_ && this.coord_) || (!this.sides_ && !this.centered_))) {
|
|
this.overlayLayer_.getSource().addFeature(new ol_Feature(new ol_geom_LineString([this.center_, this.coord_])))
|
|
}
|
|
return f
|
|
}
|
|
}
|
|
}
|
|
/** Draw sketch (Point)
|
|
*/
|
|
drawPoint_(pt, noclear) {
|
|
if (!noclear) {
|
|
this.overlayLayer_.getSource().clear()
|
|
}
|
|
this.overlayLayer_.getSource().addFeature(new ol_Feature(new ol_geom_Point(pt)))
|
|
}
|
|
/**
|
|
* @param {ol.MapBrowserEvent} evt Map browser event.
|
|
*/
|
|
handleEvent_(evt) {
|
|
var dx, dy
|
|
// Event date time
|
|
this._eventTime = new Date();
|
|
switch (evt.type) {
|
|
case "pointerdown": {
|
|
if (this.conditionFn_ && !this.conditionFn_(evt)) break
|
|
this.downPx_ = evt.pixel
|
|
this.start_(evt)
|
|
// Test long touch
|
|
var dt = 500
|
|
this._longTouch = false
|
|
setTimeout(function () {
|
|
this._longTouch = (new Date() - this._eventTime > .9 * dt)
|
|
if (this._longTouch)
|
|
this.handleMoveEvent_(evt)
|
|
}.bind(this), dt)
|
|
this.lastEvent = evt.type;
|
|
break
|
|
}
|
|
case "pointerup": {
|
|
// Started and fisrt move
|
|
if (this.started_ && this.coord_) {
|
|
dx = this.downPx_[0] - evt.pixel[0]
|
|
dy = this.downPx_[1] - evt.pixel[1]
|
|
|
|
if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
|
|
// The pointer has moved
|
|
if (this.lastEvent == "pointerdown" || this.lastEvent == "pointermove" || this.lastEvent == "keydown") {
|
|
this.end_(evt)
|
|
}
|
|
|
|
// On touch device there is no move event : terminate = click on the same point
|
|
else {
|
|
dx = this.upPx_[0] - evt.pixel[0]
|
|
dy = this.upPx_[1] - evt.pixel[1]
|
|
if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
|
|
this.end_(evt)
|
|
} else {
|
|
this.handleMoveEvent_(evt)
|
|
this.drawPoint_(evt.coordinate, true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
this.upPx_ = evt.pixel
|
|
break
|
|
}
|
|
case "pointerdrag": {
|
|
if (this.started_) {
|
|
var centerPx = this.getMap().getPixelFromCoordinate(this.center_)
|
|
dx = centerPx[0] - evt.pixel[0]
|
|
dy = centerPx[1] - evt.pixel[1]
|
|
if (dx * dx + dy * dy <= this.squaredClickTolerance_) {
|
|
this.reset()
|
|
}
|
|
}
|
|
return !this._longTouch
|
|
// break;
|
|
}
|
|
case "pointermove": {
|
|
if (this.started_) {
|
|
dx = this.downPx_[0] - evt.pixel[0]
|
|
dy = this.downPx_[1] - evt.pixel[1]
|
|
if (dx * dx + dy * dy > this.squaredClickTolerance_) {
|
|
this.handleMoveEvent_(evt)
|
|
this.lastEvent = evt.type
|
|
}
|
|
} else {
|
|
this.drawPoint_(evt.coordinate)
|
|
}
|
|
break
|
|
}
|
|
default: {
|
|
// Prevent zoom or other event on click/singleclick/dblclick
|
|
if (this.started_ && (evt.type === 'click' || evt.type === 'singleclick' || evt.type === 'dblclick')) {
|
|
//evt.stopPropagation();
|
|
return false
|
|
}
|
|
this.lastEvent = evt.type
|
|
|
|
break
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
/** Stop drawing.
|
|
*/
|
|
finishDrawing() {
|
|
if (this.started_ && this.coord_) {
|
|
this.end_({ pixel: this.upPx_, coordinate: this.coord_ })
|
|
}
|
|
}
|
|
/**
|
|
* @param {ol.MapBrowserEvent} evt Event.
|
|
*/
|
|
handleMoveEvent_(evt) {
|
|
if (this.started_) {
|
|
this.coord_ = evt.coordinate
|
|
this.coordPx_ = evt.pixel
|
|
var f = this.drawSketch_(evt)
|
|
this.dispatchEvent({
|
|
type: 'drawing',
|
|
feature: f,
|
|
pixel: evt.pixel,
|
|
startCoordinate: this.center_,
|
|
coordinate: evt.coordinate,
|
|
square: this.square_,
|
|
centered: this.centered_
|
|
})
|
|
} else {
|
|
this.drawPoint_(evt.coordinate)
|
|
}
|
|
}
|
|
/** Start an new draw
|
|
* @param {ol.MapBrowserEvent} evt Map browser event.
|
|
* @return {boolean} `false` to stop the drag sequence.
|
|
*/
|
|
start_(evt) {
|
|
if (!this.started_) {
|
|
this.started_ = true
|
|
this.center_ = evt.coordinate
|
|
this.coord_ = null
|
|
var f = this.feature_ = new ol_Feature({})
|
|
f.setGeometryName(this.geometryName_ || 'geometry')
|
|
f.setGeometry(new ol_geom_Polygon([[evt.coordinate, evt.coordinate, evt.coordinate]]))
|
|
this.drawSketch_(evt)
|
|
this.dispatchEvent({ type: 'drawstart', feature: f, pixel: evt.pixel, coordinate: evt.coordinate })
|
|
} else {
|
|
this.coord_ = evt.coordinate
|
|
}
|
|
}
|
|
/** End drawing
|
|
* @param {ol.MapBrowserEvent} evt Map browser event.
|
|
* @return {boolean} `false` to stop the drag sequence.
|
|
*/
|
|
end_(evt) {
|
|
this.coord_ = evt.coordinate
|
|
this.started_ = false
|
|
if (this.coord_ && (this.center_[0] !== this.coord_[0] || this.center_[1] !== this.coord_[1])) {
|
|
var f = this.feature_
|
|
|
|
f.setGeometry(this.getGeom_())
|
|
if (this.source_)
|
|
this.source_.addFeature(f)
|
|
else if (this.features_)
|
|
this.features_.push(f)
|
|
this.dispatchEvent({ type: 'drawend', feature: f, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ })
|
|
} else {
|
|
this.dispatchEvent({ type: 'drawcancel', feature: null, pixel: evt.pixel, coordinate: evt.coordinate, square: this.square_, centered: this.centered_ })
|
|
}
|
|
|
|
this.center_ = this.coord_ = null
|
|
this.drawSketch_()
|
|
}
|
|
}
|
|
|
|
/** Default start angle array for each sides
|
|
*/
|
|
ol_interaction_DrawRegular.prototype.startAngle = {
|
|
'default':Math.PI/2,
|
|
3: -Math.PI/2,
|
|
4: Math.PI/4
|
|
};
|
|
|
|
export default ol_interaction_DrawRegular
|