/* Copyright (c) 2017 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_geom_Polygon from 'ol/geom/Polygon.js'
import ol_geom_MultiPolygon from 'ol/geom/MultiPolygon.js'
import ol_geom_LinearRing from 'ol/geom/LinearRing.js'
import ol_interaction_Draw from 'ol/interaction/Draw.js'
import ol_interaction_Select from 'ol/interaction/Select.js'
/** Interaction to draw holes in a polygon.
* It fires a drawstart, drawend event when drawing the hole
* and a modifystart, modifyend event before and after inserting the hole in the feature geometry.
* @constructor
* @extends {ol_interaction_Interaction}
* @fires drawstart
* @fires drawend
* @fires modifystart
* @fires modifyend
* @param {olx.interaction.DrawHoleOptions} options extend olx.interaction.DrawOptions
* @param {Array
| function | undefined} options.layers A list of layers from which polygons should be selected. Alternatively, a filter function can be provided. default: all visible layers
* @param {Array | ol.Collection | function | undefined} options.featureFilter An array or a collection of features the interaction applies on or a function that takes a feature and a layer and returns true if the feature is a candidate
* @param { ol.style.Style | Array | StyleFunction | undefined } Style for the selected features, default: default edit style
* @param {function | undefined} options.geometryFunction Draw interaction geometry function to customize the hole
*/
var ol_interaction_DrawHole = class olinteractionDrawHole extends ol_interaction_Draw {
constructor(options) {
options = options || {}
// Geometry function that test points inside the current selection
var _geometryFn = function(coordinates, geometry) {
var coord = coordinates[0].pop()
if (!this.getPolygon() || this.getPolygon().intersectsCoordinate(coord)) {
this.lastOKCoord = [coord[0], coord[1]]
}
coordinates[0].push([this.lastOKCoord[0], this.lastOKCoord[1]])
if (geometry) {
geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])])
} else {
geometry = new ol_geom_Polygon(coordinates)
}
return geometry
}
var geomFn = options.geometryFunction
if (geomFn) {
options.geometryFunction = function (c, g, p) {
g = _geometryFn.bind(this)(c, g)
return geomFn.bind(this)(c, g, p)
}
} else {
options.geometryFunction = _geometryFn
}
// Create draw interaction
options.type = 'Polygon';
super(options)
// Select interaction for the current feature
this._select = new ol_interaction_Select({ style: options.style })
this._select.setActive(false)
// Layer filter function
if (options.layers) {
if (typeof (options.layers) === 'function') {
this.layers_ = options.layers
} else if (options.layers.indexOf) {
this.layers_ = function (l) {
return (options.layers.indexOf(l) >= 0)
}
}
}
// Features to apply on
if (typeof (options.featureFilter) === 'function') {
this._features = options.featureFilter
} else if (options.featureFilter) {
var features = options.featureFilter
this._features = function (f) {
if (features.indexOf) {
return !!features[features.indexOf(f)]
} else {
return !!features.item(features.getArray().indexOf(f))
}
}
} else {
this._features = function () { return true }
}
// Start drawing if inside a feature
this.on('drawstart', this._startDrawing.bind(this))
// End drawing add the hole to the current Polygon
this.on('drawend', this._finishDrawing.bind(this))
}
/**
* 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) {
// Remove previous selection
if (this.getMap()) this.getMap().removeInteraction(this._select)
// Add new one
if (map) map.addInteraction(this._select)
super.setMap.call(this, map)
}
/**
* Activate/deactivate the interaction
* @param {boolean}
* @api stable
*/
setActive(b) {
if (this._select) this._select.getFeatures().clear()
super.setActive.call(this, b)
}
/**
* Remove last point of the feature currently being drawn
* (test if points to remove before).
*/
removeLastPoint() {
if (this._feature && this._feature.getGeometry().getCoordinates()[0].length > 2) {
super.removeLastPoint.call(this)
}
}
/**
* Get the current polygon to hole
* @return {ol.Feature}
*/
getPolygon() {
return this._polygon
// return this._select.getFeatures().item(0).getGeometry();
}
/**
* Get current feature to add a hole and start drawing
* @param {ol_interaction_Draw.Event} e
* @private
*/
_startDrawing(e) {
var map = this.getMap()
this._feature = e.feature
var coord = e.feature.getGeometry().getCoordinates()[0][0]
this._current = null
// Check object under the pointer
map.forEachFeatureAtPixel(
map.getPixelFromCoordinate(coord),
function (feature, layer) {
// Not yet found?
if (!this._current && this._features(feature, layer)) {
var poly = feature.getGeometry()
if (poly.getType() === "Polygon"
&& poly.intersectsCoordinate(coord)) {
this._polygonIndex = false
this._polygon = poly
this._current = feature
} else if (poly.getType() === "MultiPolygon"
&& poly.intersectsCoordinate(coord)) {
for (var i = 0, p; p = poly.getPolygon(i); i++) {
if (p.intersectsCoordinate(coord)) {
this._polygonIndex = i
this._polygon = p
this._current = feature
break
}
}
}
}
}.bind(this), {
layerFilter: this.layers_
}
)
this._select.getFeatures().clear()
if (!this._current) {
this.setActive(false)
this.setActive(true)
} else {
this._select.getFeatures().push(this._current)
}
}
/**
* Stop drawing and add the sketch feature to the target feature.
* @param {ol_interaction_Draw.Event} e
* @private
*/
_finishDrawing(e) {
// The feature is the hole
e.hole = e.feature
// Get the current feature
e.feature = this._select.getFeatures().item(0)
this.dispatchEvent({ type: 'modifystart', features: [this._current] })
// Create the hole
var c = e.hole.getGeometry().getCoordinates()[0]
if (c.length > 3) {
if (this._polygonIndex !== false) {
var geom = e.feature.getGeometry()
var newGeom = new ol_geom_MultiPolygon([])
for (var i = 0, pi; pi = geom.getPolygon(i); i++) {
if (i === this._polygonIndex) {
pi.appendLinearRing(new ol_geom_LinearRing(c))
newGeom.appendPolygon(pi)
} else {
newGeom.appendPolygon(pi)
}
}
e.feature.setGeometry(newGeom)
} else {
this.getPolygon().appendLinearRing(new ol_geom_LinearRing(c))
}
}
this.dispatchEvent({ type: 'modifyend', features: [this._current] })
// reset
this._feature = null
this._select.getFeatures().clear()
}
/**
* Function that is called when a geometry's coordinates are updated.
* @param {Array} coordinates
* @param {ol_geom_Polygon} geometry
* @return {ol_geom_Polygon}
* @private
*/
_geometryFn(coordinates, geometry) {
var coord = coordinates[0].pop()
if (!this.getPolygon() || this.getPolygon().intersectsCoordinate(coord)) {
this.lastOKCoord = [coord[0], coord[1]]
}
coordinates[0].push([this.lastOKCoord[0], this.lastOKCoord[1]])
if (geometry) {
geometry.setCoordinates([coordinates[0].concat([coordinates[0][0]])])
} else {
geometry = new ol_geom_Polygon(coordinates)
}
return geometry
}
}
export default ol_interaction_DrawHole