314 lines
10 KiB
JavaScript
314 lines
10 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 from 'ol/style/Style.js'
|
|
import ol_style_Stroke from 'ol/style/Stroke.js'
|
|
import ol_source_Vector from 'ol/source/Vector.js'
|
|
import ol_style_Fill from 'ol/style/Fill.js'
|
|
import ol_style_Circle from 'ol/style/Circle.js'
|
|
import ol_layer_Vector from 'ol/layer/Vector.js'
|
|
import ol_geom_Point from 'ol/geom/Point.js'
|
|
import ol_Feature from 'ol/Feature.js'
|
|
import ol_geom_LineString from 'ol/geom/LineString.js'
|
|
import {ol_coordinate_dist2d} from "../geom/GeomUtils.js";
|
|
import '../geom/LineStringSplitAt.js'
|
|
import ol_ext_element from '../util/element.js'
|
|
|
|
/** Interaction split interaction for splitting feature geometry
|
|
* @constructor
|
|
* @extends {ol_interaction_Interaction}
|
|
* @fires beforesplit, aftersplit, pointermove
|
|
* @param {*}
|
|
* @param {ol.source.Vector|Array<ol.source.Vector>} [options.sources] a list of source to split (configured with useSpatialIndex set to true), if none use map visible layers.
|
|
* @param {ol.Collection.<ol.Feature>} options.features collection of feature to split (instead of a list of sources)
|
|
* @param {integer} options.snapDistance distance (in px) to snap to an object, default 25px
|
|
* @param {string|undefined} options.cursor cursor name to display when hovering an objet
|
|
* @param {function|undefined} options.filter a filter that takes a feature and return true if it can be clipped, default always split.
|
|
* @param ol_style_Style | Array<ol_style_Style> | false | undefined} options.featureStyle Style for the selected features, choose false if you don't want feature selection. By default the default edit style is used.
|
|
* @param {ol_style_Style | Array<ol_style_Style> | undefined} options.sketchStyle Style for the sektch features.
|
|
* @param {function|undefined} options.tolerance Distance between the calculated intersection and a vertex on the source geometry below which the existing vertex will be used for the split. Default is 1e-10.
|
|
*/
|
|
var ol_interaction_Split = class olinteractionSplit extends ol_interaction_Interaction {
|
|
constructor(options) {
|
|
if (!options)
|
|
options = {}
|
|
|
|
super({
|
|
handleEvent: function (e) {
|
|
switch (e.type) {
|
|
case "singleclick":
|
|
return this.handleDownEvent(e)
|
|
case "pointermove":
|
|
return this.handleMoveEvent(e)
|
|
default:
|
|
return true
|
|
}
|
|
//return true;
|
|
}
|
|
})
|
|
|
|
// Snap distance (in px)
|
|
this.snapDistance_ = options.snapDistance || 25
|
|
// Split tolerance between the calculated intersection and the geometry
|
|
this.tolerance_ = options.tolerance || 1e-10
|
|
// Cursor
|
|
this.cursor_ = options.cursor
|
|
|
|
// List of source to split
|
|
this.setSources(options.sources)
|
|
|
|
if (options.features) {
|
|
if (!this.sources_) this.sources_ = [];
|
|
this.sources_.push(new ol_source_Vector({ features: options.features }))
|
|
}
|
|
|
|
// Get all features candidate
|
|
this.filterSplit_ = options.filter || function () { return true }
|
|
|
|
// Default style
|
|
var white = [255, 255, 255, 1]
|
|
var blue = [0, 153, 255, 1]
|
|
var width = 3
|
|
var fill = new ol_style_Fill({ color: 'rgba(255,255,255,0.4)' })
|
|
var stroke = new ol_style_Stroke({
|
|
color: '#3399CC',
|
|
width: 1.25
|
|
})
|
|
var sketchStyle = [
|
|
new ol_style_Style({
|
|
image: new ol_style_Circle({
|
|
fill: fill,
|
|
stroke: stroke,
|
|
radius: 5
|
|
}),
|
|
fill: fill,
|
|
stroke: stroke
|
|
})
|
|
]
|
|
var featureStyle = [
|
|
new ol_style_Style({
|
|
stroke: new ol_style_Stroke({
|
|
color: white,
|
|
width: width + 2
|
|
})
|
|
}),
|
|
new ol_style_Style({
|
|
image: new ol_style_Circle({
|
|
radius: 2 * width,
|
|
fill: new ol_style_Fill({
|
|
color: blue
|
|
}),
|
|
stroke: new ol_style_Stroke({
|
|
color: white,
|
|
width: width / 2
|
|
})
|
|
}),
|
|
stroke: new ol_style_Stroke({
|
|
color: blue,
|
|
width: width
|
|
})
|
|
}),
|
|
]
|
|
|
|
// Custom style
|
|
if (options.sketchStyle)
|
|
sketchStyle = options.sketchStyle instanceof Array ? options.sketchStyle : [options.sketchStyle]
|
|
if (options.featureStyle)
|
|
featureStyle = options.featureStyle instanceof Array ? options.featureStyle : [options.featureStyle]
|
|
|
|
// Create a new overlay for the sketch
|
|
this.overlayLayer_ = new ol_layer_Vector({
|
|
source: new ol_source_Vector({
|
|
useSpatialIndex: false
|
|
}),
|
|
name: 'Split overlay',
|
|
displayInLayerSwitcher: false,
|
|
style: function (f) {
|
|
if (f._sketch_)
|
|
return sketchStyle
|
|
else
|
|
return featureStyle
|
|
}
|
|
})
|
|
|
|
}
|
|
/**
|
|
* 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)
|
|
}
|
|
/** Get sources to split features in
|
|
* @return {Array<ol.source.Vector>}
|
|
*/
|
|
getSources() {
|
|
if (!this.sources_ && this.getMap()) {
|
|
var sources = []
|
|
var getSources = function (layers) {
|
|
layers.forEach(function (layer) {
|
|
if (layer.getVisible()) {
|
|
if (layer.getSource && layer.getSource() instanceof ol_source_Vector) {
|
|
sources.unshift(layer.getSource())
|
|
} else if (layer.getLayers) {
|
|
getSources(layer.getLayers())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
getSources(this.getMap().getLayers())
|
|
return sources
|
|
}
|
|
return this.sources_ || []
|
|
}
|
|
/** Set sources to split features in
|
|
* @param {ol.source.Vector|Array<ol.source.Vector>|boolean} [sources] if not defined get all map vector sources
|
|
*/
|
|
setSources(sources) {
|
|
this.sources_ = sources ? (sources instanceof Array ? sources || false : [sources]) : false
|
|
}
|
|
/** Get closest feature at pixel
|
|
* @param {ol.Pixel}
|
|
* @return {ol.feature}
|
|
* @private
|
|
*/
|
|
getClosestFeature(e) {
|
|
var source, f, c, g, d = this.snapDistance_ + 1
|
|
// Look for closest point in the sources
|
|
this.getSources().forEach(function (si) {
|
|
var fi = si.getClosestFeatureToCoordinate(e.coordinate)
|
|
if (fi && fi.getGeometry().splitAt) {
|
|
var ci = fi.getGeometry().getClosestPoint(e.coordinate)
|
|
var gi = new ol_geom_LineString([e.coordinate, ci])
|
|
var di = gi.getLength() / e.frameState.viewState.resolution
|
|
if (di < d) {
|
|
source = si
|
|
d = di
|
|
f = fi
|
|
g = gi
|
|
c = ci
|
|
}
|
|
}
|
|
})
|
|
// Snap ?
|
|
if (d > this.snapDistance_) {
|
|
return false
|
|
} else {
|
|
// Snap to node
|
|
var coord = this.getNearestCoord(c, f.getGeometry().getCoordinates())
|
|
var p = this.getMap().getPixelFromCoordinate(coord)
|
|
if (ol_coordinate_dist2d(e.pixel, p) < this.snapDistance_) {
|
|
c = coord
|
|
}
|
|
//
|
|
return { source: source, feature: f, coord: c, link: g }
|
|
}
|
|
}
|
|
/** Get nearest coordinate in a list
|
|
* @param {ol.coordinate} pt the point to find nearest
|
|
* @param {Array<ol.coordinate>} coords list of coordinates
|
|
* @return {ol.coordinate} the nearest coordinate in the list
|
|
*/
|
|
getNearestCoord(pt, coords) {
|
|
var d, dm = Number.MAX_VALUE, p0
|
|
for (var i = 0; i < coords.length; i++) {
|
|
d = ol_coordinate_dist2d(pt, coords[i])
|
|
if (d < dm) {
|
|
dm = d
|
|
p0 = coords[i]
|
|
}
|
|
}
|
|
return p0
|
|
}
|
|
/**
|
|
* @param {ol.MapBrowserEvent} evt Map browser event.
|
|
* @return {boolean} `true` to start the drag sequence.
|
|
*/
|
|
handleDownEvent(evt) {
|
|
// Something to split ?
|
|
var current = this.getClosestFeature(evt)
|
|
|
|
if (current) {
|
|
var self = this
|
|
self.overlayLayer_.getSource().clear()
|
|
var split = current.feature.getGeometry().splitAt(current.coord, this.tolerance_)
|
|
var i
|
|
if (split.length > 1) {
|
|
var tosplit = []
|
|
for (i = 0; i < split.length; i++) {
|
|
var f = current.feature.clone()
|
|
f.setGeometry(split[i])
|
|
tosplit.push(f)
|
|
}
|
|
self.dispatchEvent({ type: 'beforesplit', original: current.feature, features: tosplit })
|
|
current.source.dispatchEvent({ type: 'beforesplit', original: current.feature, features: tosplit })
|
|
current.source.removeFeature(current.feature)
|
|
for (i = 0; i < tosplit.length; i++) {
|
|
current.source.addFeature(tosplit[i])
|
|
}
|
|
self.dispatchEvent({ type: 'aftersplit', original: current.feature, features: tosplit })
|
|
current.source.dispatchEvent({ type: 'aftersplit', original: current.feature, features: tosplit })
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
/**
|
|
* @param {ol.MapBrowserEvent} evt Event.
|
|
*/
|
|
handleMoveEvent(e) {
|
|
var map = e.map
|
|
this.overlayLayer_.getSource().clear()
|
|
var current = this.getClosestFeature(e)
|
|
|
|
if (current && this.filterSplit_(current.feature)) {
|
|
var p, l
|
|
// Draw sketch
|
|
this.overlayLayer_.getSource().addFeature(current.feature)
|
|
p = new ol_Feature(new ol_geom_Point(current.coord))
|
|
p._sketch_ = true
|
|
this.overlayLayer_.getSource().addFeature(p)
|
|
//
|
|
l = new ol_Feature(current.link)
|
|
l._sketch_ = true
|
|
this.overlayLayer_.getSource().addFeature(l)
|
|
// move event
|
|
this.dispatchEvent({
|
|
type: 'pointermove',
|
|
coordinate: e.coordinate,
|
|
frameState: e.frameState,
|
|
originalEvent: e.originalEvent,
|
|
map: e.map,
|
|
pixel: e.pixel,
|
|
feature: current.feature,
|
|
linkGeometry: current.link
|
|
})
|
|
} else {
|
|
this.dispatchEvent(e)
|
|
}
|
|
|
|
var element = map.getTargetElement()
|
|
if (this.cursor_) {
|
|
if (current) {
|
|
if (element.style.cursor != this.cursor_) {
|
|
this.previousCursor_ = element.style.cursor
|
|
ol_ext_element.setCursor(element, this.cursor_)
|
|
}
|
|
} else if (this.previousCursor_ !== undefined) {
|
|
ol_ext_element.setCursor(element, this.previousCursor_)
|
|
this.previousCursor_ = undefined
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default ol_interaction_Split
|