Split.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Control.js
  7. * @requires OpenLayers/Handler/Path.js
  8. * @requires OpenLayers/Layer/Vector.js
  9. */
  10. /**
  11. * Class: OpenLayers.Control.Split
  12. * Acts as a split feature agent while editing vector features.
  13. *
  14. * Inherits from:
  15. * - <OpenLayers.Control>
  16. */
  17. OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
  18. /**
  19. * APIProperty: events
  20. * {<OpenLayers.Events>} Events instance for listeners and triggering
  21. * control specific events.
  22. *
  23. * Register a listener for a particular event with the following syntax:
  24. * (code)
  25. * control.events.register(type, obj, listener);
  26. * (end)
  27. *
  28. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  29. * beforesplit - Triggered before a split occurs. Listeners receive an
  30. * event object with *source* and *target* properties.
  31. * split - Triggered when a split occurs. Listeners receive an event with
  32. * an *original* property and a *features* property. The original
  33. * is a reference to the target feature that the sketch or modified
  34. * feature intersects. The features property is a list of all features
  35. * that result from this single split. This event is triggered before
  36. * the resulting features are added to the layer (while the layer still
  37. * has a reference to the original).
  38. * aftersplit - Triggered after all splits resulting from a single sketch
  39. * or feature modification have occurred. The original features
  40. * have been destroyed and features that result from the split
  41. * have already been added to the layer. Listeners receive an event
  42. * with a *source* and *features* property. The source references the
  43. * sketch or modified feature used as a splitter. The features
  44. * property is a list of all resulting features.
  45. */
  46. /**
  47. * APIProperty: layer
  48. * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
  49. * Set at construction or after construction with <setLayer>.
  50. */
  51. layer: null,
  52. /**
  53. * Property: source
  54. * {<OpenLayers.Layer.Vector>} Optional source layer. Any newly created
  55. * or modified features from this layer will be used to split features
  56. * on the target layer. If not provided, a temporary sketch layer will
  57. * be created.
  58. */
  59. source: null,
  60. /**
  61. * Property: sourceOptions
  62. * {Options} If a temporary sketch layer is created, these layer options
  63. * will be applied.
  64. */
  65. sourceOptions: null,
  66. /**
  67. * APIProperty: tolerance
  68. * {Number} Distance between the calculated intersection and a vertex on
  69. * the source geometry below which the existing vertex will be used
  70. * for the split. Default is null.
  71. */
  72. tolerance: null,
  73. /**
  74. * APIProperty: edge
  75. * {Boolean} Allow splits given intersection of edges only. Default is
  76. * true. If false, a vertex on the source must be within the
  77. * <tolerance> distance of the calculated intersection for a split
  78. * to occur.
  79. */
  80. edge: true,
  81. /**
  82. * APIProperty: deferDelete
  83. * {Boolean} Instead of removing features from the layer, set feature
  84. * states of split features to DELETE. This assumes a save strategy
  85. * or other component is in charge of removing features from the
  86. * layer. Default is false. If false, split features will be
  87. * immediately deleted from the layer.
  88. */
  89. deferDelete: false,
  90. /**
  91. * APIProperty: mutual
  92. * {Boolean} If source and target layers are the same, split source
  93. * features and target features where they intersect. Default is
  94. * true. If false, only target features will be split.
  95. */
  96. mutual: true,
  97. /**
  98. * APIProperty: targetFilter
  99. * {<OpenLayers.Filter>} Optional filter that will be evaluated
  100. * to determine if a feature from the target layer is eligible for
  101. * splitting.
  102. */
  103. targetFilter: null,
  104. /**
  105. * APIProperty: sourceFilter
  106. * {<OpenLayers.Filter>} Optional filter that will be evaluated
  107. * to determine if a feature from the source layer is eligible for
  108. * splitting.
  109. */
  110. sourceFilter: null,
  111. /**
  112. * Property: handler
  113. * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
  114. * no source layer is provided.
  115. */
  116. handler: null,
  117. /**
  118. * Constructor: OpenLayers.Control.Split
  119. * Creates a new split control. A control is constructed with a target
  120. * layer and an optional source layer. While the control is active,
  121. * creating new features or modifying existing features on the source
  122. * layer will result in splitting any eligible features on the target
  123. * layer. If no source layer is provided, a temporary sketch layer will
  124. * be created to create lines for splitting features on the target.
  125. *
  126. * Parameters:
  127. * options - {Object} An object containing all configuration properties for
  128. * the control.
  129. *
  130. * Valid options:
  131. * layer - {<OpenLayers.Layer.Vector>} The target layer. Features from this
  132. * layer will be split by new or modified features on the source layer
  133. * or temporary sketch layer.
  134. * source - {<OpenLayers.Layer.Vector>} Optional source layer. If provided
  135. * newly created features or modified features will be used to split
  136. * features on the target layer. If not provided, a temporary sketch
  137. * layer will be created for drawing lines.
  138. * tolerance - {Number} Optional value for the distance between a source
  139. * vertex and the calculated intersection below which the split will
  140. * occur at the vertex.
  141. * edge - {Boolean} Allow splits given intersection of edges only. Default
  142. * is true. If false, a vertex on the source must be within the
  143. * <tolerance> distance of the calculated intersection for a split
  144. * to occur.
  145. * mutual - {Boolean} If source and target are the same, split source
  146. * features and target features where they intersect. Default is
  147. * true. If false, only target features will be split.
  148. * targetFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
  149. * to determine if a feature from the target layer is eligible for
  150. * splitting.
  151. * sourceFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
  152. * to determine if a feature from the target layer is eligible for
  153. * splitting.
  154. */
  155. initialize: function(options) {
  156. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  157. this.options = options || {}; // TODO: this could be done by the super
  158. // set the source layer if provided
  159. if(this.options.source) {
  160. this.setSource(this.options.source);
  161. }
  162. },
  163. /**
  164. * APIMethod: setSource
  165. * Set the source layer for edits layer.
  166. *
  167. * Parameters:
  168. * layer - {<OpenLayers.Layer.Vector>} The new source layer layer. If
  169. * null, a temporary sketch layer will be created.
  170. */
  171. setSource: function(layer) {
  172. if(this.active) {
  173. this.deactivate();
  174. if(this.handler) {
  175. this.handler.destroy();
  176. delete this.handler;
  177. }
  178. this.source = layer;
  179. this.activate();
  180. } else {
  181. this.source = layer;
  182. }
  183. },
  184. /**
  185. * APIMethod: activate
  186. * Activate the control. Activating the control registers listeners for
  187. * editing related events so that during feature creation and
  188. * modification, features in the target will be considered for
  189. * splitting.
  190. */
  191. activate: function() {
  192. var activated = OpenLayers.Control.prototype.activate.call(this);
  193. if(activated) {
  194. if(!this.source) {
  195. if(!this.handler) {
  196. this.handler = new OpenLayers.Handler.Path(this,
  197. {done: function(geometry) {
  198. this.onSketchComplete({
  199. feature: new OpenLayers.Feature.Vector(geometry)
  200. });
  201. }},
  202. {layerOptions: this.sourceOptions}
  203. );
  204. }
  205. this.handler.activate();
  206. } else if(this.source.events) {
  207. this.source.events.on({
  208. sketchcomplete: this.onSketchComplete,
  209. afterfeaturemodified: this.afterFeatureModified,
  210. scope: this
  211. });
  212. }
  213. }
  214. return activated;
  215. },
  216. /**
  217. * APIMethod: deactivate
  218. * Deactivate the control. Deactivating the control unregisters listeners
  219. * so feature editing may proceed without engaging the split agent.
  220. */
  221. deactivate: function() {
  222. var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
  223. if(deactivated) {
  224. if(this.source && this.source.events) {
  225. this.source.events.un({
  226. sketchcomplete: this.onSketchComplete,
  227. afterfeaturemodified: this.afterFeatureModified,
  228. scope: this
  229. });
  230. }
  231. }
  232. return deactivated;
  233. },
  234. /**
  235. * Method: onSketchComplete
  236. * Registered as a listener for the sketchcomplete event on the editable
  237. * layer.
  238. *
  239. * Parameters:
  240. * event - {Object} The sketch complete event.
  241. *
  242. * Returns:
  243. * {Boolean} Stop the sketch from being added to the layer (it has been
  244. * split).
  245. */
  246. onSketchComplete: function(event) {
  247. this.feature = null;
  248. return !this.considerSplit(event.feature);
  249. },
  250. /**
  251. * Method: afterFeatureModified
  252. * Registered as a listener for the afterfeaturemodified event on the
  253. * editable layer.
  254. *
  255. * Parameters:
  256. * event - {Object} The after feature modified event.
  257. */
  258. afterFeatureModified: function(event) {
  259. if(event.modified) {
  260. var feature = event.feature;
  261. if (typeof feature.geometry.split === "function") {
  262. this.feature = event.feature;
  263. this.considerSplit(event.feature);
  264. }
  265. }
  266. },
  267. /**
  268. * Method: removeByGeometry
  269. * Remove a feature from a list based on the given geometry.
  270. *
  271. * Parameters:
  272. * features - {Array(<OpenLayers.Feature.Vector>)} A list of features.
  273. * geometry - {<OpenLayers.Geometry>} A geometry.
  274. */
  275. removeByGeometry: function(features, geometry) {
  276. for(var i=0, len=features.length; i<len; ++i) {
  277. if(features[i].geometry === geometry) {
  278. features.splice(i, 1);
  279. break;
  280. }
  281. }
  282. },
  283. /**
  284. * Method: isEligible
  285. * Test if a target feature is eligible for splitting.
  286. *
  287. * Parameters:
  288. * target - {<OpenLayers.Feature.Vector>} The target feature.
  289. *
  290. * Returns:
  291. * {Boolean} The target is eligible for splitting.
  292. */
  293. isEligible: function(target) {
  294. if (!target.geometry) {
  295. return false;
  296. } else {
  297. return (
  298. target.state !== OpenLayers.State.DELETE
  299. ) && (
  300. typeof target.geometry.split === "function"
  301. ) && (
  302. this.feature !== target
  303. ) && (
  304. !this.targetFilter ||
  305. this.targetFilter.evaluate(target.attributes)
  306. );
  307. }
  308. },
  309. /**
  310. * Method: considerSplit
  311. * Decide whether or not to split target features with the supplied
  312. * feature. If <mutual> is true, both the source and target features
  313. * will be split if eligible.
  314. *
  315. * Parameters:
  316. * feature - {<OpenLayers.Feature.Vector>} The newly created or modified
  317. * feature.
  318. *
  319. * Returns:
  320. * {Boolean} The supplied feature was split (and destroyed).
  321. */
  322. considerSplit: function(feature) {
  323. var sourceSplit = false;
  324. var targetSplit = false;
  325. if(!this.sourceFilter ||
  326. this.sourceFilter.evaluate(feature.attributes)) {
  327. var features = this.layer && this.layer.features || [];
  328. var target, results, proceed;
  329. var additions = [], removals = [];
  330. var mutual = (this.layer === this.source) && this.mutual;
  331. var options = {
  332. edge: this.edge,
  333. tolerance: this.tolerance,
  334. mutual: mutual
  335. };
  336. var sourceParts = [feature.geometry];
  337. var targetFeature, targetParts;
  338. var source, parts;
  339. for(var i=0, len=features.length; i<len; ++i) {
  340. targetFeature = features[i];
  341. if(this.isEligible(targetFeature)) {
  342. targetParts = [targetFeature.geometry];
  343. // work through source geoms - this array may change
  344. for(var j=0; j<sourceParts.length; ++j) {
  345. source = sourceParts[j];
  346. // work through target parts - this array may change
  347. for(var k=0; k<targetParts.length; ++k) {
  348. target = targetParts[k];
  349. if(source.getBounds().intersectsBounds(target.getBounds())) {
  350. results = source.split(target, options);
  351. if(results) {
  352. proceed = this.events.triggerEvent(
  353. "beforesplit", {source: feature, target: targetFeature}
  354. );
  355. if(proceed !== false) {
  356. if(mutual) {
  357. parts = results[0];
  358. // handle parts that result from source splitting
  359. if(parts.length > 1) {
  360. // splice in new source parts
  361. parts.unshift(j, 1); // add args for splice below
  362. Array.prototype.splice.apply(sourceParts, parts);
  363. j += parts.length - 3;
  364. }
  365. results = results[1];
  366. }
  367. // handle parts that result from target splitting
  368. if(results.length > 1) {
  369. // splice in new target parts
  370. results.unshift(k, 1); // add args for splice below
  371. Array.prototype.splice.apply(targetParts, results);
  372. k += results.length - 3;
  373. }
  374. }
  375. }
  376. }
  377. }
  378. }
  379. if(targetParts && targetParts.length > 1) {
  380. this.geomsToFeatures(targetFeature, targetParts);
  381. this.events.triggerEvent("split", {
  382. original: targetFeature,
  383. features: targetParts
  384. });
  385. Array.prototype.push.apply(additions, targetParts);
  386. removals.push(targetFeature);
  387. targetSplit = true;
  388. }
  389. }
  390. }
  391. if(sourceParts && sourceParts.length > 1) {
  392. this.geomsToFeatures(feature, sourceParts);
  393. this.events.triggerEvent("split", {
  394. original: feature,
  395. features: sourceParts
  396. });
  397. Array.prototype.push.apply(additions, sourceParts);
  398. removals.push(feature);
  399. sourceSplit = true;
  400. }
  401. if(sourceSplit || targetSplit) {
  402. // remove and add feature events are suppressed
  403. // listen for split event on this control instead
  404. if(this.deferDelete) {
  405. // Set state instead of removing. Take care to avoid
  406. // setting delete for features that have not yet been
  407. // inserted - those should be destroyed immediately.
  408. var feat, destroys = [];
  409. for(var i=0, len=removals.length; i<len; ++i) {
  410. feat = removals[i];
  411. if(feat.state === OpenLayers.State.INSERT) {
  412. destroys.push(feat);
  413. } else {
  414. feat.state = OpenLayers.State.DELETE;
  415. this.layer.drawFeature(feat);
  416. }
  417. }
  418. this.layer.destroyFeatures(destroys, {silent: true});
  419. for(var i=0, len=additions.length; i<len; ++i) {
  420. additions[i].state = OpenLayers.State.INSERT;
  421. }
  422. } else {
  423. this.layer.destroyFeatures(removals, {silent: true});
  424. }
  425. this.layer.addFeatures(additions, {silent: true});
  426. this.events.triggerEvent("aftersplit", {
  427. source: feature,
  428. features: additions
  429. });
  430. }
  431. }
  432. return sourceSplit;
  433. },
  434. /**
  435. * Method: geomsToFeatures
  436. * Create new features given a template feature and a list of geometries.
  437. * The list of geometries is modified in place. The result will be
  438. * a list of new features.
  439. *
  440. * Parameters:
  441. * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
  442. * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries. This will
  443. * become a list of new features.
  444. */
  445. geomsToFeatures: function(feature, geoms) {
  446. var clone = feature.clone();
  447. delete clone.geometry;
  448. var newFeature;
  449. for(var i=0, len=geoms.length; i<len; ++i) {
  450. // turn results list from geoms to features
  451. newFeature = clone.clone();
  452. newFeature.geometry = geoms[i];
  453. newFeature.state = OpenLayers.State.INSERT;
  454. geoms[i] = newFeature;
  455. }
  456. },
  457. /**
  458. * Method: destroy
  459. * Clean up the control.
  460. */
  461. destroy: function() {
  462. if(this.active) {
  463. this.deactivate(); // TODO: this should be handled by the super
  464. }
  465. OpenLayers.Control.prototype.destroy.call(this);
  466. },
  467. CLASS_NAME: "OpenLayers.Control.Split"
  468. });