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