/* 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_Pointer from 'ol/interaction/Pointer.js'
import ol_geom_LineString from 'ol/geom/LineString.js'
import ol_geom_Polygon from 'ol/geom/Polygon.js'
import {ol_coordinate_dist2d, ol_coordinate_findSegment, ol_coordinate_offsetCoords} from "../geom/GeomUtils.js";
import ol_style_Style_defaultStyle from '../style/defaultStyle.js'
import ol_ext_element from '../util/element.js';
/** Offset interaction for offseting feature geometry
* @constructor
* @extends {ol_interaction_Pointer}
* @fires offsetstart
* @fires offsetting
* @fires offsetend
* @param {any} options
* @param {function} [options.filter] a function that takes a feature and a layer and return true if the feature can be modified
* @param {ol.layer.Vector | Array
} options.layers list of feature to transform
* @param {ol.Collection.} options.features collection of feature to transform
* @param {ol.source.Vector | undefined} options.source source to duplicate feature when ctrl key is down
* @param {boolean} options.duplicate force feature to duplicate (source must be set)
* @param {ol.style.Style | Array. | ol.style.StyleFunction | undefined} style style for the sketch
*/
var ol_interaction_Offset = class olinteractionOffset extends ol_interaction_Pointer {
constructor(options) {
options = options || {};
// Extend pointer
super({
handleDownEvent: function(e) { return self.handleDownEvent_(e) },
handleDragEvent: function(e) { return self.handleDragEvent_(e) },
handleMoveEvent: function(e) { return self.handleMoveEvent_(e) },
handleUpEvent: function(e) { return self.handleUpEvent_(e) },
});
var self = this;
this._filter = options.filter;
// Collection of feature to transform
this.features_ = options.features;
// List of layers to transform
this.layers_ = options.layers ? (options.layers instanceof Array) ? options.layers : [options.layers] : null;
// duplicate
this.set('duplicate', options.duplicate);
this.source_ = options.source;
// Style
this._style = (typeof (options.style) === 'function') ? options.style : function () {
if (options.style)
return options.style;
else
return ol_style_Style_defaultStyle(true);
};
// init
this.previousCursor_ = false;
}
/** Get Feature at pixel
* @param {ol.MapBrowserEvent} evt Map browser event.
* @return {any} a feature and the hit point
* @private
*/
getFeatureAtPixel_(e) {
var self = this;
return this.getMap().forEachFeatureAtPixel(e.pixel,
function (feature, layer) {
var current;
if (self._filter && !self._filter(feature, layer))
return false;
// feature belong to a layer
if (self.layers_) {
for (var i = 0; i < self.layers_.length; i++) {
if (self.layers_[i] === layer) {
current = feature;
break;
}
}
}
// feature in the collection
else if (self.features_) {
self.features_.forEach(function (f) {
if (f === feature) {
current = feature;
}
});
}
// Others
else {
current = feature;
}
// Only poygon or linestring
var typeGeom = current.getGeometry().getType();
if (current && /Polygon|LineString/.test(typeGeom)) {
if (typeGeom === 'Polygon' && current.getGeometry().getCoordinates().length > 1)
return false;
// test distance
var p = current.getGeometry().getClosestPoint(e.coordinate);
var dx = p[0] - e.coordinate[0];
var dy = p[1] - e.coordinate[1];
var d = Math.sqrt(dx * dx + dy * dy) / e.frameState.viewState.resolution;
if (d < 5) {
return {
feature: current,
hit: p,
coordinates: current.getGeometry().getCoordinates(),
geom: current.getGeometry().clone(),
geomType: typeGeom
};
} else {
return false;
}
} else {
return false;
}
}, { hitTolerance: 5 });
}
/**
* @param {ol.MapBrowserEvent} e Map browser event.
* @return {boolean} `true` to start the drag sequence.
* @private
*/
handleDownEvent_(e) {
this.current_ = this.getFeatureAtPixel_(e);
if (this.current_) {
this.currentStyle_ = this.current_.feature.getStyle();
if (this.source_ && (this.get('duplicate') || e.originalEvent.ctrlKey)) {
this.current_.feature = this.current_.feature.clone();
this.current_.feature.setStyle(this._style(this.current_.feature));
this.source_.addFeature(this.current_.feature);
} else {
// Modify the current feature
this.current_.feature.setStyle(this._style(this.current_.feature));
this._modifystart = true;
}
this.dispatchEvent({ type: 'offsetstart', feature: this.current_.feature, offset: 0 });
return true;
} else {
return false;
}
}
/**
* @param {ol.MapBrowserEvent} e Map browser event.
* @private
*/
handleDragEvent_(e) {
if (this._modifystart) {
this.dispatchEvent({ type: 'modifystart', features: [this.current_.feature] });
this._modifystart = false;
}
var p = this.current_.geom.getClosestPoint(e.coordinate);
var d = ol_coordinate_dist2d(p, e.coordinate);
var seg, v1, v2, offset;
switch (this.current_.geomType) {
case 'Polygon': {
seg = ol_coordinate_findSegment(p, this.current_.coordinates[0]).segment;
if (seg) {
v1 = [seg[1][0] - seg[0][0], seg[1][1] - seg[0][1]];
v2 = [e.coordinate[0] - p[0], e.coordinate[1] - p[1]];
if (v1[0] * v2[1] - v1[1] * v2[0] > 0) {
d = -d;
}
offset = [];
for (var i = 0; i < this.current_.coordinates.length; i++) {
offset.push(ol_coordinate_offsetCoords(this.current_.coordinates[i], i == 0 ? d : -d));
}
this.current_.feature.setGeometry(new ol_geom_Polygon(offset));
}
break;
}
case 'LineString': {
seg = ol_coordinate_findSegment(p, this.current_.coordinates).segment;
if (seg) {
v1 = [seg[1][0] - seg[0][0], seg[1][1] - seg[0][1]];
v2 = [e.coordinate[0] - p[0], e.coordinate[1] - p[1]];
if (v1[0] * v2[1] - v1[1] * v2[0] > 0) {
d = -d;
}
offset = ol_coordinate_offsetCoords(this.current_.coordinates, d);
this.current_.feature.setGeometry(new ol_geom_LineString(offset));
}
break;
}
default: {
break;
}
}
this.dispatchEvent({ type: 'offsetting', feature: this.current_.feature, offset: d, segment: [p, e.coordinate], coordinate: e.coordinate });
}
/**
* @param {ol.MapBrowserEvent} e Map browser event.
* @private
*/
handleUpEvent_(e) {
if (!this._modifystart) {
this.dispatchEvent({ type: 'offsetend', feature: this.current_.feature, coordinate: e.coordinate });
}
this.current_.feature.setStyle(this.currentStyle_);
this.current_ = false;
}
/**
* @param {ol.MapBrowserEvent} e Event.
* @private
*/
handleMoveEvent_(e) {
var f = this.getFeatureAtPixel_(e);
if (f) {
if (this.previousCursor_ === false) {
this.previousCursor_ = e.map.getTargetElement().style.cursor;
}
ol_ext_element.setCursor(e.map, 'pointer');
} else {
ol_ext_element.setCursor(e.map, this.previousCursor_);
this.previousCursor_ = false;
}
}
}
export default ol_interaction_Offset