/* 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