mirror of
https://github.com/Viglino/ol-ext.git
synced 2025-12-08 19:26:29 +00:00
255 lines
7.8 KiB
JavaScript
255 lines
7.8 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 from 'ol'
|
|
import ol_interaction_Interaction from 'ol/interaction/interaction'
|
|
import ol_source_Vector from 'ol/source/vector'
|
|
import ol_Collection from 'ol/collection'
|
|
import ol_extent from 'ol/extent'
|
|
|
|
/** Interaction splitter: acts as a split feature agent while editing vector features (LineString).
|
|
* @constructor
|
|
* @extends {ol_interaction_Interaction}
|
|
* @fires beforesplit, aftersplit
|
|
* @param {olx.interaction.SplitOptions}
|
|
* - source {ol.source.Vector|Array{ol.source.Vector}} The target source (or array of source) with features to be split (configured with useSpatialIndex set to true)
|
|
* - triggerSource {ol.source.Vector} Any newly created or modified features from this source will be used to split features on the target source. If none is provided the target source is used instead.
|
|
* - features {ol_Collection.<ol.Feature>} A collection of feature to be split (replace source target).
|
|
* - triggerFeatures {ol_Collection.<ol.Feature>} Any newly created or modified features from this collection will be used to split features on the target source (replace triggerSource).
|
|
* - filter {function|undefined} a filter that takes a feature and return true if the feature is eligible for splitting, default always split.
|
|
* - tolerance {function|undefined} 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.
|
|
* @todo verify auto intersection on features that split.
|
|
*/
|
|
var ol_interaction_Splitter = function(options)
|
|
{ if (!options) options = {};
|
|
|
|
ol_interaction_Interaction.call(this,
|
|
{ handleEvent: function(e)
|
|
{ // Hack to get only one changeFeature when draging with ol.interaction.Modify on.
|
|
if (e.type != "pointermove" && e.type != "pointerdrag")
|
|
{ if (this.lastEvent_)
|
|
{ this.splitSource(this.lastEvent_.feature);
|
|
this.lastEvent_ = null;
|
|
}
|
|
this.moving_ = false;
|
|
}
|
|
else this.moving_ = true;
|
|
return true;
|
|
},
|
|
});
|
|
|
|
// Features added / remove
|
|
this.added_ = [];
|
|
this.removed_ = [];
|
|
|
|
// Source to split
|
|
if (options.features)
|
|
{ this.source_ = new ol_source_Vector({ features: options.features });
|
|
}
|
|
else
|
|
{ this.source_ = options.source ? options.source : new ol_source_Vector({ features: new ol_Collection() });
|
|
}
|
|
var trigger = this.triggerSource;
|
|
if (options.triggerFeatures)
|
|
{ trigger = new ol_source_Vector({ features: options.triggerFeatures });
|
|
}
|
|
|
|
if (trigger)
|
|
{ trigger.on("addfeature", this.onAddFeature, this);
|
|
trigger.on("changefeature", this.onChangeFeature, this);
|
|
trigger.on("removefeature", this.onRemoveFeature, this);
|
|
}
|
|
else
|
|
{ this.source_.on("addfeature", this.onAddFeature, this);
|
|
this.source_.on("changefeature", this.onChangeFeature, this);
|
|
this.source_.on("removefeature", this.onRemoveFeature, this);
|
|
}
|
|
|
|
// Split tolerance between the calculated intersection and the geometry
|
|
this.tolerance_ = options.tolerance || 1e-10;
|
|
|
|
// Get all features candidate
|
|
this.filterSplit_ = options.filter || function(){ return true; };
|
|
};
|
|
ol.inherits(ol_interaction_Splitter, ol_interaction_Interaction);
|
|
|
|
/** Calculate intersection on 2 segs
|
|
* @param {Array<_ol_coordinate_>} s1 first seg to intersect (2 points)
|
|
* @param {Array<_ol_coordinate_>} s2 second seg to intersect (2 points)
|
|
* @return { boolean | _ol_coordinate_ } intersection point or false no intersection
|
|
*/
|
|
ol_interaction_Splitter.prototype.intersectSegs = function(s1,s2)
|
|
{ var tol = this.tolerance_;
|
|
|
|
// Solve
|
|
var x12 = s1[0][0] - s1[1][0];
|
|
var x34 = s2[0][0] - s2[1][0];
|
|
var y12 = s1[0][1] - s1[1][1];
|
|
var y34 = s2[0][1] - s2[1][1];
|
|
|
|
var det = x12 * y34 - y12 * x34;
|
|
// No intersection
|
|
if (Math.abs(det) < tol)
|
|
{ return false;
|
|
}
|
|
else
|
|
{ // Outside segement
|
|
var r1 = ((s1[0][0] - s2[1][0])*y34 - (s1[0][1] - s2[1][1])*x34) / det;
|
|
if (Math.abs(r1)<tol) return s1[0];
|
|
if (Math.abs(1-r1)<tol) return s1[1];
|
|
if (r1<0 || r1>1) return false;
|
|
|
|
var r2 = ((s1[0][1] - s2[1][1])*x12 - (s1[0][0] - s2[1][0])*y12) / det;
|
|
if (Math.abs(r2)<tol) return s2[1];
|
|
if (Math.abs(1-r2)<tol) return s2[0];
|
|
if (r2<0 || r2>1) return false;
|
|
|
|
// Intersection
|
|
var a = s1[0][0] * s1[1][1] - s1[0][1] * s1[1][0];
|
|
var b = s2[0][0] * s2[1][1] - s2[0][1] * s2[1][0];
|
|
var p = [(a * x34 - b * x12) / det, (a * y34 - b * y12) / det];
|
|
// Test start / end
|
|
/*
|
|
console.log("r1: "+r1)
|
|
console.log("r2: "+r2)
|
|
console.log ("s10: "+(_ol_coordinate_.dist2d(p,s1[0])<tol)) ;
|
|
console.log ("s11: "+(_ol_coordinate_.dist2d(p,s1[1])<tol)) ;
|
|
console.log ("s20: "+(_ol_coordinate_.dist2d(p,s2[0])<tol)) ;
|
|
console.log ("s21: "+(_ol_coordinate_.dist2d(p,s2[1])<tol)) ;
|
|
*/
|
|
return p;
|
|
}
|
|
};
|
|
|
|
/** Split the source using a feature
|
|
* @param {ol.Feature} feature The feature to use to split.
|
|
*/
|
|
ol_interaction_Splitter.prototype.splitSource = function(feature)
|
|
{ // Allready perform a split
|
|
if (this.splitting) return;
|
|
var self = this;
|
|
var i, k, f2;
|
|
// Start splitting
|
|
this.source_.dispatchEvent({ type:'beforesplit', feaure: feature, source: this.source_ });
|
|
|
|
this.splitting = true;
|
|
this.added_ = [];
|
|
this.removed_ = [];
|
|
|
|
var c = feature.getGeometry().getCoordinates();
|
|
var seg, split = [];
|
|
function intersect (f)
|
|
{ if (f !== feature)
|
|
{ var c2 = f.getGeometry().getCoordinates();
|
|
for (var j=0; j<c2.length-1; j++)
|
|
{ var p = this.intersectSegs (seg, [c2[j],c2[j+1]]);
|
|
if (p)
|
|
{ split.push(p);
|
|
g = f.getGeometry().splitAt(p, this.tolerance_);
|
|
if (g && g.length>1)
|
|
{ found = f;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Split existing features
|
|
for (i=0; i<c.length-1; i++)
|
|
{ seg = [c[i],c[i+1]];
|
|
var extent = ol_extent.buffer(ol_extent.boundingExtent(seg), this.tolerance_ /*0.01*/ );
|
|
var g;
|
|
while (true)
|
|
{ var found = false;
|
|
this.source_.forEachFeatureIntersectingExtent(extent, intersect, this);
|
|
// Split feature
|
|
if (found)
|
|
{ var f = found;
|
|
this.source_.removeFeature(f);
|
|
for (k=0; k<g.length; k++)
|
|
{ f2 = f.clone();
|
|
f2.setGeometry(g[k]);
|
|
this.source_.addFeature(f2);
|
|
}
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
// Auto intersect
|
|
for (i=0; i<c.length-2; i++)
|
|
{ for (var j=i+1; j<c.length-1; j++)
|
|
{ var p = this.intersectSegs ([c[i],c[i+1]], [c[j],c[j+1]]);
|
|
if (p && p!=c[i+1])
|
|
{ split.push(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Split original
|
|
var splitOriginal = false;
|
|
if (split.length)
|
|
{ var result = feature.getGeometry().splitAt(split, this.tolerance_);
|
|
if (result.length>1)
|
|
{ for (k=0; k<result.length; k++)
|
|
{ f2 = feature.clone();
|
|
f2.setGeometry(result[k]);
|
|
this.source_.addFeature(f2);
|
|
}
|
|
splitOriginal = true;
|
|
}
|
|
}
|
|
|
|
// If the interaction is inserted after modify interaction, the objet is not consistant
|
|
// > wait end of other interactions
|
|
setTimeout (function()
|
|
{ if (splitOriginal) self.source_.removeFeature(feature);
|
|
self.source_.dispatchEvent({ type:'aftersplit', featureAdded: self.added_, featureRemoved: self.removed_, source: this.source_ });
|
|
// Finish
|
|
self.splitting = false;
|
|
},0);
|
|
|
|
};
|
|
|
|
/** New feature source is added
|
|
*/
|
|
ol_interaction_Splitter.prototype.onAddFeature = function(e)
|
|
{ this.splitSource(e.feature);
|
|
if (this.splitting)
|
|
{ this.added_.push(e.feature);
|
|
}
|
|
/*
|
|
if (this.splitting) return;
|
|
var self = this;
|
|
setTimeout (function() { self.splitSource(e.feature); }, 0);
|
|
*/
|
|
};
|
|
|
|
/** Feature source is removed > count features added/removed
|
|
*/
|
|
ol_interaction_Splitter.prototype.onRemoveFeature = function(e)
|
|
{ if (this.splitting)
|
|
{ var n = this.added_.indexOf(e.feature);
|
|
if (n==-1)
|
|
{ this.removed_.push(e.feature);
|
|
}
|
|
else
|
|
{ this.added_.splice(n,1);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Feature source is changing
|
|
*/
|
|
ol_interaction_Splitter.prototype.onChangeFeature = function(e)
|
|
{ if (this.moving_)
|
|
{ this.lastEvent_ = e;
|
|
}
|
|
else this.splitSource(e.feature);
|
|
};
|
|
|
|
export default ol_interaction_Splitter
|