L.Clipper.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. /*global L:true, ClipperLib:true */
  2. import ClipperLib from 'clipper-lib/clipper.js'
  3. L.Clipper = L.Evented.extend({
  4. options: {
  5. //featureGroup: new L.FeatureGroup(),
  6. selectedPathOptions: {
  7. color: '#FF3399'
  8. }
  9. },
  10. initialize: function (map, options) {
  11. L.Handler.prototype.initialize.call(this, map);
  12. L.Util.setOptions(this, options);
  13. if (!(this.options.featureGroup instanceof L.FeatureGroup)) {
  14. throw new Error('options.featureGroup must be a L.FeatureGroup');
  15. }
  16. },
  17. enable: function () {
  18. if (this._enabled || !this._hasAvailableLayers()) {
  19. return;
  20. }
  21. this.fire('enabled', {handler: this.type});
  22. // this disable other handlers
  23. this._map.fire('draw:joinstart', { handler: this.type });
  24. // allow drawLayer to be updated before beginning edition.
  25. L.Handler.prototype.enable.call(this);
  26. this.options.featureGroup
  27. .on('layeradd', this._enableLayerEdit, this)
  28. .on('layerremove', this._disableLayerEdit, this);
  29. },
  30. disable: function () {
  31. if (!this._enabled) { return; }
  32. this.options.featureGroup
  33. .off('layeradd', this._enableLayerJoin, this)
  34. .off('layerremove', this._disableLayerJoin, this);
  35. L.Handler.prototype.disable.call(this);
  36. this._map.fire('draw:editstop', { handler: this.type });
  37. this.fire('disabled', {handler: this.type});
  38. },
  39. addHooks: function () {
  40. var map = this._map;
  41. if (map) {
  42. map.getContainer().focus();
  43. this.options.featureGroup.eachLayer(this._enableLayerEdit, this);
  44. // Leaflet.draw specific
  45. //this._tooltip = new L.tooltip(this._map);
  46. //this._tooltip.updateContent(this._getTooltipText());
  47. // this._map.on('mousemove', this._onMouseMove, this);
  48. }
  49. },
  50. removeHooks: function () {
  51. if (this._map) {
  52. // Clean up selected layers.
  53. this.options.featureGroup.eachLayer(this._disableLayerEdit, this);
  54. // Clear the backups of the original layers
  55. this._uneditedLayerProps = {};
  56. this._tooltip.dispose();
  57. this._tooltip = null;
  58. this._map.off('mousemove', this._onMouseMove, this);
  59. }
  60. },
  61. revertLayers: function () {
  62. this.options.featureGroup.eachLayer(function (layer) {
  63. this._revertLayer(layer);
  64. }, this);
  65. },
  66. save: function () {
  67. var editedLayers = new L.LayerGroup();
  68. this.options.featureGroup.eachLayer(function (layer) {
  69. if (layer.edited) {
  70. editedLayers.addLayer(layer);
  71. layer.edited = false;
  72. }
  73. });
  74. this._map.fire('draw:edited', {layers: editedLayers});
  75. },
  76. _revertLayer: function (layer) {
  77. var id = L.Util.stamp(layer);
  78. layer.edited = false;
  79. if (this._uneditedLayerProps.hasOwnProperty(id)) {
  80. // Polyline, Polygon or Rectangle
  81. if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
  82. layer.setLatLngs(this._uneditedLayerProps[id].latlngs);
  83. } else if (layer instanceof L.Circle) {
  84. layer.setLatLng(this._uneditedLayerProps[id].latlng);
  85. layer.setRadius(this._uneditedLayerProps[id].radius);
  86. } else if (layer instanceof L.Marker) { // Marker
  87. layer.setLatLng(this._uneditedLayerProps[id].latlng);
  88. }
  89. }
  90. },
  91. _enableLayerEdit: function (e) {
  92. var layer = e.layer || e.target || e;
  93. layer.on('click', this._subjectLayer, this);
  94. },
  95. _disableLayerEdit: function (e) {
  96. var layer = e.layer || e.target || e;
  97. layer.off('click');
  98. },
  99. // Only supported in Leaflet.draw
  100. /*_getTooltipText: function () {
  101. var labelText;
  102. if (!this._subject) {
  103. labelText = {
  104. text: 'Choose a subject layer.'
  105. };
  106. } else {
  107. labelText = {
  108. text: 'Choose a clipper layer.'
  109. };
  110. }
  111. return labelText;
  112. },*/
  113. _onMouseMove: function (e) {
  114. this._tooltip.updatePosition(e.latlng);
  115. },
  116. _hasAvailableLayers: function () {
  117. return this.options.featureGroup.getLayers().length !== 0;
  118. },
  119. _subjectLayer: function(e) {
  120. var layer = e.layer || e.target || e;
  121. if (!this._subject) {
  122. this._subject = layer;
  123. } else {
  124. this._clipperLayer(e);
  125. }
  126. layer.setStyle(this.options.selectedPathOptions);
  127. },
  128. _clipperLayer: function(e) {
  129. var layer = e.layer || e.target || e;
  130. this._clipper = layer;
  131. layer.setStyle(this.options.selectedPathOptions);
  132. this._layerClip();
  133. },
  134. _layerClip: function() {
  135. var subjCoords = this._subject.getLatLngs();
  136. var clipCoords = this._clipper.getLatLngs();
  137. var subj = this._coordsToPoints(subjCoords);
  138. var clip = this._coordsToPoints(clipCoords);
  139. var solution = ClipperLib.Paths();
  140. var cpr = new ClipperLib.Clipper();
  141. for(var s = 0, slen = subj.length; s<slen; s++) {
  142. cpr.AddPaths(subj[s], ClipperLib.PolyType.ptSubject, true);
  143. }
  144. for(var c = 0, clen = clip.length; c < clen; c++) {
  145. cpr.AddPaths(clip[c], ClipperLib.PolyType.ptClip, true);
  146. }
  147. cpr.Execute(this._cliptype, solution);
  148. this._solution = L.polygon( this._pointsToCoords([solution], true) );
  149. this.options.featureGroup.addLayer(this._solution);
  150. this.options.featureGroup.removeLayer(this._subject);
  151. this.options.featureGroup.removeLayer(this._clipper);
  152. this._subject = null;
  153. this._clipper = null;
  154. },
  155. /*
  156. --- ClipperJS ---
  157. Add in ability to convert coords or latlngs to x,y points
  158. to be used by clipperjs
  159. 4503599627370495 min/max
  160. 180.1234567890123 min/max
  161. */
  162. _pointAmplifier: function() {
  163. return Math.pow(10,13);
  164. },
  165. // coord: [lng, lat] / [x, y]
  166. // latlng: [lat, lng] / { lat:lat, lng:lng }
  167. // point: { X:x, Y:y }
  168. // polygons: [
  169. // [ // polygon
  170. // coords,
  171. // holes
  172. // ]
  173. // ]
  174. _coordsToPoints: function (polygons, latlng) {
  175. var points = [], amp = this._pointAmplifier();
  176. for (var i = 0, ilen = polygons.length; i < ilen; i++) {
  177. points.push([]);
  178. for (var j = 0, jlen = polygons[i].length; j < jlen; j++) {
  179. var coords = polygons[i][j];
  180. points[i].push([]);
  181. for (var k = 0, klen = coords.length; k < klen; k++) {
  182. var coord = coords[k];
  183. if (Array.isArray(coord)) {
  184. if (latlng) {
  185. points[i][j].push({X: Math.round(coord[1] * amp), Y: Math.round(coord[0] * amp)});
  186. } else {
  187. points[i][j].push({X: Math.round(coord[0] * amp), Y: Math.round(coord[1] * amp)});
  188. }
  189. } else {
  190. points[i][j].push({X: Math.round(coord.lng * amp), Y: Math.round(coord.lat * amp)});
  191. }
  192. }
  193. }
  194. }
  195. return points;
  196. },
  197. _pointsToCoords: function (polygons, latlng) {
  198. var coords = [], amp = this._pointAmplifier();
  199. for (var i = 0, ilen = polygons.length; i < ilen; i++) {
  200. coords.push([]);
  201. for (var j = 0, jlen = polygons[i].length; j < jlen; j++) {
  202. var points = polygons[i][j];
  203. coords[i].push([]);
  204. for (var k = 0, klen = points.length; k < klen; k++) {
  205. var point = points[k];
  206. if (latlng) {
  207. coords[i][j].push([point.Y / amp, point.X / amp]);
  208. } else {
  209. coords[i][j].push([point.X / amp, point.Y / amp]);
  210. }
  211. }
  212. }
  213. }
  214. return coords;
  215. }
  216. // --- End ClipperJS --- //
  217. });
  218. L.Clipper.AND = L.Clipper.extend({
  219. // Save the type so super can fire, need to do this as cannot do this.TYPE :(
  220. _cliptype: ClipperLib.ClipType.ctIntersection
  221. });
  222. L.Clipper.Intersection = L.Clipper.AND;
  223. L.Clipper.OR = L.Clipper.extend({
  224. _cliptype: ClipperLib.ClipType.ctUnion
  225. });
  226. L.Clipper.Union = L.Clipper.OR;
  227. L.Clipper.NOT = L.Clipper.extend({
  228. _cliptype: ClipperLib.ClipType.ctDifference
  229. });
  230. L.Clipper.Difference = L.Clipper.NOT;
  231. L.Clipper.XOR = L.Clipper.extend({
  232. _cliptype: ClipperLib.ClipType.ctXor
  233. });
  234. L.Clipper.Xor = L.Clipper.XOR;