Edit.Poly.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. L.Edit = L.Edit || {};
  2. /**
  3. * @class L.Edit.Polyline
  4. * @aka L.Edit.Poly
  5. * @aka Edit.Poly
  6. */
  7. L.Edit.Poly = L.Handler.extend({
  8. options: {},
  9. // @method initialize(): void
  10. initialize: function (poly, options) {
  11. this.latlngs = [poly._latlngs];
  12. if (poly._holes) {
  13. this.latlngs = this.latlngs.concat(poly._holes);
  14. }
  15. this._poly = poly;
  16. L.setOptions(this, options);
  17. this._poly.on('revert-edited', this._updateLatLngs, this);
  18. },
  19. // Compatibility method to normalize Poly* objects
  20. // between 0.7.x and 1.0+
  21. _defaultShape: function () {
  22. if (!L.Polyline._flat) {
  23. return this._poly._latlngs;
  24. }
  25. return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0];
  26. },
  27. _eachVertexHandler: function (callback) {
  28. for (var i = 0; i < this._verticesHandlers.length; i++) {
  29. callback(this._verticesHandlers[i]);
  30. }
  31. },
  32. // @method addHooks(): void
  33. // Add listener hooks to this handler
  34. addHooks: function () {
  35. this._initHandlers();
  36. this._eachVertexHandler(function (handler) {
  37. handler.addHooks();
  38. });
  39. },
  40. // @method removeHooks(): void
  41. // Remove listener hooks from this handler
  42. removeHooks: function () {
  43. this._eachVertexHandler(function (handler) {
  44. handler.removeHooks();
  45. });
  46. },
  47. // @method updateMarkers(): void
  48. // Fire an update for each vertex handler
  49. updateMarkers: function () {
  50. this._eachVertexHandler(function (handler) {
  51. handler.updateMarkers();
  52. });
  53. },
  54. _initHandlers: function () {
  55. this._verticesHandlers = [];
  56. for (var i = 0; i < this.latlngs.length; i++) {
  57. this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this.options));
  58. }
  59. },
  60. _updateLatLngs: function (e) {
  61. this.latlngs = [e.layer._latlngs];
  62. if (e.layer._holes) {
  63. this.latlngs = this.latlngs.concat(e.layer._holes);
  64. }
  65. }
  66. });
  67. /**
  68. * @class L.Edit.PolyVerticesEdit
  69. * @aka Edit.PolyVerticesEdit
  70. */
  71. L.Edit.PolyVerticesEdit = L.Handler.extend({
  72. options: {
  73. icon: new L.DivIcon({
  74. iconSize: new L.Point(8, 8),
  75. className: 'leaflet-div-icon leaflet-editing-icon'
  76. }),
  77. touchIcon: new L.DivIcon({
  78. iconSize: new L.Point(20, 20),
  79. className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
  80. }),
  81. drawError: {
  82. color: '#b00b00',
  83. timeout: 1000
  84. }
  85. },
  86. // @method intialize(): void
  87. initialize: function (poly, latlngs, options) {
  88. // if touch, switch to touch icon
  89. if (L.Browser.touch) {
  90. this.options.icon = this.options.touchIcon;
  91. }
  92. this._poly = poly;
  93. if (options && options.drawError) {
  94. options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
  95. }
  96. this._latlngs = latlngs;
  97. L.setOptions(this, options);
  98. },
  99. // Compatibility method to normalize Poly* objects
  100. // between 0.7.x and 1.0+
  101. _defaultShape: function () {
  102. if (!L.Polyline._flat) {
  103. return this._latlngs;
  104. }
  105. return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
  106. },
  107. // @method addHooks(): void
  108. // Add listener hooks to this handler.
  109. addHooks: function () {
  110. var poly = this._poly;
  111. if (!(poly instanceof L.Polygon)) {
  112. poly.options.fill = false;
  113. if (poly.options.editing) {
  114. poly.options.editing.fill = false;
  115. }
  116. }
  117. poly.setStyle(poly.options.editing);
  118. if (this._poly._map) {
  119. this._map = this._poly._map; // Set map
  120. if (!this._markerGroup) {
  121. this._initMarkers();
  122. }
  123. this._poly._map.addLayer(this._markerGroup);
  124. }
  125. },
  126. // @method removeHooks(): void
  127. // Remove listener hooks from this handler.
  128. removeHooks: function () {
  129. var poly = this._poly;
  130. poly.setStyle(poly.options.original);
  131. if (poly._map) {
  132. poly._map.removeLayer(this._markerGroup);
  133. delete this._markerGroup;
  134. delete this._markers;
  135. }
  136. },
  137. // @method updateMarkers(): void
  138. // Clear markers and update their location
  139. updateMarkers: function () {
  140. this._markerGroup.clearLayers();
  141. this._initMarkers();
  142. },
  143. _initMarkers: function () {
  144. if (!this._markerGroup) {
  145. this._markerGroup = new L.LayerGroup();
  146. }
  147. this._markers = [];
  148. var latlngs = this._defaultShape(),
  149. i, j, len, marker;
  150. for (i = 0, len = latlngs.length; i < len; i++) {
  151. marker = this._createMarker(latlngs[i], i);
  152. marker.on('click', this._onMarkerClick, this);
  153. this._markers.push(marker);
  154. }
  155. var markerLeft, markerRight;
  156. for (i = 0, j = len - 1; i < len; j = i++) {
  157. if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
  158. continue;
  159. }
  160. markerLeft = this._markers[j];
  161. markerRight = this._markers[i];
  162. this._createMiddleMarker(markerLeft, markerRight);
  163. this._updatePrevNext(markerLeft, markerRight);
  164. }
  165. },
  166. _createMarker: function (latlng, index) {
  167. // Extending L.Marker in TouchEvents.js to include touch.
  168. var marker = new L.Marker.Touch(latlng, {
  169. draggable: true,
  170. icon: this.options.icon,
  171. });
  172. marker._origLatLng = latlng;
  173. marker._index = index;
  174. marker
  175. .on('dragstart', this._onMarkerDragStart, this)
  176. .on('drag', this._onMarkerDrag, this)
  177. .on('dragend', this._fireEdit, this)
  178. .on('touchmove', this._onTouchMove, this)
  179. .on('touchend', this._fireEdit, this)
  180. .on('MSPointerMove', this._onTouchMove, this)
  181. .on('MSPointerUp', this._fireEdit, this);
  182. this._markerGroup.addLayer(marker);
  183. return marker;
  184. },
  185. _onMarkerDragStart: function () {
  186. this._poly.fire('editstart');
  187. },
  188. _spliceLatLngs: function () {
  189. var latlngs = this._defaultShape();
  190. var removed = [].splice.apply(latlngs, arguments);
  191. this._poly._convertLatLngs(latlngs, true);
  192. this._poly.redraw();
  193. return removed;
  194. },
  195. _removeMarker: function (marker) {
  196. var i = marker._index;
  197. this._markerGroup.removeLayer(marker);
  198. this._markers.splice(i, 1);
  199. this._spliceLatLngs(i, 1);
  200. this._updateIndexes(i, -1);
  201. marker
  202. .off('dragstart', this._onMarkerDragStart, this)
  203. .off('drag', this._onMarkerDrag, this)
  204. .off('dragend', this._fireEdit, this)
  205. .off('touchmove', this._onMarkerDrag, this)
  206. .off('touchend', this._fireEdit, this)
  207. .off('click', this._onMarkerClick, this)
  208. .off('MSPointerMove', this._onTouchMove, this)
  209. .off('MSPointerUp', this._fireEdit, this);
  210. },
  211. _fireEdit: function () {
  212. this._poly.edited = true;
  213. this._poly.fire('edit');
  214. this._poly._map.fire(L.Draw.Event.EDITVERTEX, { layers: this._markerGroup, poly: this._poly });
  215. },
  216. _onMarkerDrag: function (e) {
  217. var marker = e.target;
  218. var poly = this._poly;
  219. L.extend(marker._origLatLng, marker._latlng);
  220. if (marker._middleLeft) {
  221. marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
  222. }
  223. if (marker._middleRight) {
  224. marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
  225. }
  226. if (poly.options.poly) {
  227. var tooltip = poly._map._editTooltip; // Access the tooltip
  228. // If we don't allow intersections and the polygon intersects
  229. if (!poly.options.poly.allowIntersection && poly.intersects()) {
  230. var originalColor = poly.options.color;
  231. poly.setStyle({ color: this.options.drawError.color });
  232. // Manually trigger 'dragend' behavior on marker we are about to remove
  233. // WORKAROUND: introduced in 1.0.0-rc2, may be related to #4484
  234. if (L.version.indexOf('0.7') !== 0) {
  235. marker.dragging._draggable._onUp(e);
  236. }
  237. this._onMarkerClick(e); // Remove violating marker
  238. // FIXME: Reset the marker to it's original position (instead of remove)
  239. if (tooltip) {
  240. tooltip.updateContent({
  241. text: L.drawLocal.draw.handlers.polyline.error
  242. });
  243. }
  244. // Reset everything back to normal after a second
  245. setTimeout(function () {
  246. poly.setStyle({ color: originalColor });
  247. if (tooltip) {
  248. tooltip.updateContent({
  249. text: L.drawLocal.edit.handlers.edit.tooltip.text,
  250. subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
  251. });
  252. }
  253. }, 1000);
  254. }
  255. }
  256. this._poly.redraw();
  257. this._poly.fire('editdrag');
  258. },
  259. _onMarkerClick: function (e) {
  260. var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3,
  261. marker = e.target;
  262. // If removing this point would create an invalid polyline/polygon don't remove
  263. if (this._defaultShape().length < minPoints) {
  264. return;
  265. }
  266. // remove the marker
  267. this._removeMarker(marker);
  268. // update prev/next links of adjacent markers
  269. this._updatePrevNext(marker._prev, marker._next);
  270. // remove ghost markers near the removed marker
  271. if (marker._middleLeft) {
  272. this._markerGroup.removeLayer(marker._middleLeft);
  273. }
  274. if (marker._middleRight) {
  275. this._markerGroup.removeLayer(marker._middleRight);
  276. }
  277. // create a ghost marker in place of the removed one
  278. if (marker._prev && marker._next) {
  279. this._createMiddleMarker(marker._prev, marker._next);
  280. } else if (!marker._prev) {
  281. marker._next._middleLeft = null;
  282. } else if (!marker._next) {
  283. marker._prev._middleRight = null;
  284. }
  285. this._fireEdit();
  286. },
  287. _onTouchMove: function (e) {
  288. var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
  289. latlng = this._map.layerPointToLatLng(layerPoint),
  290. marker = e.target;
  291. L.extend(marker._origLatLng, latlng);
  292. if (marker._middleLeft) {
  293. marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
  294. }
  295. if (marker._middleRight) {
  296. marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
  297. }
  298. this._poly.redraw();
  299. this.updateMarkers();
  300. },
  301. _updateIndexes: function (index, delta) {
  302. this._markerGroup.eachLayer(function (marker) {
  303. if (marker._index > index) {
  304. marker._index += delta;
  305. }
  306. });
  307. },
  308. _createMiddleMarker: function (marker1, marker2) {
  309. var latlng = this._getMiddleLatLng(marker1, marker2),
  310. marker = this._createMarker(latlng),
  311. onClick,
  312. onDragStart,
  313. onDragEnd;
  314. marker.setOpacity(0.6);
  315. marker1._middleRight = marker2._middleLeft = marker;
  316. onDragStart = function () {
  317. marker.off('touchmove', onDragStart, this);
  318. var i = marker2._index;
  319. marker._index = i;
  320. marker
  321. .off('click', onClick, this)
  322. .on('click', this._onMarkerClick, this);
  323. latlng.lat = marker.getLatLng().lat;
  324. latlng.lng = marker.getLatLng().lng;
  325. this._spliceLatLngs(i, 0, latlng);
  326. this._markers.splice(i, 0, marker);
  327. marker.setOpacity(1);
  328. this._updateIndexes(i, 1);
  329. marker2._index++;
  330. this._updatePrevNext(marker1, marker);
  331. this._updatePrevNext(marker, marker2);
  332. this._poly.fire('editstart');
  333. };
  334. onDragEnd = function () {
  335. marker.off('dragstart', onDragStart, this);
  336. marker.off('dragend', onDragEnd, this);
  337. marker.off('touchmove', onDragStart, this);
  338. this._createMiddleMarker(marker1, marker);
  339. this._createMiddleMarker(marker, marker2);
  340. };
  341. onClick = function () {
  342. onDragStart.call(this);
  343. onDragEnd.call(this);
  344. this._fireEdit();
  345. };
  346. marker
  347. .on('click', onClick, this)
  348. .on('dragstart', onDragStart, this)
  349. .on('dragend', onDragEnd, this)
  350. .on('touchmove', onDragStart, this);
  351. this._markerGroup.addLayer(marker);
  352. },
  353. _updatePrevNext: function (marker1, marker2) {
  354. if (marker1) {
  355. marker1._next = marker2;
  356. }
  357. if (marker2) {
  358. marker2._prev = marker1;
  359. }
  360. },
  361. _getMiddleLatLng: function (marker1, marker2) {
  362. var map = this._poly._map,
  363. p1 = map.project(marker1.getLatLng()),
  364. p2 = map.project(marker2.getLatLng());
  365. return map.unproject(p1._add(p2)._divideBy(2));
  366. }
  367. });
  368. L.Polyline.addInitHook(function () {
  369. // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit
  370. if (this.editing) {
  371. return;
  372. }
  373. if (L.Edit.Poly) {
  374. this.editing = new L.Edit.Poly(this, this.options.poly);
  375. if (this.options.editable) {
  376. this.editing.enable();
  377. }
  378. }
  379. this.on('add', function () {
  380. if (this.editing && this.editing.enabled()) {
  381. this.editing.addHooks();
  382. }
  383. });
  384. this.on('remove', function () {
  385. if (this.editing && this.editing.enabled()) {
  386. this.editing.removeHooks();
  387. }
  388. });
  389. });