From a207da0cdd760ff7e75dc9db011dc4b0a81b7f20 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 23 Mar 2019 21:56:58 -0400 Subject: [PATCH 01/18] dist file --- dist/leaflet.distortableimage.js | 180 ++++++++----------------------- 1 file changed, 42 insertions(+), 138 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 0040ad9f2..e04116628 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -784,11 +784,6 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ L.DistortableImage = L.DistortableImage || {}; var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ -<<<<<<< HEAD - initialize: function(map, overlay, options) { - this._overlay = overlay; - this._map = map; -======= initialize: function(map, overlay, options) { this._overlay = overlay; this._map = map; @@ -833,160 +828,54 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ tooltip: 'Delete image', title: 'Delete image' }}, ->>>>>>> 7d1dc5bdd8e16a65b9204667f6066757f5245d1d - LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); - } - }), - ToggleTransparency = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Toggle Image Transparency", - title: "Toggle Image Transparency" - } - }, + addHooks: function() { + var map = this._map; + + map.removeLayer(this._overlay); + this._overlay.fire('delete'); + this.disable(); + } + }), -<<<<<<< HEAD - addHooks: function() { - var editing = this._overlay.editing; -======= ToggleEditable = EditOverlayAction.extend({ options: { toolbarIcon: { html: '', tooltip: 'Lock / Unlock editing', title: 'Lock / Unlock editing' }}, ->>>>>>> 7d1dc5bdd8e16a65b9204667f6066757f5245d1d - editing._toggleTransparency(); - this.disable(); - } - }), - ToggleOutline = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Toggle Image Outline", - title: "Toggle Image Outline" - } - }, + addHooks: function() { + var editing = this._overlay.editing; - addHooks: function() { - var editing = this._overlay.editing; + editing._toggleLock(); + this.disable(); + } + }), - editing._toggleOutline(); - this.disable(); - } - }), - RemoveOverlay = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Delete image", - title: "Delete image" - } - }, + ToggleRotateDistort = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var icon = overlay.editing._mode === 'rotate' ? 'image' : 'rotate-left'; -<<<<<<< HEAD - addHooks: function() { - var map = this._map; -======= options = options || {}; options.toolbarIcon = { html: '', tooltip: 'Rotate', title: 'Rotate' }; ->>>>>>> 7d1dc5bdd8e16a65b9204667f6066757f5245d1d - - map.removeLayer(this._overlay); - this._overlay.fire("delete"); - this.disable(); - } - }), - ToggleEditable = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Lock / Unlock editing", - title: "Lock / Unlock editing" - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - editing._toggleLock(); - this.disable(); - } - }), - ToggleRotateDistort = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var icon = overlay.editing._mode === "rotate" ? "image" : "rotate-left"; - - options = options || {}; - options.toolbarIcon = { - html: '', - tooltip: "Rotate", - title: "Rotate" - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleRotateDistort(); - this.disable(); - } - }), - ToggleExport = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Export Image", - title: "Export Image" - } - }, + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, - addHooks: function() { - var editing = this._overlay.editing; + addHooks: function() { + var editing = this._overlay.editing; -<<<<<<< HEAD - editing._toggleExport(); - this.disable(); - } - }), - EnableEXIF = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Enable EXIF", - title: "Geocode Image" - } - }, + editing._toggleRotateDistort(); + this.disable(); + } + }), - addHooks: function() { - var image = this._overlay._image; - EXIF.getData(image, L.EXIF(image)); - } - }); -L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: [ - ToggleTransparency, - RemoveOverlay, - ToggleOutline, - ToggleEditable, - ToggleRotateDistort, - ToggleExport, - EnableEXIF - ] - } -======= ToggleExport = EditOverlayAction.extend({ options: { toolbarIcon: { @@ -1021,7 +910,22 @@ L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ editing._toggleOrder(); this.disable(); } - }); + }), + + EnableEXIF = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: "Enable EXIF", + title: "Geocode Image" + } + }, + + addHooks: function() { + var image = this._overlay._image; + EXIF.getData(image, L.EXIF(image)); + } + }); L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ options: { @@ -1032,10 +936,10 @@ L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ ToggleEditable, ToggleRotateDistort, ToggleExport, + EnableEXIF, ToggleOrder ] } ->>>>>>> 7d1dc5bdd8e16a65b9204667f6066757f5245d1d }); L.DistortableImage = L.DistortableImage || {}; From 00ea7524a218689905657ad04c0a4e1e0615c0b5 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 20 Apr 2019 22:00:04 -0400 Subject: [PATCH 02/18] Delete 3 hotkeys --- dist/leaflet.distortableimage.js | 89 +++++++++++++++++-------------- src/edit/DistortableImage.Edit.js | 89 +++++++++++++++++-------------- 2 files changed, 98 insertions(+), 80 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index dd242d2cf..27f7806cd 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1364,51 +1364,60 @@ window.addEventListener('load', function() { L.DistortableImage = L.DistortableImage || {}; L.DistortableImage.Edit = L.Handler.extend({ - options: { - opacity: 0.7, - outline: '1px solid red', - keymap: { - 8: '_removeOverlay', // backspace windows / delete mac - 46: '_removeOverlay', // delete windows / delete + fn mac - 20: '_toggleRotate', // CAPS - 27: '_deselect', // esc - 68: '_toggleRotateDistort', // d - 69: '_toggleIsolate', // e - 73: '_toggleIsolate', // i - 74: '_sendUp', // j - 75: '_sendDown', // k - 76: '_toggleLock', // l - 79: '_toggleOutline', // o - 82: '_toggleRotateDistort', // r - 83: '_toggleScale', // s - 84: '_toggleTransparency', // t - } - }, + options: { + opacity: 0.7, + outline: "1px solid red", + keymap: { + 8: "_removeOverlay", // backspace windows / delete mac + 46: "_removeOverlay", // delete windows / delete + fn mac + 27: "_deselect", // esc + 68: "_toggleRotateDistort", // d + 74: "_sendUp", // j + 75: "_sendDown", // k + 76: "_toggleLock", // l + 79: "_toggleOutline", // o + 82: "_toggleRotateDistort", // r + 83: "_toggleScale", // s + 84: "_toggleTransparency" // t + } + }, - initialize: function(overlay) { - this._overlay = overlay; - this._toggledImage = false; + initialize: function(overlay) { + this._overlay = overlay; + this._toggledImage = false; - /* Interaction modes. */ - this._mode = this._overlay.options.mode || 'distort'; - this._transparent = false; - this._outlined = false; + /* Interaction modes. */ + this._mode = this._overlay.options.mode || "distort"; + this._transparent = false; + this._outlined = false; + }, - /* generate instance counts */ - this.instance_count = L.DistortableImage.Edit.prototype.instances = - L.DistortableImage.Edit.prototype.instances ? L.DistortableImage.Edit.prototype.instances + 1 : 1; - }, + /* Run on image selection. */ + addHooks: function() { + var overlay = this._overlay, + map = overlay._map, + i; - /* Run on image selection. */ - addHooks: function() { - var overlay = this._overlay, - map = overlay._map, - keymapper_position,i; + this._lockHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._lockHandles.addLayer( + new L.LockHandle(overlay, i, { draggable: false }) + ); + } - /* instantiate and render keymapper for one instance only*/ - if (this.instance_count === 1 && overlay.options.keymapper !== false) { - keymapper_position = overlay.options.keymapper_position || 'topright'; - map.addControl(new L.DistortableImage.Keymapper({position: keymapper_position})); + this._distortHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); + } + + this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale + for (i = 0; i < 4; i++) { + this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + } + + this._scaleHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } /* bring the selected image into view */ diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 696e3ca41..cc728e82a 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -1,51 +1,60 @@ L.DistortableImage = L.DistortableImage || {}; L.DistortableImage.Edit = L.Handler.extend({ - options: { - opacity: 0.7, - outline: '1px solid red', - keymap: { - 8: '_removeOverlay', // backspace windows / delete mac - 46: '_removeOverlay', // delete windows / delete + fn mac - 20: '_toggleRotate', // CAPS - 27: '_deselect', // esc - 68: '_toggleRotateDistort', // d - 69: '_toggleIsolate', // e - 73: '_toggleIsolate', // i - 74: '_sendUp', // j - 75: '_sendDown', // k - 76: '_toggleLock', // l - 79: '_toggleOutline', // o - 82: '_toggleRotateDistort', // r - 83: '_toggleScale', // s - 84: '_toggleTransparency', // t - } - }, + options: { + opacity: 0.7, + outline: "1px solid red", + keymap: { + 8: "_removeOverlay", // backspace windows / delete mac + 46: "_removeOverlay", // delete windows / delete + fn mac + 27: "_deselect", // esc + 68: "_toggleRotateDistort", // d + 74: "_sendUp", // j + 75: "_sendDown", // k + 76: "_toggleLock", // l + 79: "_toggleOutline", // o + 82: "_toggleRotateDistort", // r + 83: "_toggleScale", // s + 84: "_toggleTransparency" // t + } + }, - initialize: function(overlay) { - this._overlay = overlay; - this._toggledImage = false; + initialize: function(overlay) { + this._overlay = overlay; + this._toggledImage = false; - /* Interaction modes. */ - this._mode = this._overlay.options.mode || 'distort'; - this._transparent = false; - this._outlined = false; + /* Interaction modes. */ + this._mode = this._overlay.options.mode || "distort"; + this._transparent = false; + this._outlined = false; + }, - /* generate instance counts */ - this.instance_count = L.DistortableImage.Edit.prototype.instances = - L.DistortableImage.Edit.prototype.instances ? L.DistortableImage.Edit.prototype.instances + 1 : 1; - }, + /* Run on image selection. */ + addHooks: function() { + var overlay = this._overlay, + map = overlay._map, + i; - /* Run on image selection. */ - addHooks: function() { - var overlay = this._overlay, - map = overlay._map, - keymapper_position,i; + this._lockHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._lockHandles.addLayer( + new L.LockHandle(overlay, i, { draggable: false }) + ); + } + + this._distortHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); + } - /* instantiate and render keymapper for one instance only*/ - if (this.instance_count === 1 && overlay.options.keymapper !== false) { - keymapper_position = overlay.options.keymapper_position || 'topright'; - map.addControl(new L.DistortableImage.Keymapper({position: keymapper_position})); + this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale + for (i = 0; i < 4; i++) { + this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + } + + this._scaleHandles = new L.LayerGroup(); + for (i = 0; i < 4; i++) { + this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } /* bring the selected image into view */ From 8967851978e868505e53aba34d4b0067c1934a0a Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 20 Apr 2019 22:59:24 -0400 Subject: [PATCH 03/18] Both select and index demos use rotateScaleHandles now --- dist/leaflet.distortableimage.js | 965 +++++++++++----------- examples/select.html | 12 - src/DistortableImageOverlay.js | 577 ++++++------- src/edit/DistortableImage.Edit.js | 133 ++- src/edit/DistortableImage.EditToolbar.js | 379 ++++----- src/edit/RotateAndScaleHandle.js | 8 +- test/src/edit/DistortableImageEditSpec.js | 17 +- 7 files changed, 991 insertions(+), 1100 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 27f7806cd..5c7374198 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -140,295 +140,298 @@ L.TrigUtil = { }; L.DistortableImageOverlay = L.ImageOverlay.extend({ - include: L.Mixin.Events, + include: L.Mixin.Events, - options: { - alt: '', - height: 200, + options: { + alt: "", + height: 200, crossOrigin: true, - edgeMinWidth: 500, - }, - - initialize: function(url, options) { - this._toolArray = L.DistortableImage.EditToolbarDefaults; - this._url = url; - this._rotation = this.options.rotation; - L.DistortableImage._options = options; - - L.setOptions(this, options); - }, - - onAdd: function(map) { - /* Copied from L.ImageOverlay */ - this._map = map; - - if (!this._image) { this._initImage(); } - if (!this._events) { this._initEvents(); } - - map._panes.overlayPane.appendChild(this._image); - - map.on('viewreset', this._reset, this); - /* End copied from L.ImageOverlay */ - - /* Use provided corners if available */ - if (this.options.corners) { - this._corners = this.options.corners; - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on('zoomanim', this._animateZoom, this); - } + // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) + edgeMinWidth: 520 + }, - /* This reset happens before image load; it allows - * us to place the image on the map earlier with - * "guessed" dimensions. */ - this._reset(); - } + initialize: function(url, options) { + this._toolArray = L.DistortableImage.EditToolbarDefaults; + this.edgeMinWidth = this.options.edgeMinWidth; + this._url = url; + this._rotation = this.options.rotation; - /* Have to wait for the image to load because - * we need to access its width and height. */ - L.DomEvent.on(this._image, 'load', function() { - this._initImageDimensions(); - this._reset(); - /* Initialize default corners if not already set */ - if (!this._corners) { - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on('zoomanim', this._animateZoom, this); - } - } - }, this); + L.Util.setOptions(this, options); + }, - this.fire('add'); - }, + onAdd: function(map) { + /* Copied from L.ImageOverlay */ + this._map = map; - onRemove: function(map) { - this.fire('remove'); + if (!this._image) { this._initImage(); } + if (!this._events) { this._initEvents(); } - L.ImageOverlay.prototype.onRemove.call(this, map); - }, + map._panes.overlayPane.appendChild(this._image); - _initImage: function () { - L.ImageOverlay.prototype._initImage.call(this); + map.on("viewreset", this._reset, this); + /* End copied from L.ImageOverlay */ - L.extend(this._image, { - alt: this.options.alt - }); - }, + /* Use provided corners if available */ + if (this.options.corners) { + this._corners = this.options.corners; + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } - _addTool: function(tool) { - this._toolArray.push(tool); - L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: this._toolArray - } - }); - }, + /* This reset happens before image load; it allows + * us to place the image on the map earlier with + * "guessed" dimensions. */ + this._reset(); + } - _initImageDimensions: function() { - var map = this._map, + /* Have to wait for the image to load because + * we need to access its width and height. */ + L.DomEvent.on(this._image, "load", function() { + this._initImageDimensions(); + this._reset(); + /* Initialize default corners if not already set */ + if (!this._corners) { + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + } + }, this); - originalImageWidth = L.DomUtil.getStyle(this._image, 'width'), - originalImageHeight = L.DomUtil.getStyle(this._image, 'height'), + this.fire("add"); + }, - aspectRatio = parseInt(originalImageWidth) / parseInt(originalImageHeight), + onRemove: function(map) { + this.fire("remove"); - imageHeight = this.options.height, - imageWidth = parseInt(aspectRatio*imageHeight), + L.ImageOverlay.prototype.onRemove.call(this, map); + }, - center = map.latLngToContainerPoint(map.getCenter()), - offset = new L.Point(imageWidth, imageHeight).divideBy(2); + _initImage: function() { + L.ImageOverlay.prototype._initImage.call(this); - if (this.options.corners) { this._corners = this.options.corners; } - else { - this._corners = [ - map.containerPointToLatLng(center.subtract(offset)), - map.containerPointToLatLng(center.add(new L.Point(offset.x, - offset.y))), - map.containerPointToLatLng(center.add(new L.Point(- offset.x, offset.y))), - map.containerPointToLatLng(center.add(offset)) - ]; - } - }, + L.extend(this._image, { + alt: this.options.alt + }); + }, - _initEvents: function() { - this._events = [ 'click' ]; + _addTool: function(tool) { + this._toolArray.push(tool); + L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: this._toolArray + } + }); + }, - for (var i = 0, l = this._events.length; i < l; i++) { - L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); - } - }, + _initImageDimensions: function() { + var map = this._map, + originalImageWidth = L.DomUtil.getStyle(this._image, "width"), + originalImageHeight = L.DomUtil.getStyle(this._image, "height"), + aspectRatio = + parseInt(originalImageWidth) / parseInt(originalImageHeight), + imageHeight = this.options.height, + imageWidth = parseInt(aspectRatio * imageHeight), + center = map.latLngToContainerPoint(map.getCenter()), + offset = new L.Point(imageWidth, imageHeight).divideBy(2); + + if (this.options.corners) { + this._corners = this.options.corners; + } else { + this._corners = [ + map.containerPointToLatLng(center.subtract(offset)), + map.containerPointToLatLng( + center.add(new L.Point(offset.x, -offset.y)) + ), + map.containerPointToLatLng( + center.add(new L.Point(-offset.x, offset.y)) + ), + map.containerPointToLatLng(center.add(offset)) + ]; + } + }, - /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ - _fireMouseEvent: function(event) { - if (!this.hasEventListeners(event.type)) { return; } + _initEvents: function() { + this._events = ["click"]; - var map = this._map, - containerPoint = map.mouseEventToContainerPoint(event), - layerPoint = map.containerPointToLayerPoint(containerPoint), - latlng = map.layerPointToLatLng(layerPoint); + for (var i = 0, l = this._events.length; i < l; i++) { + L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); + } + }, - this.fire(event.type, { - latlng: latlng, - layerPoint: layerPoint, - containerPoint: containerPoint, - originalEvent: event - }); - }, + /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ + _fireMouseEvent: function(event) { + if (!this.hasEventListeners(event.type)) { return; } - _updateCorner: function(corner, latlng) { - this._corners[corner] = latlng; - this._reset(); - }, + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(event), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); + + this.fire(event.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: event + }); + }, - // fires a reset after all corner positions are updated instead of after each one (above). Use for translating - _updateCorners: function (latlngObj) { - var i = 0; - for (var k in latlngObj) { - this._corners[i] = latlngObj[k]; - i += 1; - } + _updateCorner: function(corner, latlng) { + this._corners[corner] = latlng; + this._reset(); + }, - this._reset(); - }, + // fires a reset after all corner positions are updated instead of after each one (above). Use for translating + _updateCorners: function(latlngObj) { + var i = 0; + for (var k in latlngObj) { + this._corners[i] = latlngObj[k]; + i += 1; + } - _updateCornersFromPoints: function (pointsObj) { - var map = this._map; - var i = 0; - for (var k in pointsObj) { - this._corners[i] = map.layerPointToLatLng(pointsObj[k]); - i += 1; - } + this._reset(); + }, - this._reset(); - }, + _updateCornersFromPoints: function(pointsObj) { + var map = this._map; + var i = 0; + for (var k in pointsObj) { + this._corners[i] = map.layerPointToLatLng(pointsObj[k]); + i += 1; + } - /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ - /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ - _getTranslateString: function (point) { - // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate - // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care - // (same speed either way), Opera 12 doesn't support translate3d + this._reset(); + }, - var is3d = L.Browser.webkit3d, - open = 'translate' + (is3d ? '3d' : '') + '(', - close = (is3d ? ',0' : '') + ')'; + /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ + /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ + _getTranslateString: function(point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d - return open + point.x + 'px,' + point.y + 'px' + close; - }, + var is3d = L.Browser.webkit3d, + open = "translate" + (is3d ? "3d" : "") + "(", + close = (is3d ? ",0" : "") + ")"; - _reset: function() { - var map = this._map, - image = this._image, - latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), + return open + point.x + "px," + point.y + "px" + close; + }, - transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), - topLeft = latLngToLayerPoint(this._corners[0]), + _reset: function() { + var map = this._map, + image = this._image, + latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), + transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), + topLeft = latLngToLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); + /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ + image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; + }, - /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ - image.style[L.DomUtil.TRANSFORM + '-origin'] = "0 0 0"; - }, + /* + * Calculates the transform string that will be correct *at the end* of zooming. + * Leaflet then generates a CSS3 animation between the current transform and + * future transform which makes the transition appear smooth. + */ + _animateZoom: function(event) { + var map = this._map, + image = this._image, + latLngToNewLayerPoint = function(latlng) { + return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); + }, + transformMatrix = this._calculateProjectiveTransform( + latLngToNewLayerPoint + ), + topLeft = latLngToNewLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); + + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; + + if (!L.Browser.gecko) { + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + } + }, - /* - * Calculates the transform string that will be correct *at the end* of zooming. - * Leaflet then generates a CSS3 animation between the current transform and - * future transform which makes the transition appear smooth. - */ - _animateZoom: function(event) { - var map = this._map, - image = this._image, - latLngToNewLayerPoint = function(latlng) { - return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); - }, - - transformMatrix = this._calculateProjectiveTransform(latLngToNewLayerPoint), - topLeft = latLngToNewLayerPoint(this._corners[0]), - - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; - - if (!L.Browser.gecko) { - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); - } - }, + getCorners: function() { + return this._corners; + }, - getCorners: function() { - return this._corners; - }, + getCorner: function(i) { + return this._corners[i]; + }, - getCorner: function(i) { - return this._corners[i]; - }, + /* + * Calculates the centroid of the image. + * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral + */ + getCenter: function(ll2c, c2ll) { + var map = this._map, + latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, + cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, + nw = latLngToCartesian.call(map, this._corners[0]), + ne = latLngToCartesian.call(map, this._corners[1]), + se = latLngToCartesian.call(map, this._corners[2]), + sw = latLngToCartesian.call(map, this._corners[3]), + nmid = nw.add(ne.subtract(nw).divideBy(2)), + smid = sw.add(se.subtract(sw).divideBy(2)); + + return cartesianToLatLng.call( + map, + nmid.add(smid.subtract(nmid).divideBy(2)) + ); + }, - /* - * Calculates the centroid of the image. - * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral - */ - getCenter: function(ll2c, c2ll) { - var map = this._map, - latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, - cartesianToLatLng = c2ll ? c2ll: map.layerPointToLatLng, - nw = latLngToCartesian.call(map, this._corners[0]), - ne = latLngToCartesian.call(map, this._corners[1]), - se = latLngToCartesian.call(map, this._corners[2]), - sw = latLngToCartesian.call(map, this._corners[3]), - - nmid = nw.add(ne.subtract(nw).divideBy(2)), - smid = sw.add(se.subtract(sw).divideBy(2)); - - return cartesianToLatLng.call(map, nmid.add(smid.subtract(nmid).divideBy(2))); - }, + // Use for translation calculations - for translation the delta for 1 corner applies to all 4 + _calcCornerPointDelta: function() { + return this._dragStartPoints[0].subtract(this._dragPoints[0]); + }, - // Use for translation calculations - for translation the delta for 1 corner applies to all 4 - _calcCornerPointDelta: function () { - return this._dragStartPoints[0].subtract(this._dragPoints[0]); - }, + _calcCenterTwoCornerPoints: function(topLeft, topRight) { + var toolPoint = { x: "", y: "" }; - _calcCenterTwoCornerPoints: function (topLeft, topRight) { - var toolPoint = { x: "", y: "" }; + toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; + toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; - toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - - return toolPoint; - }, + return toolPoint; + }, - _calculateProjectiveTransform: function(latLngToCartesian) { - /* Setting reasonable but made-up image defaults - * allow us to place images on the map before - * they've finished downloading. */ - var offset = latLngToCartesian(this._corners[0]), - w = this._image.offsetWidth || 500, - h = this._image.offsetHeight || 375, - c = [], - j; - /* Convert corners to container points (i.e. cartesian coordinates). */ - for (j = 0; j < this._corners.length; j++) { - c.push(latLngToCartesian(this._corners[j])._subtract(offset)); - } + _calculateProjectiveTransform: function(latLngToCartesian) { + /* Setting reasonable but made-up image defaults + * allow us to place images on the map before + * they've finished downloading. */ + var offset = latLngToCartesian(this._corners[0]), + w = this._image.offsetWidth || 500, + h = this._image.offsetHeight || 375, + c = [], + j; + /* Convert corners to container points (i.e. cartesian coordinates). */ + for (j = 0; j < this._corners.length; j++) { + c.push(latLngToCartesian(this._corners[j])._subtract(offset)); + } - /* - * This matrix describes the action of the CSS transform on each corner of the image. - * It maps from the coordinate system centered at the upper left corner of the image - * to the region bounded by the latlngs in this._corners. - * For example: - * 0, 0, c[0].x, c[0].y - * says that the upper-left corner of the image maps to the first latlng in this._corners. - */ - return L.MatrixUtil.general2DProjection( - 0, 0, c[0].x, c[0].y, - w, 0, c[1].x, c[1].y, - 0, h, c[2].x, c[2].y, - w, h, c[3].x, c[3].y - ); - } + /* + * This matrix describes the action of the CSS transform on each corner of the image. + * It maps from the coordinate system centered at the upper left corner of the image + * to the region bounded by the latlngs in this._corners. + * For example: + * 0, 0, c[0].x, c[0].y + * says that the upper-left corner of the image maps to the first latlng in this._corners. + */ + return L.MatrixUtil.general2DProjection( + 0, 0, c[0].x, c[0].y, + w, 0, c[1].x, c[1].y, + 0, h, c[2].x, c[2].y, + w, h, c[3].x, c[3].y + ); + } }); L.distortableImageOverlay = function(id, options) { @@ -647,7 +650,7 @@ L.DistortHandle = L.EditHandle.extend({ L.RotateAndScaleHandle = L.EditHandle.extend({ options: { - TYPE: 'rotate', + TYPE: 'rotateScale', icon: new L.Icon({ iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', iconSize: [32, 32], @@ -669,10 +672,10 @@ L.RotateAndScaleHandle = L.EditHandle.extend({ checks whether the "edgeMinWidth" property is set and tracks the minimum edge length; this enables preventing scaling to zero, but we might also add an overall scale limit */ - if (this._handled.options.hasOwnProperty('edgeMinWidth')){ - var edgeMinWidth = this._handled.options.edgeMinWidth, + if (this._handled.hasOwnProperty('edgeMinWidth')){ + var edgeMinWidth = this._handled.edgeMinWidth, w = L.latLng(overlay._corners[0]).distanceTo(overlay._corners[1]), - h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); + h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { overlay.editing._scaleBy(scale); } @@ -1157,207 +1160,184 @@ L.DistortableImage.Keymapper = L.Control.extend({ L.DistortableImage = L.DistortableImage || {}; var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ - initialize: function(map, overlay, options) { - this._overlay = overlay; - this._map = map; + initialize: function(map, overlay, options) { + this._overlay = overlay; + this._map = map; - LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); - } - }), - - ToggleTransparency = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Toggle Image Transparency', - title: 'Toggle Image Transparency' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleTransparency(); - this.disable(); - } - }), - - ToggleOutline = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Toggle Image Outline', - title: 'Toggle Image Outline' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOutline(); - this.disable(); - } - }), - - RemoveOverlay = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Delete image', - title: 'Delete image' - } - }, - - addHooks: function() { - var map = this._map; - - map.removeLayer(this._overlay); - this._overlay.fire('delete'); - this.disable(); - } - }), - - ToggleEditable = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Lock / Unlock editing', - title: 'Lock / Unlock editing' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleLock(); - this.disable(); - } - }), + LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); + } + }), - ToggleRotateDistort = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var icon = overlay.editing._mode === 'rotate' ? 'image' : 'rotate-left'; + ToggleTransparency = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Toggle Image Transparency', + title: 'Toggle Image Transparency' + }}, - options = options || {}; - options.toolbarIcon = { - html: '', - tooltip: 'Rotate', - title: 'Rotate' - }; + addHooks: function() { + var editing = this._overlay.editing; - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, + editing._toggleTransparency(); + this.disable(); + } + }), - addHooks: function() { - var editing = this._overlay.editing; + ToggleOutline = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Toggle Image Outline', + title: 'Toggle Image Outline' + }}, - editing._toggleRotateDistort(); - editing._showToolbar(); - this.disable(); - } - }), + addHooks: function() { + var editing = this._overlay.editing; + editing._toggleOutline(); + this.disable(); + } + }), - ToggleExport = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Export Image', - title: 'Export Image' - } - }, + RemoveOverlay = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Delete image', + title: 'Delete image' + }}, - addHooks: function() { - var editing = this._overlay.editing; + addHooks: function() { + var map = this._map; - editing._toggleExport(); - this.disable(); - } - }), - - ToggleOrder = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Change order', - title: 'Toggle order' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOrder(); - this.disable(); - } - }), - - EnableEXIF = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Enable EXIF", - title: "Geocode Image" - } - }, - - addHooks: function() { - var image = this._overlay._image; - EXIF.getData(image, L.EXIF(image)); - } - }); + map.removeLayer(this._overlay); + this._overlay.fire('delete'); + this.disable(); + } + }), -// implement a promise polyfill here -window.addEventListener('load', function() { + ToggleEditable = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Lock / Unlock editing', + title: 'Lock / Unlock editing' + }}, - var lazy_interval = setInterval(function() { + addHooks: function() { + var editing = this._overlay.editing; - if (typeof L.DistortableImage._options !== 'undefined') { + editing._toggleLock(); + this.disable(); + } + }), - var toolbarType = L.DistortableImage._options.toolbarType === "Control" ? "Control" : "Popup"; + ToggleRotateScale = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var icon = overlay.editing._mode === 'rotateScale' ? 'image' : 'rotate-left'; - L.DistortableImage.EditToolbar = LeafletToolbar[toolbarType].extend({ - options: { - position: 'topleft', - actions: [ - ToggleTransparency, - RemoveOverlay, - ToggleOutline, - ToggleEditable, - ToggleRotateDistort, - ToggleExport, - ToggleOrder, - EnableEXIF - ] - }, + options = options || {}; + options.toolbarIcon = { + html: '', + tooltip: 'RotateScale', + title: 'RotateScale' + }; - // todo: move to some sort of util class, these methods could be useful in future - _rotateToolbarAngleDeg: function(angle) { - var div = this._container, - divStyle = div.style; + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, - var oldTransform = divStyle.transform; + addHooks: function() { + var editing = this._overlay.editing; - divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; - divStyle.transformOrigin = "1080% 650%"; + editing._toggleRotateScale(); + editing._showToolbar(); + this.disable(); + } + }), - this._rotateToolbarIcons(angle); - }, - _rotateToolbarIcons: function(angle) { - var icons = document.querySelectorAll(".fa"); + ToggleExport = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: 'Export Image', + title: 'Export Image' + } + }, - for (var i = 0; i < icons.length; i++) { - icons.item(i).style.transform = "rotate(" + -angle + "deg)"; - } - }, + addHooks: function () + { + var editing = this._overlay.editing; - }); + editing._toggleExport(); + this.disable(); + } + }), + + ToggleOrder = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: 'Change order', + title: 'Toggle order' + } + }, - clearInterval(lazy_interval); - } + addHooks: function () + { + var editing = this._overlay.editing; + + editing._toggleOrder(); + this.disable(); + } + }), + + EnableEXIF = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: "Enable EXIF", + title: "Geocode Image" + } + }, + + addHooks: function() { + var image = this._overlay._image; + EXIF.getData(image, L.EXIF(image)); + } + }); - }, 100); +L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: [ + ToggleTransparency, + RemoveOverlay, + ToggleOutline, + ToggleEditable, + ToggleRotateScale, + ToggleExport, + EnableEXIF, + ToggleOrder + ] + }, + + // todo: move to some sort of util class, these methods could be useful in future + _rotateToolbarAngleDeg: function(angle) { + var div = this._container, + divStyle = div.style; + + var oldTransform = divStyle.transform; + + divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; + divStyle.transformOrigin = "1080% 650%"; + + this._rotateToolbarIcons(angle); + }, + + _rotateToolbarIcons: function(angle) { + var icons = document.querySelectorAll(".fa"); + + for (var i = 0; i < icons.length; i++) { + icons.item(i).style.transform = "rotate(" + -angle + "deg)"; + } + }, }); @@ -1370,13 +1350,16 @@ L.DistortableImage.Edit = L.Handler.extend({ keymap: { 8: "_removeOverlay", // backspace windows / delete mac 46: "_removeOverlay", // delete windows / delete + fn mac + // 20: "_toggleRotate", // CAPS 27: "_deselect", // esc - 68: "_toggleRotateDistort", // d + 68: "_toggleRotateScale", // d + // 69: "_toggleIsolate", // e + // 73: "_toggleIsolate", // i 74: "_sendUp", // j 75: "_sendDown", // k 76: "_toggleLock", // l 79: "_toggleOutline", // o - 82: "_toggleRotateDistort", // r + 82: "_toggleRotateScale", // r 83: "_toggleScale", // s 84: "_toggleTransparency" // t } @@ -1410,9 +1393,9 @@ L.DistortableImage.Edit = L.Handler.extend({ this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); } - this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale + this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale for (i = 0; i < 4; i++) { - this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); } this._scaleHandles = new L.LayerGroup(); @@ -1420,77 +1403,49 @@ L.DistortableImage.Edit = L.Handler.extend({ this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } - /* bring the selected image into view */ - overlay.bringToFront(); - - this._lockHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._lockHandles.addLayer(new L.LockHandle(overlay, i, { draggable: false })); - } - - this._distortHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); - } - - this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale - for (i = 0; i < 4; i++) { - this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); - } - - this._scaleHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); - } - - this.__rotateHandles = new L.LayerGroup(); // individual rotate - for (i = 0; i < 4; i++) { - this.__rotateHandles.addLayer(new L.RotateHandle(overlay, i)); - } + this._handles = { + 'lock': this._lockHandles, + 'distort': this._distortHandles, + 'rotateScale': this._rotateScaleHandles, + 'scale': this._scaleHandles, + // 'rotateStandalone': this.__rotateHandles + }; - this._handles = { - 'lock': this._lockHandles, - 'distort': this._distortHandles, - 'rotate': this._rotateHandles, - 'scale': this._scaleHandles, - 'rotateStandalone': this.__rotateHandles - }; if (this._mode === 'lock') { - map.addLayer(this._lockHandles); - } else { - this._mode = 'distort'; - map.addLayer(this._distortHandles); - this._distortHandles.eachLayer(function (layer) { - layer.setOpacity(0); - layer.dragging.disable(); - layer.options.draggable = false; - }); - this._enableDragging(); + map.addLayer(this._lockHandles); + } else { + this._mode = 'distort'; + map.addLayer(this._distortHandles); + this._distortHandles.eachLayer(function (layer) { + layer.setOpacity(0); + layer.dragging.disable(); + layer.options.draggable = false; + }); + this._enableDragging(); } this._initToolbar(); - this._overlay._dragStartPoints = { - 0: new L.point(0, 0), - 1: new L.point(0, 0), - 2: new L.point(0, 0), - 3: new L.point(0, 0) - }; + this._overlay._dragStartPoints = { + 0: new L.point(0, 0), + 1: new L.point(0, 0), + 2: new L.point(0, 0), + 3: new L.point(0, 0) + }; - L.DomEvent.on(map, "click", this._deselect, this); - L.DomEvent.on(overlay._image, 'click', this._select, this); + L.DomEvent.on(map, "click", this._deselect, this); + L.DomEvent.on(overlay._image, 'click', this._select, this); - /* Enable hotkeys. */ - L.DomEvent.on(window, 'keydown', this._onKeyDown, this); + /* Enable hotkeys. */ + L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - overlay.fire('select'); - }, + overlay.fire('select'); + }, - /* Run on image deselection. */ - removeHooks: function() { - var overlay = this._overlay, - map = overlay._map; + removeHooks: function () { + var overlay = this._overlay, + map = overlay._map; L.DomEvent.off(map, "click", this._deselect, this); L.DomEvent.off(overlay._image, 'click', this._select, this); @@ -1510,13 +1465,13 @@ L.DistortableImage.Edit = L.Handler.extend({ overlay.fire("deselect"); }, - _initToolbar: function() { + _initToolbar: function () { this._showToolbar(); try { this.toolbar._hide(); this.toolbar._tip.style.opacity = 0; } - catch (e) {} + catch (e) { } }, confirmDelete: function() { @@ -1611,14 +1566,14 @@ L.DistortableImage.Edit = L.Handler.extend({ } }, - _toggleRotateDistort: function() { + _toggleRotateScale: function() { var map = this._overlay._map; map.removeLayer(this._handles[this._mode]); /* Switch mode. */ - if (this._mode === "rotate") { this._mode = "distort"; } - else { this._mode = "rotate"; } + if (this._mode === "rotateScale") { this._mode = "distort"; } + else { this._mode = "rotateScale"; } this._showToolbar(); @@ -1640,18 +1595,18 @@ L.DistortableImage.Edit = L.Handler.extend({ map.addLayer(this._handles[this._mode]); }, - _toggleRotate: function() { - var map = this._overlay._map; - - if (this._mode === "lock") { return; } - - map.removeLayer(this._handles[this._mode]); - this._mode = "rotateStandalone"; + // _toggleRotate: function() { + // var map = this._overlay._map; + + // if (this._mode === "lock") { return; } - this._showToolbar(); + // map.removeLayer(this._handles[this._mode]); + // this._mode = "rotateStandalone"; - map.addLayer(this._handles[this._mode]); - }, + // this._showToolbar(); + + // map.addLayer(this._handles[this._mode]); + // }, _toggleTransparency: function() { var image = this._overlay._image, diff --git a/examples/select.html b/examples/select.html index 108be9cf4..9e55a934f 100644 --- a/examples/select.html +++ b/examples/select.html @@ -54,9 +54,6 @@ L.latLng(51.50,-0.14) ], mode: 'lock', - // HARDCODED: we should find the img w/h dynamically so that it fits the image each time. - edgeMinWidth: 521, - edgeMinHeight: 348, }).addTo(map); // create a second image @@ -68,9 +65,6 @@ L.latLng(51.49,-0.11), L.latLng(51.49,-0.15) ], - // HARDCODED: we should find the img w/h dynamically so that it fits the image each time. (initImageDimensions?) - edgeMinWidth: 521, - edgeMinHeight: 348 }).addTo(map); img3 = L.distortableImageOverlay( @@ -81,9 +75,6 @@ L.latLng(51.49,-0.11), L.latLng(51.49,-0.15) ], - // HARDCODED: we should find the img w/h dynamically so that it fits the image each time. (initImageDimensions?) - edgeMinWidth: 521, - edgeMinHeight: 348 }).addTo(map); img4 = L.distortableImageOverlay( @@ -94,9 +85,6 @@ L.latLng(51.49,-0.11), L.latLng(51.49,-0.15) ], - // HARDCODED: we should find the img w/h dynamically so that it fits the image each time. (initImageDimensions?) - edgeMinWidth: 521, - edgeMinHeight: 348 }).addTo(map); L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); diff --git a/src/DistortableImageOverlay.js b/src/DistortableImageOverlay.js index f1ab2de50..06b0bfd92 100644 --- a/src/DistortableImageOverlay.js +++ b/src/DistortableImageOverlay.js @@ -1,293 +1,296 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ - include: L.Mixin.Events, + include: L.Mixin.Events, - options: { - alt: '', - height: 200, + options: { + alt: "", + height: 200, crossOrigin: true, - edgeMinWidth: 500, - }, - - initialize: function(url, options) { - this._toolArray = L.DistortableImage.EditToolbarDefaults; - this._url = url; - this._rotation = this.options.rotation; - L.DistortableImage._options = options; - - L.setOptions(this, options); - }, - - onAdd: function(map) { - /* Copied from L.ImageOverlay */ - this._map = map; - - if (!this._image) { this._initImage(); } - if (!this._events) { this._initEvents(); } - - map._panes.overlayPane.appendChild(this._image); - - map.on('viewreset', this._reset, this); - /* End copied from L.ImageOverlay */ - - /* Use provided corners if available */ - if (this.options.corners) { - this._corners = this.options.corners; - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on('zoomanim', this._animateZoom, this); - } - - /* This reset happens before image load; it allows - * us to place the image on the map earlier with - * "guessed" dimensions. */ - this._reset(); - } - - /* Have to wait for the image to load because - * we need to access its width and height. */ - L.DomEvent.on(this._image, 'load', function() { - this._initImageDimensions(); - this._reset(); - /* Initialize default corners if not already set */ - if (!this._corners) { - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on('zoomanim', this._animateZoom, this); - } - } - }, this); - - this.fire('add'); - }, - - onRemove: function(map) { - this.fire('remove'); - - L.ImageOverlay.prototype.onRemove.call(this, map); - }, - - _initImage: function () { - L.ImageOverlay.prototype._initImage.call(this); - - L.extend(this._image, { - alt: this.options.alt - }); - }, - - _addTool: function(tool) { - this._toolArray.push(tool); - L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: this._toolArray - } - }); - }, - - _initImageDimensions: function() { - var map = this._map, - - originalImageWidth = L.DomUtil.getStyle(this._image, 'width'), - originalImageHeight = L.DomUtil.getStyle(this._image, 'height'), - - aspectRatio = parseInt(originalImageWidth) / parseInt(originalImageHeight), - - imageHeight = this.options.height, - imageWidth = parseInt(aspectRatio*imageHeight), - - center = map.latLngToContainerPoint(map.getCenter()), - offset = new L.Point(imageWidth, imageHeight).divideBy(2); - - if (this.options.corners) { this._corners = this.options.corners; } - else { - this._corners = [ - map.containerPointToLatLng(center.subtract(offset)), - map.containerPointToLatLng(center.add(new L.Point(offset.x, - offset.y))), - map.containerPointToLatLng(center.add(new L.Point(- offset.x, offset.y))), - map.containerPointToLatLng(center.add(offset)) - ]; - } - }, - - _initEvents: function() { - this._events = [ 'click' ]; - - for (var i = 0, l = this._events.length; i < l; i++) { - L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); - } - }, - - /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ - _fireMouseEvent: function(event) { - if (!this.hasEventListeners(event.type)) { return; } - - var map = this._map, - containerPoint = map.mouseEventToContainerPoint(event), - layerPoint = map.containerPointToLayerPoint(containerPoint), - latlng = map.layerPointToLatLng(layerPoint); - - this.fire(event.type, { - latlng: latlng, - layerPoint: layerPoint, - containerPoint: containerPoint, - originalEvent: event - }); - }, - - _updateCorner: function(corner, latlng) { - this._corners[corner] = latlng; - this._reset(); - }, - - // fires a reset after all corner positions are updated instead of after each one (above). Use for translating - _updateCorners: function (latlngObj) { - var i = 0; - for (var k in latlngObj) { - this._corners[i] = latlngObj[k]; - i += 1; - } - - this._reset(); - }, - - _updateCornersFromPoints: function (pointsObj) { - var map = this._map; - var i = 0; - for (var k in pointsObj) { - this._corners[i] = map.layerPointToLatLng(pointsObj[k]); - i += 1; - } - - this._reset(); - }, - - /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ - /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ - _getTranslateString: function (point) { - // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate - // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care - // (same speed either way), Opera 12 doesn't support translate3d - - var is3d = L.Browser.webkit3d, - open = 'translate' + (is3d ? '3d' : '') + '(', - close = (is3d ? ',0' : '') + ')'; - - return open + point.x + 'px,' + point.y + 'px' + close; - }, - - _reset: function() { - var map = this._map, - image = this._image, - latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), - - transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), - topLeft = latLngToLayerPoint(this._corners[0]), - - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; - - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); - - /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ - image.style[L.DomUtil.TRANSFORM + '-origin'] = "0 0 0"; - }, - - /* - * Calculates the transform string that will be correct *at the end* of zooming. - * Leaflet then generates a CSS3 animation between the current transform and - * future transform which makes the transition appear smooth. - */ - _animateZoom: function(event) { - var map = this._map, - image = this._image, - latLngToNewLayerPoint = function(latlng) { - return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); - }, - - transformMatrix = this._calculateProjectiveTransform(latLngToNewLayerPoint), - topLeft = latLngToNewLayerPoint(this._corners[0]), - - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; - - if (!L.Browser.gecko) { - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); - } - }, - - getCorners: function() { - return this._corners; - }, - - getCorner: function(i) { - return this._corners[i]; - }, - - /* - * Calculates the centroid of the image. - * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral - */ - getCenter: function(ll2c, c2ll) { - var map = this._map, - latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, - cartesianToLatLng = c2ll ? c2ll: map.layerPointToLatLng, - nw = latLngToCartesian.call(map, this._corners[0]), - ne = latLngToCartesian.call(map, this._corners[1]), - se = latLngToCartesian.call(map, this._corners[2]), - sw = latLngToCartesian.call(map, this._corners[3]), - - nmid = nw.add(ne.subtract(nw).divideBy(2)), - smid = sw.add(se.subtract(sw).divideBy(2)); - - return cartesianToLatLng.call(map, nmid.add(smid.subtract(nmid).divideBy(2))); - }, - - // Use for translation calculations - for translation the delta for 1 corner applies to all 4 - _calcCornerPointDelta: function () { - return this._dragStartPoints[0].subtract(this._dragPoints[0]); - }, - - _calcCenterTwoCornerPoints: function (topLeft, topRight) { - var toolPoint = { x: "", y: "" }; - - toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; - toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - - return toolPoint; - }, - - _calculateProjectiveTransform: function(latLngToCartesian) { - /* Setting reasonable but made-up image defaults - * allow us to place images on the map before - * they've finished downloading. */ - var offset = latLngToCartesian(this._corners[0]), - w = this._image.offsetWidth || 500, - h = this._image.offsetHeight || 375, - c = [], - j; - /* Convert corners to container points (i.e. cartesian coordinates). */ - for (j = 0; j < this._corners.length; j++) { - c.push(latLngToCartesian(this._corners[j])._subtract(offset)); - } - - /* - * This matrix describes the action of the CSS transform on each corner of the image. - * It maps from the coordinate system centered at the upper left corner of the image - * to the region bounded by the latlngs in this._corners. - * For example: - * 0, 0, c[0].x, c[0].y - * says that the upper-left corner of the image maps to the first latlng in this._corners. - */ - return L.MatrixUtil.general2DProjection( - 0, 0, c[0].x, c[0].y, - w, 0, c[1].x, c[1].y, - 0, h, c[2].x, c[2].y, - w, h, c[3].x, c[3].y - ); - } + // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) + edgeMinWidth: 520 + }, + + initialize: function(url, options) { + this._toolArray = L.DistortableImage.EditToolbarDefaults; + this.edgeMinWidth = this.options.edgeMinWidth; + this._url = url; + this._rotation = this.options.rotation; + + L.Util.setOptions(this, options); + }, + + onAdd: function(map) { + /* Copied from L.ImageOverlay */ + this._map = map; + + if (!this._image) { this._initImage(); } + if (!this._events) { this._initEvents(); } + + map._panes.overlayPane.appendChild(this._image); + + map.on("viewreset", this._reset, this); + /* End copied from L.ImageOverlay */ + + /* Use provided corners if available */ + if (this.options.corners) { + this._corners = this.options.corners; + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + + /* This reset happens before image load; it allows + * us to place the image on the map earlier with + * "guessed" dimensions. */ + this._reset(); + } + + /* Have to wait for the image to load because + * we need to access its width and height. */ + L.DomEvent.on(this._image, "load", function() { + this._initImageDimensions(); + this._reset(); + /* Initialize default corners if not already set */ + if (!this._corners) { + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + } + }, this); + + this.fire("add"); + }, + + onRemove: function(map) { + this.fire("remove"); + + L.ImageOverlay.prototype.onRemove.call(this, map); + }, + + _initImage: function() { + L.ImageOverlay.prototype._initImage.call(this); + + L.extend(this._image, { + alt: this.options.alt + }); + }, + + _addTool: function(tool) { + this._toolArray.push(tool); + L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: this._toolArray + } + }); + }, + + _initImageDimensions: function() { + var map = this._map, + originalImageWidth = L.DomUtil.getStyle(this._image, "width"), + originalImageHeight = L.DomUtil.getStyle(this._image, "height"), + aspectRatio = + parseInt(originalImageWidth) / parseInt(originalImageHeight), + imageHeight = this.options.height, + imageWidth = parseInt(aspectRatio * imageHeight), + center = map.latLngToContainerPoint(map.getCenter()), + offset = new L.Point(imageWidth, imageHeight).divideBy(2); + + if (this.options.corners) { + this._corners = this.options.corners; + } else { + this._corners = [ + map.containerPointToLatLng(center.subtract(offset)), + map.containerPointToLatLng( + center.add(new L.Point(offset.x, -offset.y)) + ), + map.containerPointToLatLng( + center.add(new L.Point(-offset.x, offset.y)) + ), + map.containerPointToLatLng(center.add(offset)) + ]; + } + }, + + _initEvents: function() { + this._events = ["click"]; + + for (var i = 0, l = this._events.length; i < l; i++) { + L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); + } + }, + + /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ + _fireMouseEvent: function(event) { + if (!this.hasEventListeners(event.type)) { return; } + + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(event), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); + + this.fire(event.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: event + }); + }, + + _updateCorner: function(corner, latlng) { + this._corners[corner] = latlng; + this._reset(); + }, + + // fires a reset after all corner positions are updated instead of after each one (above). Use for translating + _updateCorners: function(latlngObj) { + var i = 0; + for (var k in latlngObj) { + this._corners[i] = latlngObj[k]; + i += 1; + } + + this._reset(); + }, + + _updateCornersFromPoints: function(pointsObj) { + var map = this._map; + var i = 0; + for (var k in pointsObj) { + this._corners[i] = map.layerPointToLatLng(pointsObj[k]); + i += 1; + } + + this._reset(); + }, + + /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ + /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ + _getTranslateString: function(point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d + + var is3d = L.Browser.webkit3d, + open = "translate" + (is3d ? "3d" : "") + "(", + close = (is3d ? ",0" : "") + ")"; + + return open + point.x + "px," + point.y + "px" + close; + }, + + _reset: function() { + var map = this._map, + image = this._image, + latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), + transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), + topLeft = latLngToLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); + + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; + + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + + /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ + image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; + }, + + /* + * Calculates the transform string that will be correct *at the end* of zooming. + * Leaflet then generates a CSS3 animation between the current transform and + * future transform which makes the transition appear smooth. + */ + _animateZoom: function(event) { + var map = this._map, + image = this._image, + latLngToNewLayerPoint = function(latlng) { + return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); + }, + transformMatrix = this._calculateProjectiveTransform( + latLngToNewLayerPoint + ), + topLeft = latLngToNewLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); + + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; + + if (!L.Browser.gecko) { + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + } + }, + + getCorners: function() { + return this._corners; + }, + + getCorner: function(i) { + return this._corners[i]; + }, + + /* + * Calculates the centroid of the image. + * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral + */ + getCenter: function(ll2c, c2ll) { + var map = this._map, + latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, + cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, + nw = latLngToCartesian.call(map, this._corners[0]), + ne = latLngToCartesian.call(map, this._corners[1]), + se = latLngToCartesian.call(map, this._corners[2]), + sw = latLngToCartesian.call(map, this._corners[3]), + nmid = nw.add(ne.subtract(nw).divideBy(2)), + smid = sw.add(se.subtract(sw).divideBy(2)); + + return cartesianToLatLng.call( + map, + nmid.add(smid.subtract(nmid).divideBy(2)) + ); + }, + + // Use for translation calculations - for translation the delta for 1 corner applies to all 4 + _calcCornerPointDelta: function() { + return this._dragStartPoints[0].subtract(this._dragPoints[0]); + }, + + _calcCenterTwoCornerPoints: function(topLeft, topRight) { + var toolPoint = { x: "", y: "" }; + + toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; + toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; + + return toolPoint; + }, + + _calculateProjectiveTransform: function(latLngToCartesian) { + /* Setting reasonable but made-up image defaults + * allow us to place images on the map before + * they've finished downloading. */ + var offset = latLngToCartesian(this._corners[0]), + w = this._image.offsetWidth || 500, + h = this._image.offsetHeight || 375, + c = [], + j; + /* Convert corners to container points (i.e. cartesian coordinates). */ + for (j = 0; j < this._corners.length; j++) { + c.push(latLngToCartesian(this._corners[j])._subtract(offset)); + } + + /* + * This matrix describes the action of the CSS transform on each corner of the image. + * It maps from the coordinate system centered at the upper left corner of the image + * to the region bounded by the latlngs in this._corners. + * For example: + * 0, 0, c[0].x, c[0].y + * says that the upper-left corner of the image maps to the first latlng in this._corners. + */ + return L.MatrixUtil.general2DProjection( + 0, 0, c[0].x, c[0].y, + w, 0, c[1].x, c[1].y, + 0, h, c[2].x, c[2].y, + w, h, c[3].x, c[3].y + ); + } }); L.distortableImageOverlay = function(id, options) { diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index cc728e82a..efc785fa2 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -7,13 +7,16 @@ L.DistortableImage.Edit = L.Handler.extend({ keymap: { 8: "_removeOverlay", // backspace windows / delete mac 46: "_removeOverlay", // delete windows / delete + fn mac + // 20: "_toggleRotate", // CAPS 27: "_deselect", // esc - 68: "_toggleRotateDistort", // d + 68: "_toggleRotateScale", // d + // 69: "_toggleIsolate", // e + // 73: "_toggleIsolate", // i 74: "_sendUp", // j 75: "_sendDown", // k 76: "_toggleLock", // l 79: "_toggleOutline", // o - 82: "_toggleRotateDistort", // r + 82: "_toggleRotateScale", // r 83: "_toggleScale", // s 84: "_toggleTransparency" // t } @@ -47,9 +50,9 @@ L.DistortableImage.Edit = L.Handler.extend({ this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); } - this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale + this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale for (i = 0; i < 4; i++) { - this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); } this._scaleHandles = new L.LayerGroup(); @@ -57,77 +60,49 @@ L.DistortableImage.Edit = L.Handler.extend({ this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } - /* bring the selected image into view */ - overlay.bringToFront(); - - this._lockHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._lockHandles.addLayer(new L.LockHandle(overlay, i, { draggable: false })); - } - - this._distortHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); - } - - this._rotateHandles = new L.LayerGroup(); // handle includes rotate AND scale - for (i = 0; i < 4; i++) { - this._rotateHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); - } - - this._scaleHandles = new L.LayerGroup(); - for (i = 0; i < 4; i++) { - this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); - } - - this.__rotateHandles = new L.LayerGroup(); // individual rotate - for (i = 0; i < 4; i++) { - this.__rotateHandles.addLayer(new L.RotateHandle(overlay, i)); - } + this._handles = { + 'lock': this._lockHandles, + 'distort': this._distortHandles, + 'rotateScale': this._rotateScaleHandles, + 'scale': this._scaleHandles, + // 'rotateStandalone': this.__rotateHandles + }; - this._handles = { - 'lock': this._lockHandles, - 'distort': this._distortHandles, - 'rotate': this._rotateHandles, - 'scale': this._scaleHandles, - 'rotateStandalone': this.__rotateHandles - }; if (this._mode === 'lock') { - map.addLayer(this._lockHandles); - } else { - this._mode = 'distort'; - map.addLayer(this._distortHandles); - this._distortHandles.eachLayer(function (layer) { - layer.setOpacity(0); - layer.dragging.disable(); - layer.options.draggable = false; - }); - this._enableDragging(); + map.addLayer(this._lockHandles); + } else { + this._mode = 'distort'; + map.addLayer(this._distortHandles); + this._distortHandles.eachLayer(function (layer) { + layer.setOpacity(0); + layer.dragging.disable(); + layer.options.draggable = false; + }); + this._enableDragging(); } this._initToolbar(); - this._overlay._dragStartPoints = { - 0: new L.point(0, 0), - 1: new L.point(0, 0), - 2: new L.point(0, 0), - 3: new L.point(0, 0) - }; + this._overlay._dragStartPoints = { + 0: new L.point(0, 0), + 1: new L.point(0, 0), + 2: new L.point(0, 0), + 3: new L.point(0, 0) + }; - L.DomEvent.on(map, "click", this._deselect, this); - L.DomEvent.on(overlay._image, 'click', this._select, this); + L.DomEvent.on(map, "click", this._deselect, this); + L.DomEvent.on(overlay._image, 'click', this._select, this); - /* Enable hotkeys. */ - L.DomEvent.on(window, 'keydown', this._onKeyDown, this); + /* Enable hotkeys. */ + L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - overlay.fire('select'); - }, + overlay.fire('select'); + }, - /* Run on image deselection. */ - removeHooks: function() { - var overlay = this._overlay, - map = overlay._map; + removeHooks: function () { + var overlay = this._overlay, + map = overlay._map; L.DomEvent.off(map, "click", this._deselect, this); L.DomEvent.off(overlay._image, 'click', this._select, this); @@ -147,13 +122,13 @@ L.DistortableImage.Edit = L.Handler.extend({ overlay.fire("deselect"); }, - _initToolbar: function() { + _initToolbar: function () { this._showToolbar(); try { this.toolbar._hide(); this.toolbar._tip.style.opacity = 0; } - catch (e) {} + catch (e) { } }, confirmDelete: function() { @@ -248,14 +223,14 @@ L.DistortableImage.Edit = L.Handler.extend({ } }, - _toggleRotateDistort: function() { + _toggleRotateScale: function() { var map = this._overlay._map; map.removeLayer(this._handles[this._mode]); /* Switch mode. */ - if (this._mode === "rotate") { this._mode = "distort"; } - else { this._mode = "rotate"; } + if (this._mode === "rotateScale") { this._mode = "distort"; } + else { this._mode = "rotateScale"; } this._showToolbar(); @@ -277,18 +252,18 @@ L.DistortableImage.Edit = L.Handler.extend({ map.addLayer(this._handles[this._mode]); }, - _toggleRotate: function() { - var map = this._overlay._map; - - if (this._mode === "lock") { return; } - - map.removeLayer(this._handles[this._mode]); - this._mode = "rotateStandalone"; + // _toggleRotate: function() { + // var map = this._overlay._map; + + // if (this._mode === "lock") { return; } - this._showToolbar(); + // map.removeLayer(this._handles[this._mode]); + // this._mode = "rotateStandalone"; - map.addLayer(this._handles[this._mode]); - }, + // this._showToolbar(); + + // map.addLayer(this._handles[this._mode]); + // }, _toggleTransparency: function() { var image = this._overlay._image, diff --git a/src/edit/DistortableImage.EditToolbar.js b/src/edit/DistortableImage.EditToolbar.js index bac22a50c..2cb2af2da 100644 --- a/src/edit/DistortableImage.EditToolbar.js +++ b/src/edit/DistortableImage.EditToolbar.js @@ -1,206 +1,183 @@ L.DistortableImage = L.DistortableImage || {}; var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ - initialize: function(map, overlay, options) { - this._overlay = overlay; - this._map = map; - - LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); - } - }), - - ToggleTransparency = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Toggle Image Transparency', - title: 'Toggle Image Transparency' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleTransparency(); - this.disable(); - } - }), - - ToggleOutline = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Toggle Image Outline', - title: 'Toggle Image Outline' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOutline(); - this.disable(); - } - }), - - RemoveOverlay = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Delete image', - title: 'Delete image' - } - }, - - addHooks: function() { - var map = this._map; - - map.removeLayer(this._overlay); - this._overlay.fire('delete'); - this.disable(); - } - }), - - ToggleEditable = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Lock / Unlock editing', - title: 'Lock / Unlock editing' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleLock(); - this.disable(); - } - }), - - ToggleRotateDistort = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var icon = overlay.editing._mode === 'rotate' ? 'image' : 'rotate-left'; - - options = options || {}; - options.toolbarIcon = { - html: '', - tooltip: 'Rotate', - title: 'Rotate' - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleRotateDistort(); - editing._showToolbar(); - this.disable(); - } - }), - - - ToggleExport = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Export Image', - title: 'Export Image' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleExport(); - this.disable(); - } - }), - - ToggleOrder = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: 'Change order', - title: 'Toggle order' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOrder(); - this.disable(); - } - }), - - EnableEXIF = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: '', - tooltip: "Enable EXIF", - title: "Geocode Image" - } - }, - - addHooks: function() { - var image = this._overlay._image; - EXIF.getData(image, L.EXIF(image)); - } - }); - -// implement a promise polyfill here -window.addEventListener('load', function() { - - var lazy_interval = setInterval(function() { - - if (typeof L.DistortableImage._options !== 'undefined') { - - var toolbarType = L.DistortableImage._options.toolbarType === "Control" ? "Control" : "Popup"; - - L.DistortableImage.EditToolbar = LeafletToolbar[toolbarType].extend({ - options: { - position: 'topleft', - actions: [ - ToggleTransparency, - RemoveOverlay, - ToggleOutline, - ToggleEditable, - ToggleRotateDistort, - ToggleExport, - ToggleOrder, - EnableEXIF - ] - }, - - // todo: move to some sort of util class, these methods could be useful in future - _rotateToolbarAngleDeg: function(angle) { - var div = this._container, - divStyle = div.style; - - var oldTransform = divStyle.transform; - - divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; - divStyle.transformOrigin = "1080% 650%"; - - this._rotateToolbarIcons(angle); - }, - - _rotateToolbarIcons: function(angle) { - var icons = document.querySelectorAll(".fa"); - - for (var i = 0; i < icons.length; i++) { - icons.item(i).style.transform = "rotate(" + -angle + "deg)"; - } - }, - - }); - - clearInterval(lazy_interval); - } - - }, 100); + initialize: function(map, overlay, options) { + this._overlay = overlay; + this._map = map; + + LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); + } + }), + + ToggleTransparency = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Toggle Image Transparency', + title: 'Toggle Image Transparency' + }}, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleTransparency(); + this.disable(); + } + }), + + ToggleOutline = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Toggle Image Outline', + title: 'Toggle Image Outline' + }}, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleOutline(); + this.disable(); + } + }), + + RemoveOverlay = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Delete image', + title: 'Delete image' + }}, + + addHooks: function() { + var map = this._map; + + map.removeLayer(this._overlay); + this._overlay.fire('delete'); + this.disable(); + } + }), + + ToggleEditable = EditOverlayAction.extend({ + options: { toolbarIcon: { + html: '', + tooltip: 'Lock / Unlock editing', + title: 'Lock / Unlock editing' + }}, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleLock(); + this.disable(); + } + }), + + ToggleRotateScale = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var icon = overlay.editing._mode === 'rotateScale' ? 'image' : 'rotate-left'; + + options = options || {}; + options.toolbarIcon = { + html: '', + tooltip: 'RotateScale', + title: 'RotateScale' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleRotateScale(); + editing._showToolbar(); + this.disable(); + } + }), + + + ToggleExport = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: 'Export Image', + title: 'Export Image' + } + }, + + addHooks: function () + { + var editing = this._overlay.editing; + + editing._toggleExport(); + this.disable(); + } + }), + + ToggleOrder = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: 'Change order', + title: 'Toggle order' + } + }, + + addHooks: function () + { + var editing = this._overlay.editing; + + editing._toggleOrder(); + this.disable(); + } + }), + + EnableEXIF = EditOverlayAction.extend({ + options: { + toolbarIcon: { + html: '', + tooltip: "Enable EXIF", + title: "Geocode Image" + } + }, + + addHooks: function() { + var image = this._overlay._image; + EXIF.getData(image, L.EXIF(image)); + } + }); + +L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: [ + ToggleTransparency, + RemoveOverlay, + ToggleOutline, + ToggleEditable, + ToggleRotateScale, + ToggleExport, + EnableEXIF, + ToggleOrder + ] + }, + + // todo: move to some sort of util class, these methods could be useful in future + _rotateToolbarAngleDeg: function(angle) { + var div = this._container, + divStyle = div.style; + + var oldTransform = divStyle.transform; + + divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; + divStyle.transformOrigin = "1080% 650%"; + + this._rotateToolbarIcons(angle); + }, + + _rotateToolbarIcons: function(angle) { + var icons = document.querySelectorAll(".fa"); + + for (var i = 0; i < icons.length; i++) { + icons.item(i).style.transform = "rotate(" + -angle + "deg)"; + } + }, }); diff --git a/src/edit/RotateAndScaleHandle.js b/src/edit/RotateAndScaleHandle.js index 794e477b2..73c0bd0db 100644 --- a/src/edit/RotateAndScaleHandle.js +++ b/src/edit/RotateAndScaleHandle.js @@ -1,6 +1,6 @@ L.RotateAndScaleHandle = L.EditHandle.extend({ options: { - TYPE: 'rotate', + TYPE: 'rotateScale', icon: new L.Icon({ iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', iconSize: [32, 32], @@ -22,10 +22,10 @@ L.RotateAndScaleHandle = L.EditHandle.extend({ checks whether the "edgeMinWidth" property is set and tracks the minimum edge length; this enables preventing scaling to zero, but we might also add an overall scale limit */ - if (this._handled.options.hasOwnProperty('edgeMinWidth')){ - var edgeMinWidth = this._handled.options.edgeMinWidth, + if (this._handled.hasOwnProperty('edgeMinWidth')){ + var edgeMinWidth = this._handled.edgeMinWidth, w = L.latLng(overlay._corners[0]).distanceTo(overlay._corners[1]), - h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); + h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { overlay.editing._scaleBy(scale); } diff --git a/test/src/edit/DistortableImageEditSpec.js b/test/src/edit/DistortableImageEditSpec.js index a39d01e5d..af1f50b01 100644 --- a/test/src/edit/DistortableImageEditSpec.js +++ b/test/src/edit/DistortableImageEditSpec.js @@ -5,7 +5,7 @@ describe("L.DistortableImage.Edit", function() { beforeEach(function(done) { map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); - overlay = L.distortableImageOverlay('/examples/example.png', { + overlay = new L.DistortableImageOverlay('/examples/example.png', { corners: [ new L.LatLng(41.7934, -87.6052), new L.LatLng(41.7934, -87.5852), @@ -15,16 +15,9 @@ describe("L.DistortableImage.Edit", function() { }).addTo(map); /* Forces the image to load before any tests are run. */ - L.DomEvent.on(overlay._image, 'load', function() { - overlay.editing.enable(); - done(); - }); + L.DomEvent.on(overlay._image, 'load', function() { done (); }); }); - afterEach(function () { - L.DomUtil.remove(overlay); - }); - it("Should be initialized along with each instance of L.DistortableImageOverlay.", function() { expect(overlay.editing).to.be.an.instanceOf(L.DistortableImage.Edit); }); @@ -41,11 +34,11 @@ describe("L.DistortableImage.Edit", function() { overlay.editing._distortHandles.eachLayer(function(handle) { expect(handle.getLatLng()).to.be.closeToLatLng(corners[handle._corner]); }); - - overlay.editing._toggleRotateDistort(); + + overlay.editing._toggleRotateScale(); /* After we toggle modes, the rotateHandles are on the map and should be synced. */ - overlay.editing._rotateHandles.eachLayer(function(handle) { + overlay.editing._rotateScaleHandles.eachLayer(function(handle) { expect(handle.getLatLng()).to.be.closeToLatLng(corners[handle._corner]); }); }); From 5f48cd2644ccb536bd08d64ce9c70658961406be Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 20 Apr 2019 23:07:26 -0400 Subject: [PATCH 04/18] Keep regular rotate --- dist/leaflet.distortableimage.js | 42 ++++++++++++++++++------------- src/edit/DistortableImage.Edit.js | 42 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 36 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 5c7374198..569c7cd62 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1353,14 +1353,12 @@ L.DistortableImage.Edit = L.Handler.extend({ // 20: "_toggleRotate", // CAPS 27: "_deselect", // esc 68: "_toggleRotateScale", // d - // 69: "_toggleIsolate", // e - // 73: "_toggleIsolate", // i 74: "_sendUp", // j 75: "_sendDown", // k 76: "_toggleLock", // l 79: "_toggleOutline", // o 82: "_toggleRotateScale", // r - 83: "_toggleScale", // s + // 83: "_toggleScale", // s 84: "_toggleTransparency" // t } }, @@ -1393,9 +1391,9 @@ L.DistortableImage.Edit = L.Handler.extend({ this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); } - this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale + this._rotateHandles = new L.LayerGroup(); // individual rotate for (i = 0; i < 4; i++) { - this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateHandles.addLayer(new L.RotateHandle(overlay, i)); } this._scaleHandles = new L.LayerGroup(); @@ -1403,12 +1401,17 @@ L.DistortableImage.Edit = L.Handler.extend({ this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } + this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale + for (i = 0; i < 4; i++) { + this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + } + this._handles = { - 'lock': this._lockHandles, - 'distort': this._distortHandles, - 'rotateScale': this._rotateScaleHandles, - 'scale': this._scaleHandles, - // 'rotateStandalone': this.__rotateHandles + lock: this._lockHandles, + distort: this._distortHandles, + rotateScale: this._rotateScaleHandles, + scale: this._scaleHandles, + rotate: this._rotateHandles }; @@ -1569,6 +1572,8 @@ L.DistortableImage.Edit = L.Handler.extend({ _toggleRotateScale: function() { var map = this._overlay._map; + if (this._mode === "lock") { return; } + map.removeLayer(this._handles[this._mode]); /* Switch mode. */ @@ -1595,18 +1600,19 @@ L.DistortableImage.Edit = L.Handler.extend({ map.addLayer(this._handles[this._mode]); }, - // _toggleRotate: function() { - // var map = this._overlay._map; + _toggleRotate: function() { + var map = this._overlay._map; - // if (this._mode === "lock") { return; } + if (this._mode === "lock") { return; } - // map.removeLayer(this._handles[this._mode]); - // this._mode = "rotateStandalone"; + map.removeLayer(this._handles[this._mode]); + if (this._mode === "rotate") { this._mode = "distort"; } + else { this._mode = "rotate"; } - // this._showToolbar(); + this._showToolbar(); - // map.addLayer(this._handles[this._mode]); - // }, + map.addLayer(this._handles[this._mode]); + }, _toggleTransparency: function() { var image = this._overlay._image, diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index efc785fa2..80660aaa7 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -10,14 +10,12 @@ L.DistortableImage.Edit = L.Handler.extend({ // 20: "_toggleRotate", // CAPS 27: "_deselect", // esc 68: "_toggleRotateScale", // d - // 69: "_toggleIsolate", // e - // 73: "_toggleIsolate", // i 74: "_sendUp", // j 75: "_sendDown", // k 76: "_toggleLock", // l 79: "_toggleOutline", // o 82: "_toggleRotateScale", // r - 83: "_toggleScale", // s + // 83: "_toggleScale", // s 84: "_toggleTransparency" // t } }, @@ -50,9 +48,9 @@ L.DistortableImage.Edit = L.Handler.extend({ this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); } - this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale + this._rotateHandles = new L.LayerGroup(); // individual rotate for (i = 0; i < 4; i++) { - this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateHandles.addLayer(new L.RotateHandle(overlay, i)); } this._scaleHandles = new L.LayerGroup(); @@ -60,12 +58,17 @@ L.DistortableImage.Edit = L.Handler.extend({ this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); } + this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale + for (i = 0; i < 4; i++) { + this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + } + this._handles = { - 'lock': this._lockHandles, - 'distort': this._distortHandles, - 'rotateScale': this._rotateScaleHandles, - 'scale': this._scaleHandles, - // 'rotateStandalone': this.__rotateHandles + lock: this._lockHandles, + distort: this._distortHandles, + rotateScale: this._rotateScaleHandles, + scale: this._scaleHandles, + rotate: this._rotateHandles }; @@ -226,6 +229,8 @@ L.DistortableImage.Edit = L.Handler.extend({ _toggleRotateScale: function() { var map = this._overlay._map; + if (this._mode === "lock") { return; } + map.removeLayer(this._handles[this._mode]); /* Switch mode. */ @@ -252,18 +257,19 @@ L.DistortableImage.Edit = L.Handler.extend({ map.addLayer(this._handles[this._mode]); }, - // _toggleRotate: function() { - // var map = this._overlay._map; + _toggleRotate: function() { + var map = this._overlay._map; - // if (this._mode === "lock") { return; } + if (this._mode === "lock") { return; } - // map.removeLayer(this._handles[this._mode]); - // this._mode = "rotateStandalone"; + map.removeLayer(this._handles[this._mode]); + if (this._mode === "rotate") { this._mode = "distort"; } + else { this._mode = "rotate"; } - // this._showToolbar(); + this._showToolbar(); - // map.addLayer(this._handles[this._mode]); - // }, + map.addLayer(this._handles[this._mode]); + }, _toggleTransparency: function() { var image = this._overlay._image, From 93bdcd82fa09898ba629f84ccf35a13024f9e0ba Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 20 Apr 2019 23:12:24 -0400 Subject: [PATCH 05/18] Update some naming conventions --- Gruntfile.js | 27 +- dist/leaflet.distortableimage.js | 1065 ++++++++--------- src/edit/DistortableImage.Edit.js | 2 +- ...AndScaleHandle.js => RotateScaleHandle.js} | 2 +- test/karma.conf.js | 2 +- ...HandleSpec.js => RotateScaleHandleSpec.js} | 4 +- 6 files changed, 539 insertions(+), 563 deletions(-) rename src/edit/{RotateAndScaleHandle.js => RotateScaleHandle.js} (98%) rename test/src/edit/{RotateAndScaleHandleSpec.js => RotateScaleHandleSpec.js} (90%) diff --git a/Gruntfile.js b/Gruntfile.js index 6a5ea87ce..b94118096 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -78,20 +78,19 @@ module.exports = function(grunt) { concat: { dist: { src: [ - 'src/util/*.js', - 'src/DistortableImageOverlay.js', - 'src/edit/getEXIFdata.js', - 'src/edit/EditHandle.js', - 'src/edit/LockHandle.js', - 'src/edit/DistortHandle.js', - 'src/edit/RotateAndScaleHandle.js', - 'src/edit/RotateHandle.js', - 'src/edit/ScaleHandle.js', - 'src/DistortableCollection.js', - 'src/edit/BoxSelectHandle.js', - 'src/edit/tools/DistortableImage.Keymapper.js', - 'src/edit/DistortableImage.EditToolbar.js', - 'src/edit/DistortableImage.Edit.js' + 'src/util/*.js', + 'src/edit/getEXIFdata.js', + 'src/edit/EditHandle.js', + 'src/edit/LockHandle.js', + 'src/edit/DistortHandle.js', + 'src/edit/RotateScaleHandle.js', + 'src/edit/RotateHandle.js', + 'src/edit/ScaleHandle.js', + 'src/DistortableImageOverlay.js', + 'src/DistortableCollection.js', + 'src/edit/DistortableImage.EditToolbar.js', + 'src/edit/DistortableImage.Edit.js', + 'src/edit/BoxSelectHandle.js' ], dest: 'dist/leaflet.distortableimage.js', } diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 569c7cd62..b5980f229 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -139,308 +139,6 @@ L.TrigUtil = { } }; -L.DistortableImageOverlay = L.ImageOverlay.extend({ - include: L.Mixin.Events, - - options: { - alt: "", - height: 200, - crossOrigin: true, - // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) - edgeMinWidth: 520 - }, - - initialize: function(url, options) { - this._toolArray = L.DistortableImage.EditToolbarDefaults; - this.edgeMinWidth = this.options.edgeMinWidth; - this._url = url; - this._rotation = this.options.rotation; - - L.Util.setOptions(this, options); - }, - - onAdd: function(map) { - /* Copied from L.ImageOverlay */ - this._map = map; - - if (!this._image) { this._initImage(); } - if (!this._events) { this._initEvents(); } - - map._panes.overlayPane.appendChild(this._image); - - map.on("viewreset", this._reset, this); - /* End copied from L.ImageOverlay */ - - /* Use provided corners if available */ - if (this.options.corners) { - this._corners = this.options.corners; - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on("zoomanim", this._animateZoom, this); - } - - /* This reset happens before image load; it allows - * us to place the image on the map earlier with - * "guessed" dimensions. */ - this._reset(); - } - - /* Have to wait for the image to load because - * we need to access its width and height. */ - L.DomEvent.on(this._image, "load", function() { - this._initImageDimensions(); - this._reset(); - /* Initialize default corners if not already set */ - if (!this._corners) { - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on("zoomanim", this._animateZoom, this); - } - } - }, this); - - this.fire("add"); - }, - - onRemove: function(map) { - this.fire("remove"); - - L.ImageOverlay.prototype.onRemove.call(this, map); - }, - - _initImage: function() { - L.ImageOverlay.prototype._initImage.call(this); - - L.extend(this._image, { - alt: this.options.alt - }); - }, - - _addTool: function(tool) { - this._toolArray.push(tool); - L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: this._toolArray - } - }); - }, - - _initImageDimensions: function() { - var map = this._map, - originalImageWidth = L.DomUtil.getStyle(this._image, "width"), - originalImageHeight = L.DomUtil.getStyle(this._image, "height"), - aspectRatio = - parseInt(originalImageWidth) / parseInt(originalImageHeight), - imageHeight = this.options.height, - imageWidth = parseInt(aspectRatio * imageHeight), - center = map.latLngToContainerPoint(map.getCenter()), - offset = new L.Point(imageWidth, imageHeight).divideBy(2); - - if (this.options.corners) { - this._corners = this.options.corners; - } else { - this._corners = [ - map.containerPointToLatLng(center.subtract(offset)), - map.containerPointToLatLng( - center.add(new L.Point(offset.x, -offset.y)) - ), - map.containerPointToLatLng( - center.add(new L.Point(-offset.x, offset.y)) - ), - map.containerPointToLatLng(center.add(offset)) - ]; - } - }, - - _initEvents: function() { - this._events = ["click"]; - - for (var i = 0, l = this._events.length; i < l; i++) { - L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); - } - }, - - /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ - _fireMouseEvent: function(event) { - if (!this.hasEventListeners(event.type)) { return; } - - var map = this._map, - containerPoint = map.mouseEventToContainerPoint(event), - layerPoint = map.containerPointToLayerPoint(containerPoint), - latlng = map.layerPointToLatLng(layerPoint); - - this.fire(event.type, { - latlng: latlng, - layerPoint: layerPoint, - containerPoint: containerPoint, - originalEvent: event - }); - }, - - _updateCorner: function(corner, latlng) { - this._corners[corner] = latlng; - this._reset(); - }, - - // fires a reset after all corner positions are updated instead of after each one (above). Use for translating - _updateCorners: function(latlngObj) { - var i = 0; - for (var k in latlngObj) { - this._corners[i] = latlngObj[k]; - i += 1; - } - - this._reset(); - }, - - _updateCornersFromPoints: function(pointsObj) { - var map = this._map; - var i = 0; - for (var k in pointsObj) { - this._corners[i] = map.layerPointToLatLng(pointsObj[k]); - i += 1; - } - - this._reset(); - }, - - /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ - /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ - _getTranslateString: function(point) { - // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate - // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care - // (same speed either way), Opera 12 doesn't support translate3d - - var is3d = L.Browser.webkit3d, - open = "translate" + (is3d ? "3d" : "") + "(", - close = (is3d ? ",0" : "") + ")"; - - return open + point.x + "px," + point.y + "px" + close; - }, - - _reset: function() { - var map = this._map, - image = this._image, - latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), - transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), - topLeft = latLngToLayerPoint(this._corners[0]), - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; - - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - - /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ - image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; - }, - - /* - * Calculates the transform string that will be correct *at the end* of zooming. - * Leaflet then generates a CSS3 animation between the current transform and - * future transform which makes the transition appear smooth. - */ - _animateZoom: function(event) { - var map = this._map, - image = this._image, - latLngToNewLayerPoint = function(latlng) { - return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); - }, - transformMatrix = this._calculateProjectiveTransform( - latLngToNewLayerPoint - ), - topLeft = latLngToNewLayerPoint(this._corners[0]), - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; - - if (!L.Browser.gecko) { - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - } - }, - - getCorners: function() { - return this._corners; - }, - - getCorner: function(i) { - return this._corners[i]; - }, - - /* - * Calculates the centroid of the image. - * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral - */ - getCenter: function(ll2c, c2ll) { - var map = this._map, - latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, - cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, - nw = latLngToCartesian.call(map, this._corners[0]), - ne = latLngToCartesian.call(map, this._corners[1]), - se = latLngToCartesian.call(map, this._corners[2]), - sw = latLngToCartesian.call(map, this._corners[3]), - nmid = nw.add(ne.subtract(nw).divideBy(2)), - smid = sw.add(se.subtract(sw).divideBy(2)); - - return cartesianToLatLng.call( - map, - nmid.add(smid.subtract(nmid).divideBy(2)) - ); - }, - - // Use for translation calculations - for translation the delta for 1 corner applies to all 4 - _calcCornerPointDelta: function() { - return this._dragStartPoints[0].subtract(this._dragPoints[0]); - }, - - _calcCenterTwoCornerPoints: function(topLeft, topRight) { - var toolPoint = { x: "", y: "" }; - - toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; - toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - - return toolPoint; - }, - - _calculateProjectiveTransform: function(latLngToCartesian) { - /* Setting reasonable but made-up image defaults - * allow us to place images on the map before - * they've finished downloading. */ - var offset = latLngToCartesian(this._corners[0]), - w = this._image.offsetWidth || 500, - h = this._image.offsetHeight || 375, - c = [], - j; - /* Convert corners to container points (i.e. cartesian coordinates). */ - for (j = 0; j < this._corners.length; j++) { - c.push(latLngToCartesian(this._corners[j])._subtract(offset)); - } - - /* - * This matrix describes the action of the CSS transform on each corner of the image. - * It maps from the coordinate system centered at the upper left corner of the image - * to the region bounded by the latlngs in this._corners. - * For example: - * 0, 0, c[0].x, c[0].y - * says that the upper-left corner of the image maps to the first latlng in this._corners. - */ - return L.MatrixUtil.general2DProjection( - 0, 0, c[0].x, c[0].y, - w, 0, c[1].x, c[1].y, - 0, h, c[2].x, c[2].y, - w, h, c[3].x, c[3].y - ); - } -}); - -L.distortableImageOverlay = function(id, options) { - return new L.DistortableImageOverlay(id, options); -}; - - - - L.EXIF = function getEXIFdata(img) { if (Object.keys(EXIF.getAllTags(img)).length !== 0) { console.log(EXIF.getAllTags(img)); @@ -648,7 +346,7 @@ L.DistortHandle = L.EditHandle.extend({ } }); -L.RotateAndScaleHandle = L.EditHandle.extend({ +L.RotateScaleHandle = L.EditHandle.extend({ options: { TYPE: 'rotateScale', icon: new L.Icon({ @@ -691,171 +389,473 @@ L.RotateAndScaleHandle = L.EditHandle.extend({ this.setLatLng(this._handled._corners[this._corner]); }, - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + + return newAngle - initialAngle; + }, + + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); + + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, + + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; + + return Math.pow(dx, 2) + Math.pow(dy, 2); + } +}); + +L.RotateHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: new L.Icon({ + iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, + + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = this._handled._corners[this._corner], + newLatLng = this.getLatLng(), + angle = this._calculateAngle(formerLatLng, newLatLng); + + overlay.editing._rotateBy(angle); + + overlay.fire('update'); + + this._handled.editing._showToolbar(); + }, + + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, + + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + + return newAngle - initialAngle; + }, + + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); + + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, + + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; + + return Math.pow(dx, 2) + Math.pow(dy, 2); + } +}); + +L.ScaleHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: new L.Icon({ + iconUrl:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTkiIGhlaWdodD0iNDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iIj48cmVjdCBpZD0iYmFja2dyb3VuZHJlY3QiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9IjAiIHk9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgY2xhc3M9IiIgc3R5bGU9IiIvPjxnIGNsYXNzPSJjdXJyZW50TGF5ZXIiIHN0eWxlPSIiPjx0aXRsZT5MYXllciAxPC90aXRsZT48cGF0aCBkPSJNNDU5LjA0OTE1OTUzMDQ3MTM0LDg2LjkyNjIzNDUxMjU1MDAyIFYwIGgtODUuNzE0NTczMzU2MzEyMDkgdjI3LjA0MzcxNzQwMzkwNDQ1MiBIODUuNzE0NTczMzU2MzEyMDMgVjAgSDAgdjg2LjkyNjIzNDUxMjU1MDAyIGgyNS43MTQzNzIwMDY4OTM2MjYgdjI4OS43NTQxMTUwNDE4MzM0IEgwIHY4Ni45MjYyMzQ1MTI1NTAwMiBoODUuNzE0NTczMzU2MzEyMDkgdi0yNy4wNDM3MTc0MDM5MDQ0NTIgaDI4NS43MTUyNDQ1MjEwNDAzIHYyNy4wNDM3MTc0MDM5MDQ0NTIgaDg1LjcxNDU3MzM1NjMxMjA5IHYtODYuOTI2MjM0NTEyNTUwMDIgaC0yMy44MDk2MDM3MTAwODY2OSBWODYuOTI2MjM0NTEyNTUwMDIgSDQ1OS4wNDkxNTk1MzA0NzEzNCB6TTM4NC43NjMxOTU5NTUwMDA5LDEyLjU1NjAxMTY1MTgxMjc4MSBoNjEuOTA0OTY5NjQ2MjI1Mzk2IHY2Mi43ODAwNTgyNTkwNjM5MSBoLTYxLjkwNDk2OTY0NjIyNTM5NiBWMTIuNTU2MDExNjUxODEyNzgxIHpNMTIuMzgwOTkzOTI5MjQ1MDUsMTIuNTU2MDExNjUxODEyNzgxIGg2MS45MDQ5Njk2NDYyMjUzOTYgdjYyLjc4MDA1ODI1OTA2MzkxIEgxMi4zODA5OTM5MjkyNDUwNSBWMTIuNTU2MDExNjUxODEyNzgxIHpNNzQuMjg1OTYzNTc1NDcwNTMsNDUxLjA1MDU3MjQxNTEyMDY2IEgxMi4zODA5OTM5MjkyNDUwNSB2LTYyLjc4MDA1ODI1OTA2MzkxIGg2MS45MDQ5Njk2NDYyMjUzOTYgVjQ1MS4wNTA1NzI0MTUxMjA2NiB6TTQ0NS43MTU3ODE0NTI4MjI3NCw0NTEuMDUwNTcyNDE1MTIwNjYgaC02Mi44NTczNTM3OTQ2Mjg4NjQgdi02Mi43ODAwNTgyNTkwNjM5MSBoNjIuODU3MzUzNzk0NjI4ODY0IFY0NTEuMDUwNTcyNDE1MTIwNjYgek00MDcuNjIwNDE1NTE2Njg0MjYsMzc2LjY4MDM0OTU1NDM4MzQ0IGgtMzYuMTkwNTk3NjM5MzMxNzcgdjMyLjgzODc5OTcwNDc0MTEyIEg4NS43MTQ1NzMzNTYzMTIwMyB2LTMyLjgzODc5OTcwNDc0MTEyIEg0OS41MjM5NzU3MTY5ODAzMiBWODYuOTI2MjM0NTEyNTUwMDIgaDM2LjE5MDU5NzYzOTMzMTc3IFY1MC4yMjQwNDY2MDcyNTExMjUgaDI4Ny42MjAwMTI4MTc4NDcyIHYzNi43MDIxODc5MDUyOTg5IGgzNC4yODU4MjkzNDI1MjQ4MzUgVjM3Ni42ODAzNDk1NTQzODM0NCB6IiBpZD0ic3ZnXzIiIGNsYXNzPSIiIGZpbGw9IiMxYTFhZWIiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L3N2Zz4=', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, + + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = this._handled._corners[this._corner], + newLatLng = this.getLatLng(), + + scale = this._calculateScalingFactor(formerLatLng, newLatLng); + + overlay.editing._scaleBy(scale); + + overlay.fire('update'); + + this._handled.editing._showToolbar(); + }, + + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, + + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + + return newAngle - initialAngle; + }, + + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, + + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); + + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, + + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; + + return Math.pow(dx, 2) + Math.pow(dy, 2); + } +}); + +L.DistortableImageOverlay = L.ImageOverlay.extend({ + include: L.Mixin.Events, + + options: { + alt: "", + height: 200, + crossOrigin: true, + // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) + edgeMinWidth: 520 + }, + + initialize: function(url, options) { + this._toolArray = L.DistortableImage.EditToolbarDefaults; + this.edgeMinWidth = this.options.edgeMinWidth; + this._url = url; + this._rotation = this.options.rotation; + + L.Util.setOptions(this, options); + }, + + onAdd: function(map) { + /* Copied from L.ImageOverlay */ + this._map = map; + + if (!this._image) { this._initImage(); } + if (!this._events) { this._initEvents(); } + + map._panes.overlayPane.appendChild(this._image); + + map.on("viewreset", this._reset, this); + /* End copied from L.ImageOverlay */ + + /* Use provided corners if available */ + if (this.options.corners) { + this._corners = this.options.corners; + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + + /* This reset happens before image load; it allows + * us to place the image on the map earlier with + * "guessed" dimensions. */ + this._reset(); + } + + /* Have to wait for the image to load because + * we need to access its width and height. */ + L.DomEvent.on(this._image, "load", function() { + this._initImageDimensions(); + this._reset(); + /* Initialize default corners if not already set */ + if (!this._corners) { + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + } + }, this); + + this.fire("add"); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + onRemove: function(map) { + this.fire("remove"); - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + L.ImageOverlay.prototype.onRemove.call(this, map); + }, - return newAngle - initialAngle; - }, + _initImage: function() { + L.ImageOverlay.prototype._initImage.call(this); - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + L.extend(this._image, { + alt: this.options.alt + }); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + _addTool: function(tool) { + this._toolArray.push(tool); + L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: this._toolArray + } + }); + }, - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); + _initImageDimensions: function() { + var map = this._map, + originalImageWidth = L.DomUtil.getStyle(this._image, "width"), + originalImageHeight = L.DomUtil.getStyle(this._image, "height"), + aspectRatio = + parseInt(originalImageWidth) / parseInt(originalImageHeight), + imageHeight = this.options.height, + imageWidth = parseInt(aspectRatio * imageHeight), + center = map.latLngToContainerPoint(map.getCenter()), + offset = new L.Point(imageWidth, imageHeight).divideBy(2); - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, + if (this.options.corners) { + this._corners = this.options.corners; + } else { + this._corners = [ + map.containerPointToLatLng(center.subtract(offset)), + map.containerPointToLatLng( + center.add(new L.Point(offset.x, -offset.y)) + ), + map.containerPointToLatLng( + center.add(new L.Point(-offset.x, offset.y)) + ), + map.containerPointToLatLng(center.add(offset)) + ]; + } + }, - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; + _initEvents: function() { + this._events = ["click"]; - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); + for (var i = 0, l = this._events.length; i < l; i++) { + L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); + } + }, -L.RotateHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: new L.Icon({ - iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, - - _onHandleDrag: function() { - var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], - newLatLng = this.getLatLng(), - angle = this._calculateAngle(formerLatLng, newLatLng); + /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ + _fireMouseEvent: function(event) { + if (!this.hasEventListeners(event.type)) { return; } - overlay.editing._rotateBy(angle); + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(event), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); - overlay.fire('update'); + this.fire(event.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: event + }); + }, - this._handled.editing._showToolbar(); - }, + _updateCorner: function(corner, latlng) { + this._corners[corner] = latlng; + this._reset(); + }, - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, + // fires a reset after all corner positions are updated instead of after each one (above). Use for translating + _updateCorners: function(latlngObj) { + var i = 0; + for (var k in latlngObj) { + this._corners[i] = latlngObj[k]; + i += 1; + } - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + this._reset(); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + _updateCornersFromPoints: function(pointsObj) { + var map = this._map; + var i = 0; + for (var k in pointsObj) { + this._corners[i] = map.layerPointToLatLng(pointsObj[k]); + i += 1; + } - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + this._reset(); + }, - return newAngle - initialAngle; - }, + /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ + /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ + _getTranslateString: function(point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + var is3d = L.Browser.webkit3d, + open = "translate" + (is3d ? "3d" : "") + "(", + close = (is3d ? ",0" : "") + ")"; - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + return open + point.x + "px," + point.y + "px" + close; + }, - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); + _reset: function() { + var map = this._map, + image = this._image, + latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), + transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), + topLeft = latLngToLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); + /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ + image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; + }, -L.ScaleHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: new L.Icon({ - iconUrl:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTkiIGhlaWdodD0iNDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iIj48cmVjdCBpZD0iYmFja2dyb3VuZHJlY3QiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9IjAiIHk9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgY2xhc3M9IiIgc3R5bGU9IiIvPjxnIGNsYXNzPSJjdXJyZW50TGF5ZXIiIHN0eWxlPSIiPjx0aXRsZT5MYXllciAxPC90aXRsZT48cGF0aCBkPSJNNDU5LjA0OTE1OTUzMDQ3MTM0LDg2LjkyNjIzNDUxMjU1MDAyIFYwIGgtODUuNzE0NTczMzU2MzEyMDkgdjI3LjA0MzcxNzQwMzkwNDQ1MiBIODUuNzE0NTczMzU2MzEyMDMgVjAgSDAgdjg2LjkyNjIzNDUxMjU1MDAyIGgyNS43MTQzNzIwMDY4OTM2MjYgdjI4OS43NTQxMTUwNDE4MzM0IEgwIHY4Ni45MjYyMzQ1MTI1NTAwMiBoODUuNzE0NTczMzU2MzEyMDkgdi0yNy4wNDM3MTc0MDM5MDQ0NTIgaDI4NS43MTUyNDQ1MjEwNDAzIHYyNy4wNDM3MTc0MDM5MDQ0NTIgaDg1LjcxNDU3MzM1NjMxMjA5IHYtODYuOTI2MjM0NTEyNTUwMDIgaC0yMy44MDk2MDM3MTAwODY2OSBWODYuOTI2MjM0NTEyNTUwMDIgSDQ1OS4wNDkxNTk1MzA0NzEzNCB6TTM4NC43NjMxOTU5NTUwMDA5LDEyLjU1NjAxMTY1MTgxMjc4MSBoNjEuOTA0OTY5NjQ2MjI1Mzk2IHY2Mi43ODAwNTgyNTkwNjM5MSBoLTYxLjkwNDk2OTY0NjIyNTM5NiBWMTIuNTU2MDExNjUxODEyNzgxIHpNMTIuMzgwOTkzOTI5MjQ1MDUsMTIuNTU2MDExNjUxODEyNzgxIGg2MS45MDQ5Njk2NDYyMjUzOTYgdjYyLjc4MDA1ODI1OTA2MzkxIEgxMi4zODA5OTM5MjkyNDUwNSBWMTIuNTU2MDExNjUxODEyNzgxIHpNNzQuMjg1OTYzNTc1NDcwNTMsNDUxLjA1MDU3MjQxNTEyMDY2IEgxMi4zODA5OTM5MjkyNDUwNSB2LTYyLjc4MDA1ODI1OTA2MzkxIGg2MS45MDQ5Njk2NDYyMjUzOTYgVjQ1MS4wNTA1NzI0MTUxMjA2NiB6TTQ0NS43MTU3ODE0NTI4MjI3NCw0NTEuMDUwNTcyNDE1MTIwNjYgaC02Mi44NTczNTM3OTQ2Mjg4NjQgdi02Mi43ODAwNTgyNTkwNjM5MSBoNjIuODU3MzUzNzk0NjI4ODY0IFY0NTEuMDUwNTcyNDE1MTIwNjYgek00MDcuNjIwNDE1NTE2Njg0MjYsMzc2LjY4MDM0OTU1NDM4MzQ0IGgtMzYuMTkwNTk3NjM5MzMxNzcgdjMyLjgzODc5OTcwNDc0MTEyIEg4NS43MTQ1NzMzNTYzMTIwMyB2LTMyLjgzODc5OTcwNDc0MTEyIEg0OS41MjM5NzU3MTY5ODAzMiBWODYuOTI2MjM0NTEyNTUwMDIgaDM2LjE5MDU5NzYzOTMzMTc3IFY1MC4yMjQwNDY2MDcyNTExMjUgaDI4Ny42MjAwMTI4MTc4NDcyIHYzNi43MDIxODc5MDUyOTg5IGgzNC4yODU4MjkzNDI1MjQ4MzUgVjM3Ni42ODAzNDk1NTQzODM0NCB6IiBpZD0ic3ZnXzIiIGNsYXNzPSIiIGZpbGw9IiMxYTFhZWIiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L3N2Zz4=', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, + /* + * Calculates the transform string that will be correct *at the end* of zooming. + * Leaflet then generates a CSS3 animation between the current transform and + * future transform which makes the transition appear smooth. + */ + _animateZoom: function(event) { + var map = this._map, + image = this._image, + latLngToNewLayerPoint = function(latlng) { + return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); + }, + transformMatrix = this._calculateProjectiveTransform( + latLngToNewLayerPoint + ), + topLeft = latLngToNewLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); - _onHandleDrag: function() { - var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], - newLatLng = this.getLatLng(), + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; - scale = this._calculateScalingFactor(formerLatLng, newLatLng); + if (!L.Browser.gecko) { + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + } + }, - overlay.editing._scaleBy(scale); + getCorners: function() { + return this._corners; + }, - overlay.fire('update'); + getCorner: function(i) { + return this._corners[i]; + }, - this._handled.editing._showToolbar(); - }, + /* + * Calculates the centroid of the image. + * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral + */ + getCenter: function(ll2c, c2ll) { + var map = this._map, + latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, + cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, + nw = latLngToCartesian.call(map, this._corners[0]), + ne = latLngToCartesian.call(map, this._corners[1]), + se = latLngToCartesian.call(map, this._corners[2]), + sw = latLngToCartesian.call(map, this._corners[3]), + nmid = nw.add(ne.subtract(nw).divideBy(2)), + smid = sw.add(se.subtract(sw).divideBy(2)); - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, + return cartesianToLatLng.call( + map, + nmid.add(smid.subtract(nmid).divideBy(2)) + ); + }, - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + // Use for translation calculations - for translation the delta for 1 corner applies to all 4 + _calcCornerPointDelta: function() { + return this._dragStartPoints[0].subtract(this._dragPoints[0]); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + _calcCenterTwoCornerPoints: function(topLeft, topRight) { + var toolPoint = { x: "", y: "" }; - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; + toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - return newAngle - initialAngle; - }, + return toolPoint; + }, - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + _calculateProjectiveTransform: function(latLngToCartesian) { + /* Setting reasonable but made-up image defaults + * allow us to place images on the map before + * they've finished downloading. */ + var offset = latLngToCartesian(this._corners[0]), + w = this._image.offsetWidth || 500, + h = this._image.offsetHeight || 375, + c = [], + j; + /* Convert corners to container points (i.e. cartesian coordinates). */ + for (j = 0; j < this._corners.length; j++) { + c.push(latLngToCartesian(this._corners[j])._subtract(offset)); + } - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + /* + * This matrix describes the action of the CSS transform on each corner of the image. + * It maps from the coordinate system centered at the upper left corner of the image + * to the region bounded by the latlngs in this._corners. + * For example: + * 0, 0, c[0].x, c[0].y + * says that the upper-left corner of the image maps to the first latlng in this._corners. + */ + return L.MatrixUtil.general2DProjection( + 0, 0, c[0].x, c[0].y, + w, 0, c[1].x, c[1].y, + 0, h, c[2].x, c[2].y, + w, h, c[3].x, c[3].y + ); + } +}); - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); +L.distortableImageOverlay = function(id, options) { + return new L.DistortableImageOverlay(id, options); +}; - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); L.DistortableCollection = L.FeatureGroup.extend({ include: L.Mixin.Events, @@ -1041,122 +1041,6 @@ L.DistortableCollection = L.FeatureGroup.extend({ L.distortableCollection = function(id, options) { return new L.DistortableCollection(id, options); }; -L.Map.mergeOptions({ boxSelector: true, boxZoom: false }); - -// used for multiple image select. Temporarily disabled until click -// propagation issue is fixed - -L.Map.BoxSelectHandle = L.Map.BoxZoom.extend({ - - initialize: function (map) { - this._map = map; - this._container = map._container; - this._pane = map._panes.overlayPane; - }, - - addHooks: function () { - L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); - }, - - removeHooks: function () { - L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); - }, - - _onMouseDown: function (e) { - if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } - - L.DomUtil.disableTextSelection(); - L.DomUtil.disableImageDrag(); - - this._startLayerPoint = this._map.mouseEventToLayerPoint(e); - - this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); - L.DomUtil.setPosition(this._box, this._startLayerPoint); - - this._container.style.cursor = 'crosshair'; - - L.DomEvent - .on(document, 'mousemove', this._onMouseMove, this) - .on(document, 'mouseup', this._onMouseUp, this) - .preventDefault(e); - - this._map.fire('boxzoomstart'); - }, - - _onMouseMove: function (e) { - var startPoint = this._startLayerPoint, - box = this._box, - - layerPoint = this._map.mouseEventToLayerPoint(e), - offset = layerPoint.subtract(startPoint), - - newPos = new L.Point( - Math.min(layerPoint.x, startPoint.x), - Math.min(layerPoint.y, startPoint.y)); - - L.DomUtil.setPosition(box, newPos); - - box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; - box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; - }, - - _onMouseUp: function (e) { - var map = this._map, - layerPoint = map.mouseEventToLayerPoint(e); - - if (this._startLayerPoint.equals(layerPoint)) { return; } - - this._boxBounds = new L.LatLngBounds( - map.layerPointToLatLng(this._startLayerPoint), - map.layerPointToLatLng(layerPoint)); - - this._finish(); - - map.fire('boxzoomend', { boxZoomBounds: this._boxBounds }); - - // this._finish(); - }, - - _finish: function () { - $(this._map.boxSelector._box).remove(); - // L.DomUtil.remove(this._box); - // L.DomUtil.remove(this._map.boxSelector); - this._container.style.cursor = ''; - - L.DomUtil.enableTextSelection(); - L.DomUtil.enableImageDrag(); - - L.DomEvent - .off(document, 'mousemove', this._onMouseMove) - .off(document, 'mouseup', this._onMouseUp); - }, -}); - -L.Map.addInitHook('addHandler', 'boxSelector', L.Map.BoxSelectHandle); -L.DomUtil = L.DomUtil || {}; -L.DistortableImage = L.DistortableImage || {}; - -L.DistortableImage.Keymapper = L.Control.extend({ - initialize: function(options) { - L.Control.prototype.initialize.call(this, options); - }, - onAdd: function() { - var el_wrapper = L.DomUtil.create("div", "l-container"); - el_wrapper.innerHTML = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; - return el_wrapper; - } -}); L.DistortableImage = L.DistortableImage || {}; var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ @@ -1403,7 +1287,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale for (i = 0; i < 4; i++) { - this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateScaleHandles.addLayer(new L.RotateScaleHandle(overlay, i)); } this._handles = { @@ -1851,3 +1735,96 @@ L.DistortableImageOverlay.addInitHook(function() { if (this.editing) { this.editing.disable(); } }); }); + +L.Map.mergeOptions({ boxSelector: true, boxZoom: false }); + +// used for multiple image select. Temporarily disabled until click +// propagation issue is fixed + +L.Map.BoxSelectHandle = L.Map.BoxZoom.extend({ + + initialize: function (map) { + this._map = map; + this._container = map._container; + this._pane = map._panes.overlayPane; + }, + + addHooks: function () { + L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); + }, + + removeHooks: function () { + L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); + }, + + _onMouseDown: function (e) { + if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } + + L.DomUtil.disableTextSelection(); + L.DomUtil.disableImageDrag(); + + this._startLayerPoint = this._map.mouseEventToLayerPoint(e); + + this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); + L.DomUtil.setPosition(this._box, this._startLayerPoint); + + this._container.style.cursor = 'crosshair'; + + L.DomEvent + .on(document, 'mousemove', this._onMouseMove, this) + .on(document, 'mouseup', this._onMouseUp, this) + .preventDefault(e); + + this._map.fire('boxzoomstart'); + }, + + _onMouseMove: function (e) { + var startPoint = this._startLayerPoint, + box = this._box, + + layerPoint = this._map.mouseEventToLayerPoint(e), + offset = layerPoint.subtract(startPoint), + + newPos = new L.Point( + Math.min(layerPoint.x, startPoint.x), + Math.min(layerPoint.y, startPoint.y)); + + L.DomUtil.setPosition(box, newPos); + + box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; + box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; + }, + + _onMouseUp: function (e) { + var map = this._map, + layerPoint = map.mouseEventToLayerPoint(e); + + if (this._startLayerPoint.equals(layerPoint)) { return; } + + this._boxBounds = new L.LatLngBounds( + map.layerPointToLatLng(this._startLayerPoint), + map.layerPointToLatLng(layerPoint)); + + this._finish(); + + map.fire('boxzoomend', { boxZoomBounds: this._boxBounds }); + + // this._finish(); + }, + + _finish: function () { + $(this._map.boxSelector._box).remove(); + // L.DomUtil.remove(this._box); + // L.DomUtil.remove(this._map.boxSelector); + this._container.style.cursor = ''; + + L.DomUtil.enableTextSelection(); + L.DomUtil.enableImageDrag(); + + L.DomEvent + .off(document, 'mousemove', this._onMouseMove) + .off(document, 'mouseup', this._onMouseUp); + }, +}); + +L.Map.addInitHook('addHandler', 'boxSelector', L.Map.BoxSelectHandle); \ No newline at end of file diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 80660aaa7..091374c51 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -60,7 +60,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._rotateScaleHandles = new L.LayerGroup(); // handle includes rotate AND scale for (i = 0; i < 4; i++) { - this._rotateScaleHandles.addLayer(new L.RotateAndScaleHandle(overlay, i)); + this._rotateScaleHandles.addLayer(new L.RotateScaleHandle(overlay, i)); } this._handles = { diff --git a/src/edit/RotateAndScaleHandle.js b/src/edit/RotateScaleHandle.js similarity index 98% rename from src/edit/RotateAndScaleHandle.js rename to src/edit/RotateScaleHandle.js index 73c0bd0db..89875fad1 100644 --- a/src/edit/RotateAndScaleHandle.js +++ b/src/edit/RotateScaleHandle.js @@ -1,4 +1,4 @@ -L.RotateAndScaleHandle = L.EditHandle.extend({ +L.RotateScaleHandle = L.EditHandle.extend({ options: { TYPE: 'rotateScale', icon: new L.Icon({ diff --git a/test/karma.conf.js b/test/karma.conf.js index 303b539f2..afc99af6d 100644 --- a/test/karma.conf.js +++ b/test/karma.conf.js @@ -38,7 +38,7 @@ module.exports = function(config) { "src/edit/EditHandle.js", "src/edit/LockHandle.js", "src/edit/DistortHandle.js", - "src/edit/RotateAndScaleHandle.js", + "src/edit/RotateScaleHandle.js", "src/edit/RotateHandle.js", "src/edit/ScaleHandle.js", "src/DistortableCollection.js", diff --git a/test/src/edit/RotateAndScaleHandleSpec.js b/test/src/edit/RotateScaleHandleSpec.js similarity index 90% rename from test/src/edit/RotateAndScaleHandleSpec.js rename to test/src/edit/RotateScaleHandleSpec.js index 98bce4258..23876a6d7 100644 --- a/test/src/edit/RotateAndScaleHandleSpec.js +++ b/test/src/edit/RotateScaleHandleSpec.js @@ -1,4 +1,4 @@ -describe("L.RotateAndScaleHandle", function() { +describe("L.RotateScaleHandle", function() { var map, distortable, rotateHandle; @@ -15,7 +15,7 @@ describe("L.RotateAndScaleHandle", function() { }).addTo(map); L.DomEvent.on(distortable._image, 'load', function() { - rotateHandle = new L.RotateAndScaleHandle(distortable, 0); + rotateHandle = new L.RotateScaleHandle(distortable, 0); done(); }); }); From 669db3e80ab1d80bef0b82117c0c6898de2ddbf7 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sun, 21 Apr 2019 02:52:16 -0400 Subject: [PATCH 06/18] Make sure lock mode is secure --- dist/leaflet.distortableimage.js | 42 +++++++++++++++++-------------- src/DistortableCollection.js | 15 +++++++---- src/edit/DistortableImage.Edit.js | 27 ++++++++++---------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index b5980f229..9e9012d7c 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -911,8 +911,9 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectOthers: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; + if (edit._mode === "lock") { return; } if (layer._image !== event.target) { - edit._hideMarkers(); + edit._deselect(); } else { this._toggleMultiSelect(event, edit); } @@ -941,7 +942,9 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { - this._deselectAll(); + this._deselectAll(e); + } else { + L.DomEvent.stopPropagation(e); } }, @@ -955,7 +958,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ this.eachLayer(function(layer) { for (i = 0; i < 4; i++) { - if (layer !== overlay) { + if (layer !== overlay && layer.editing._mode !== "lock") { layer.editing._hideToolbar(); } layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( @@ -985,16 +988,18 @@ L.DistortableCollection = L.FeatureGroup.extend({ this._updateCollectionFromPoints(cpd, overlay); }, - _deselectAll: function() { + _deselectAll: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - + if (edit._mode === "lock") { return; } L.DomUtil.removeClass(layer.getElement(), "selected"); if (edit.toolbar) { edit._hideToolbar(); } edit._hideMarkers(); }); + + L.DomEvent.stopPropagation(event); }, /** @@ -1327,7 +1332,7 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Enable hotkeys. */ L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - overlay.fire('select'); + // overlay.fire("select"); }, removeHooks: function () { @@ -1349,14 +1354,15 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Disable hotkeys. */ L.DomEvent.off(window, "keydown", this._onKeyDown, this); - overlay.fire("deselect"); + // overlay.fire("deselect"); }, _initToolbar: function () { this._showToolbar(); try { - this.toolbar._hide(); - this.toolbar._tip.style.opacity = 0; + // this.toolbar._hide(); + // this.toolbar._tip.style.opacity = 0; + this.toolbar._container.style.opacity = 0; } catch (e) { } }, @@ -1451,7 +1457,7 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { this[handlerName].call(this); } - }, + }, _toggleRotateScale: function() { var map = this._overlay._map; @@ -1464,9 +1470,9 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this._mode === "rotateScale") { this._mode = "distort"; } else { this._mode = "rotateScale"; } - this._showToolbar(); - map.addLayer(this._handles[this._mode]); + + this._showToolbar(); }, _toggleScale: function() { @@ -1479,9 +1485,8 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this._mode === "scale") { this._mode = "distort"; } else { this._mode = "scale"; } - this._showToolbar(); - map.addLayer(this._handles[this._mode]); + }, _toggleRotate: function() { @@ -1492,8 +1497,6 @@ L.DistortableImage.Edit = L.Handler.extend({ map.removeLayer(this._handles[this._mode]); if (this._mode === "rotate") { this._mode = "distort"; } else { this._mode = "rotate"; } - - this._showToolbar(); map.addLayer(this._handles[this._mode]); }, @@ -1550,14 +1553,15 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _select: function(event) { - this._showToolbar(event); + this._showToolbar(); this._showMarkers(); L.DomEvent.stopPropagation(event); }, - _deselect: function(event) { - this._hideToolbar(event); + _deselect: function() { + if (this._mode === "lock") { return; } + this._hideToolbar(); this._hideMarkers(); }, diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index 470c66de3..1c13a09c9 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -52,8 +52,9 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectOthers: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; + if (edit._mode === "lock") { return; } if (layer._image !== event.target) { - edit._hideMarkers(); + edit._deselect(); } else { this._toggleMultiSelect(event, edit); } @@ -82,7 +83,9 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { - this._deselectAll(); + this._deselectAll(e); + } else { + L.DomEvent.stopPropagation(e); } }, @@ -96,7 +99,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ this.eachLayer(function(layer) { for (i = 0; i < 4; i++) { - if (layer !== overlay) { + if (layer !== overlay && layer.editing._mode !== "lock") { layer.editing._hideToolbar(); } layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( @@ -126,16 +129,18 @@ L.DistortableCollection = L.FeatureGroup.extend({ this._updateCollectionFromPoints(cpd, overlay); }, - _deselectAll: function() { + _deselectAll: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - + if (edit._mode === "lock") { return; } L.DomUtil.removeClass(layer.getElement(), "selected"); if (edit.toolbar) { edit._hideToolbar(); } edit._hideMarkers(); }); + + L.DomEvent.stopPropagation(event); }, /** diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 091374c51..757013b1e 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -100,7 +100,7 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Enable hotkeys. */ L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - overlay.fire('select'); + // overlay.fire("select"); }, removeHooks: function () { @@ -122,14 +122,15 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Disable hotkeys. */ L.DomEvent.off(window, "keydown", this._onKeyDown, this); - overlay.fire("deselect"); + // overlay.fire("deselect"); }, _initToolbar: function () { this._showToolbar(); try { - this.toolbar._hide(); - this.toolbar._tip.style.opacity = 0; + // this.toolbar._hide(); + // this.toolbar._tip.style.opacity = 0; + this.toolbar._container.style.opacity = 0; } catch (e) { } }, @@ -224,7 +225,7 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { this[handlerName].call(this); } - }, + }, _toggleRotateScale: function() { var map = this._overlay._map; @@ -237,9 +238,9 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this._mode === "rotateScale") { this._mode = "distort"; } else { this._mode = "rotateScale"; } - this._showToolbar(); - map.addLayer(this._handles[this._mode]); + + this._showToolbar(); }, _toggleScale: function() { @@ -252,9 +253,8 @@ L.DistortableImage.Edit = L.Handler.extend({ if (this._mode === "scale") { this._mode = "distort"; } else { this._mode = "scale"; } - this._showToolbar(); - map.addLayer(this._handles[this._mode]); + }, _toggleRotate: function() { @@ -265,8 +265,6 @@ L.DistortableImage.Edit = L.Handler.extend({ map.removeLayer(this._handles[this._mode]); if (this._mode === "rotate") { this._mode = "distort"; } else { this._mode = "rotate"; } - - this._showToolbar(); map.addLayer(this._handles[this._mode]); }, @@ -323,14 +321,15 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _select: function(event) { - this._showToolbar(event); + this._showToolbar(); this._showMarkers(); L.DomEvent.stopPropagation(event); }, - _deselect: function(event) { - this._hideToolbar(event); + _deselect: function() { + if (this._mode === "lock") { return; } + this._hideToolbar(); this._hideMarkers(); }, From 1b6c0b67dae057d82807c3eaafbea1ad43140977 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sun, 21 Apr 2019 03:26:53 -0400 Subject: [PATCH 07/18] Initialize image as already selected --- dist/leaflet.distortableimage.js | 2 -- src/edit/DistortableImage.Edit.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 9e9012d7c..36e9eb4d2 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1360,8 +1360,6 @@ L.DistortableImage.Edit = L.Handler.extend({ _initToolbar: function () { this._showToolbar(); try { - // this.toolbar._hide(); - // this.toolbar._tip.style.opacity = 0; this.toolbar._container.style.opacity = 0; } catch (e) { } diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 757013b1e..c6934f2a8 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -128,8 +128,6 @@ L.DistortableImage.Edit = L.Handler.extend({ _initToolbar: function () { this._showToolbar(); try { - // this.toolbar._hide(); - // this.toolbar._tip.style.opacity = 0; this.toolbar._container.style.opacity = 0; } catch (e) { } From 84405866977f503621754e2aa2b9164687232b0c Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Mon, 22 Apr 2019 01:35:53 -0400 Subject: [PATCH 08/18] for multiple image select, hotkeys only toggle single selected image --- dist/leaflet.distortableimage.js | 30 +++++++++-------------- src/DistortableCollection.js | 17 +++---------- src/edit/DistortableImage.Edit.js | 13 +++++++--- test/src/edit/DistortableImageEditSpec.js | 2 ++ 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 36e9eb4d2..80bd795bd 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -911,7 +911,6 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectOthers: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - if (edit._mode === "lock") { return; } if (layer._image !== event.target) { edit._deselect(); } else { @@ -943,8 +942,6 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { this._deselectAll(e); - } else { - L.DomEvent.stopPropagation(e); } }, @@ -952,9 +949,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ var overlay = event.target, i; - if (!this.isSelected(overlay)) { - return; - } + if (!this.isSelected(overlay)) { return; } this.eachLayer(function(layer) { for (i = 0; i < 4; i++) { @@ -973,9 +968,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ map = this._map, i; - if (!this.isSelected(overlay)) { - return; - } + if (!this.isSelected(overlay)) { return; } overlay._dragPoints = {}; @@ -991,12 +984,8 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectAll: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - if (edit._mode === "lock") { return; } L.DomUtil.removeClass(layer.getElement(), "selected"); - if (edit.toolbar) { - edit._hideToolbar(); - } - edit._hideMarkers(); + edit._deselect(); }); L.DomEvent.stopPropagation(event); @@ -1260,6 +1249,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._mode = this._overlay.options.mode || "distort"; this._transparent = false; this._outlined = false; + this._selected = false; }, /* Run on image selection. */ @@ -1453,7 +1443,9 @@ L.DistortableImage.Edit = L.Handler.extend({ handlerName = keymap[event.which]; if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { - this[handlerName].call(this); + if (this._selected) { + this[handlerName].call(this); + } } }, @@ -1465,8 +1457,8 @@ L.DistortableImage.Edit = L.Handler.extend({ map.removeLayer(this._handles[this._mode]); /* Switch mode. */ - if (this._mode === "rotateScale") { this._mode = "distort"; } - else { this._mode = "rotateScale"; } + if (this._mode === "rotateScale") { this._mode = "distort"; } + else { this._mode = "rotateScale"; } map.addLayer(this._handles[this._mode]); @@ -1551,6 +1543,7 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _select: function(event) { + this._selected = true; this._showToolbar(); this._showMarkers(); @@ -1558,8 +1551,9 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _deselect: function() { - if (this._mode === "lock") { return; } + this._selected = false; this._hideToolbar(); + if (this._mode === "lock") { return; } this._hideMarkers(); }, diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index 1c13a09c9..e2e49a4f9 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -52,7 +52,6 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectOthers: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - if (edit._mode === "lock") { return; } if (layer._image !== event.target) { edit._deselect(); } else { @@ -84,8 +83,6 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { this._deselectAll(e); - } else { - L.DomEvent.stopPropagation(e); } }, @@ -93,9 +90,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ var overlay = event.target, i; - if (!this.isSelected(overlay)) { - return; - } + if (!this.isSelected(overlay)) { return; } this.eachLayer(function(layer) { for (i = 0; i < 4; i++) { @@ -114,9 +109,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ map = this._map, i; - if (!this.isSelected(overlay)) { - return; - } + if (!this.isSelected(overlay)) { return; } overlay._dragPoints = {}; @@ -132,12 +125,8 @@ L.DistortableCollection = L.FeatureGroup.extend({ _deselectAll: function(event) { this.eachLayer(function(layer) { var edit = layer.editing; - if (edit._mode === "lock") { return; } L.DomUtil.removeClass(layer.getElement(), "selected"); - if (edit.toolbar) { - edit._hideToolbar(); - } - edit._hideMarkers(); + edit._deselect(); }); L.DomEvent.stopPropagation(event); diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index c6934f2a8..f6ccd85eb 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -28,6 +28,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._mode = this._overlay.options.mode || "distort"; this._transparent = false; this._outlined = false; + this._selected = false; }, /* Run on image selection. */ @@ -221,7 +222,9 @@ L.DistortableImage.Edit = L.Handler.extend({ handlerName = keymap[event.which]; if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { - this[handlerName].call(this); + if (this._selected) { + this[handlerName].call(this); + } } }, @@ -233,8 +236,8 @@ L.DistortableImage.Edit = L.Handler.extend({ map.removeLayer(this._handles[this._mode]); /* Switch mode. */ - if (this._mode === "rotateScale") { this._mode = "distort"; } - else { this._mode = "rotateScale"; } + if (this._mode === "rotateScale") { this._mode = "distort"; } + else { this._mode = "rotateScale"; } map.addLayer(this._handles[this._mode]); @@ -319,6 +322,7 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _select: function(event) { + this._selected = true; this._showToolbar(); this._showMarkers(); @@ -326,8 +330,9 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _deselect: function() { - if (this._mode === "lock") { return; } + this._selected = false; this._hideToolbar(); + if (this._mode === "lock") { return; } this._hideMarkers(); }, diff --git a/test/src/edit/DistortableImageEditSpec.js b/test/src/edit/DistortableImageEditSpec.js index af1f50b01..4ac2bce0d 100644 --- a/test/src/edit/DistortableImageEditSpec.js +++ b/test/src/edit/DistortableImageEditSpec.js @@ -27,6 +27,8 @@ describe("L.DistortableImage.Edit", function() { overlay.editing.enable(); + overlay.editing._selected = true; + overlay._updateCorner(0, new L.LatLng(41.7934, -87.6252)); overlay.fire('update'); From 57338057e8c99d32aec1e59fad22517a5da1e6df Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Mon, 22 Apr 2019 02:17:49 -0400 Subject: [PATCH 09/18] Make sure that removeOverlay first removes layer from feature group --- dist/leaflet.distortableimage.js | 16 +++++++++++++--- src/DistortableCollection.js | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 80bd795bd..9dcf6c31a 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -942,6 +942,16 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { this._deselectAll(e); + } + if (e.key === "Backspace") { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit._selected) { + var choice = edit.confirmDelete(); + if (choice) { this.removeLayer(layer); } + return; + } + }, this); } }, @@ -952,10 +962,10 @@ L.DistortableCollection = L.FeatureGroup.extend({ if (!this.isSelected(overlay)) { return; } this.eachLayer(function(layer) { + var edit = layer.editing; + edit._deselect(); + for (i = 0; i < 4; i++) { - if (layer !== overlay && layer.editing._mode !== "lock") { - layer.editing._hideToolbar(); - } layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( layer.getCorner(i) ); diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index e2e49a4f9..b69679c7f 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -83,6 +83,16 @@ L.DistortableCollection = L.FeatureGroup.extend({ _onKeyDown: function(e) { if (e.key === "Escape") { this._deselectAll(e); + } + if (e.key === "Backspace") { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit._selected) { + var choice = edit.confirmDelete(); + if (choice) { this.removeLayer(layer); } + return; + } + }, this); } }, @@ -93,10 +103,10 @@ L.DistortableCollection = L.FeatureGroup.extend({ if (!this.isSelected(overlay)) { return; } this.eachLayer(function(layer) { + var edit = layer.editing; + edit._deselect(); + for (i = 0; i < 4; i++) { - if (layer !== overlay && layer.editing._mode !== "lock") { - layer.editing._hideToolbar(); - } layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( layer.getCorner(i) ); From c4d4d0c524b9eef188f8e6aa379ef761db9c6416 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Mon, 22 Apr 2019 06:34:33 -0400 Subject: [PATCH 10/18] revert support for multi image delete --- dist/leaflet.distortableimage.js | 24 ++++++++++++++++-------- src/DistortableCollection.js | 24 ++++++++++++++++-------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 9dcf6c31a..d6b267fdc 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -944,14 +944,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ this._deselectAll(e); } if (e.key === "Backspace") { - this.eachLayer(function(layer) { - var edit = layer.editing; - if (edit._selected) { - var choice = edit.confirmDelete(); - if (choice) { this.removeLayer(layer); } - return; - } - }, this); + this._removeFromGroup(e); } }, @@ -1001,6 +994,21 @@ L.DistortableCollection = L.FeatureGroup.extend({ L.DomEvent.stopPropagation(event); }, + _removeFromGroup: function(e) { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit._selected && edit._mode !== "lock") { + var choice = edit.confirmDelete(); + if (choice) { + edit._selected = false; + this.removeLayer(layer); + } else { + L.DomEvent.stopPropagation(e); + return; + } + } + }, this); + }, /** * images in 'lock' mode are included in this feature group collection for functionalities * such as export, but are filtered out for editing / dragging here diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index b69679c7f..ed964c56d 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -85,14 +85,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ this._deselectAll(e); } if (e.key === "Backspace") { - this.eachLayer(function(layer) { - var edit = layer.editing; - if (edit._selected) { - var choice = edit.confirmDelete(); - if (choice) { this.removeLayer(layer); } - return; - } - }, this); + this._removeFromGroup(e); } }, @@ -142,6 +135,21 @@ L.DistortableCollection = L.FeatureGroup.extend({ L.DomEvent.stopPropagation(event); }, + _removeFromGroup: function(e) { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit._selected && edit._mode !== "lock") { + var choice = edit.confirmDelete(); + if (choice) { + edit._selected = false; + this.removeLayer(layer); + } else { + L.DomEvent.stopPropagation(e); + return; + } + } + }, this); + }, /** * images in 'lock' mode are included in this feature group collection for functionalities * such as export, but are filtered out for editing / dragging here From 8fc720a82d0c809eb5705e71de18bcc62f4f17fd Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Tue, 30 Apr 2019 19:28:02 -0400 Subject: [PATCH 11/18] Update keybindings to use key property --- dist/leaflet.distortableimage.js | 25 ++++++++++++------------- src/edit/DistortableImage.Edit.js | 25 ++++++++++++------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index d6b267fdc..f6f5927e3 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1244,18 +1244,17 @@ L.DistortableImage.Edit = L.Handler.extend({ opacity: 0.7, outline: "1px solid red", keymap: { - 8: "_removeOverlay", // backspace windows / delete mac - 46: "_removeOverlay", // delete windows / delete + fn mac - // 20: "_toggleRotate", // CAPS - 27: "_deselect", // esc - 68: "_toggleRotateScale", // d - 74: "_sendUp", // j - 75: "_sendDown", // k - 76: "_toggleLock", // l - 79: "_toggleOutline", // o - 82: "_toggleRotateScale", // r - // 83: "_toggleScale", // s - 84: "_toggleTransparency" // t + 'Backspace': '_removeOverlay', // backspace windows / delete mac + // 'CapsLock': '_toggleRotate', + 'Escape': '_deselect', + 'd': '_toggleRotateScale', + 'r': '_toggleRotateScale', + 'j': '_sendUp', + 'k': '_sendDown', + 'l': '_toggleLock', + 'o': '_toggleOutline', + // 's': '_toggleScale', + 't': '_toggleTransparency', } }, @@ -1458,7 +1457,7 @@ L.DistortableImage.Edit = L.Handler.extend({ _onKeyDown: function(event) { var keymap = this.options.keymap, - handlerName = keymap[event.which]; + handlerName = keymap[event.key]; if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { if (this._selected) { diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index f6ccd85eb..a55631b01 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -5,18 +5,17 @@ L.DistortableImage.Edit = L.Handler.extend({ opacity: 0.7, outline: "1px solid red", keymap: { - 8: "_removeOverlay", // backspace windows / delete mac - 46: "_removeOverlay", // delete windows / delete + fn mac - // 20: "_toggleRotate", // CAPS - 27: "_deselect", // esc - 68: "_toggleRotateScale", // d - 74: "_sendUp", // j - 75: "_sendDown", // k - 76: "_toggleLock", // l - 79: "_toggleOutline", // o - 82: "_toggleRotateScale", // r - // 83: "_toggleScale", // s - 84: "_toggleTransparency" // t + 'Backspace': '_removeOverlay', // backspace windows / delete mac + // 'CapsLock': '_toggleRotate', + 'Escape': '_deselect', + 'd': '_toggleRotateScale', + 'r': '_toggleRotateScale', + 'j': '_sendUp', + 'k': '_sendDown', + 'l': '_toggleLock', + 'o': '_toggleOutline', + // 's': '_toggleScale', + 't': '_toggleTransparency', } }, @@ -219,7 +218,7 @@ L.DistortableImage.Edit = L.Handler.extend({ _onKeyDown: function(event) { var keymap = this.options.keymap, - handlerName = keymap[event.which]; + handlerName = keymap[event.key]; if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { if (this._selected) { From d9ef68217bbcb9305fcdb78f7414355ea1ab6050 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Tue, 30 Apr 2019 22:52:56 -0400 Subject: [PATCH 12/18] Add option to initialize image as select --- dist/leaflet.distortableimage.js | 1684 +++++++++++++++-------------- examples/index.html | 1 + examples/select.html | 24 +- src/DistortableCollection.js | 10 + src/edit/DistortableImage.Edit.js | 192 ++-- 5 files changed, 998 insertions(+), 913 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index f6f5927e3..cde2f0c94 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -139,920 +139,930 @@ L.TrigUtil = { } }; -L.EXIF = function getEXIFdata(img) { - if (Object.keys(EXIF.getAllTags(img)).length !== 0) { - console.log(EXIF.getAllTags(img)); - var GPS = EXIF.getAllTags(img), - altitude; - - /* If the lat/lng is available. */ - if ( - typeof GPS.GPSLatitude !== "undefined" && - typeof GPS.GPSLongitude !== "undefined" - ) { - // sadly, encoded in [degrees,minutes,seconds] - // primitive value = GPS.GPSLatitude[x].numerator - var lat = - GPS.GPSLatitude[0] + - GPS.GPSLatitude[1] / 60 + - GPS.GPSLatitude[2] / 3600; - var lng = - GPS.GPSLongitude[0] + - GPS.GPSLongitude[1] / 60 + - GPS.GPSLongitude[2] / 3600; - - if (GPS.GPSLatitudeRef !== "N") { - lat = lat * -1; - } - if (GPS.GPSLongitudeRef === "W") { - lng = lng * -1; - } - } - - // Attempt to use GPS compass heading; will require - // some trig to calc corner points, which you can find below: +L.DistortableImageOverlay = L.ImageOverlay.extend({ + include: L.Mixin.Events, - var angle = 0; - // "T" refers to "True north", so -90. - if (GPS.GPSImgDirectionRef === "T") { - angle = - (Math.PI / 180) * - (GPS.GPSImgDirection.numerator / GPS.GPSImgDirection.denominator - 90); - } - // "M" refers to "Magnetic north" - else if (GPS.GPSImgDirectionRef === "M") { - angle = - (Math.PI / 180) * - (GPS.GPSImgDirection.numerator / GPS.GPSImgDirection.denominator - 90); - } else { - console.log("No compass data found"); - } + options: { + alt: "", + height: 200, + crossOrigin: true, + // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) + edgeMinWidth: 520 + }, - console.log("Orientation:", GPS.Orientation); + initialize: function(url, options) { + this._toolArray = L.DistortableImage.EditToolbarDefaults; + this.edgeMinWidth = this.options.edgeMinWidth; + this._url = url; + this._rotation = this.options.rotation; - /* If there is orientation data -- i.e. landscape/portrait etc */ - if (GPS.Orientation === 6) { - //CCW - angle += (Math.PI / 180) * -90; - } else if (GPS.Orientation === 8) { - //CW - angle += (Math.PI / 180) * 90; - } else if (GPS.Orientation === 3) { - //180 - angle += (Math.PI / 180) * 180; - } + L.Util.setOptions(this, options); + }, - /* If there is altitude data */ - if ( - typeof GPS.GPSAltitude !== "undefined" && - typeof GPS.GPSAltitudeRef !== "undefined" - ) { - // Attempt to use GPS altitude: - // (may eventually need to find EXIF field of view for correction) - if ( - typeof GPS.GPSAltitude !== "undefined" && - typeof GPS.GPSAltitudeRef !== "undefined" - ) { - altitude = - GPS.GPSAltitude.numerator / GPS.GPSAltitude.denominator + - GPS.GPSAltitudeRef; - } else { - altitude = 0; // none - } - } - } else { - alert("EXIF initialized. Press again to view data in console."); - } -}; + onAdd: function(map) { + /* Copied from L.ImageOverlay */ + this._map = map; -L.EditHandle = L.Marker.extend({ - initialize: function(overlay, corner, options) { - var markerOptions, - latlng = overlay._corners[corner]; + if (!this._image) { this._initImage(); } + if (!this._events) { this._initEvents(); } - L.setOptions(this, options); + map._panes.overlayPane.appendChild(this._image); - this._handled = overlay; - this._corner = corner; + map.on("viewreset", this._reset, this); + /* End copied from L.ImageOverlay */ - markerOptions = { - draggable: true, - zIndexOffset: 10 - }; + /* Use provided corners if available */ + if (this.options.corners) { + this._corners = this.options.corners; + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } - if (options && options.hasOwnProperty("draggable")) { - markerOptions.draggable = options.draggable; + /* This reset happens before image load; it allows + * us to place the image on the map earlier with + * "guessed" dimensions. */ + this._reset(); } - L.Marker.prototype.initialize.call(this, latlng, markerOptions); - }, - - onAdd: function(map) { - L.Marker.prototype.onAdd.call(this, map); - this._bindListeners(); + /* Have to wait for the image to load because + * we need to access its width and height. */ + L.DomEvent.on(this._image, "load", function() { + this._initImageDimensions(); + this._reset(); + /* Initialize default corners if not already set */ + if (!this._corners) { + if (map.options.zoomAnimation && L.Browser.any3d) { + map.on("zoomanim", this._animateZoom, this); + } + } + }, this); - this.updateHandle(); + this.fire("add"); }, onRemove: function(map) { - this._unbindListeners(); - L.Marker.prototype.onRemove.call(this, map); - }, - - _onHandleDragStart: function() { - this._handled.fire("editstart"); - }, - - _onHandleDragEnd: function() { - this._fireEdit(); - }, + this.fire("remove"); - _fireEdit: function() { - this._handled.edited = true; - this._handled.fire("edit"); + L.ImageOverlay.prototype.onRemove.call(this, map); }, - _bindListeners: function() { - this.on( - { - dragstart: this._onHandleDragStart, - drag: this._onHandleDrag, - dragend: this._onHandleDragEnd - }, - this - ); - - this._handled._map.on("zoomend", this.updateHandle, this); + _initImage: function() { + L.ImageOverlay.prototype._initImage.call(this); - this._handled.on("update", this.updateHandle, this); + L.extend(this._image, { + alt: this.options.alt + }); }, - _unbindListeners: function() { - this.off( - { - dragstart: this._onHandleDragStart, - drag: this._onHandleDrag, - dragend: this._onHandleDragEnd - }, - this - ); - - this._handled._map.off("zoomend", this.updateHandle, this); - this._handled.off("update", this.updateHandle, this); - } -}); - -L.LockHandle = L.EditHandle.extend({ - options: { - TYPE: 'lock', - icon: new L.Icon({ - iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAD8SURBVO3BPU7CYAAA0AdfjIcQlRCQBG7C3gk2uIPG2RC3Dk16Gz0FTO1WZs/gwGCMP/2+xsSl7+n1er1Iz9LtRQjaPeMeO+TinLDCJV78YqjdA04YodKuxhUaPGoRxMmxwRQZSt87Yo4KExGCeAUyLLFB4bMacxywEClIU2KDKXbInTUYo8JCgoFuGoxQO5uiwY1EA91VmDqrcKeDoX8WdNNgjApvmGGLXKIgXY0xGkxQYItrrFFIEKQ5Yo4KEx9yrDFDhlKkIF6NOQ5Y+KpAhiXWKEQI4pxwiwoLPyuxwQw75FoE7fZYocFEuwI7jHCBV39gL92TXq/Xi/AOcmczZmaIMScAAAAASUVORK5CYII=', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, - - /* cannot be dragged */ - _onHandleDrag: function() { - }, - - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - L.DomUtil.removeClass(this._handled.getElement(), 'selected'); - } - -}); - -L.DistortHandle = L.EditHandle.extend({ - options: { - TYPE: "distort", - icon: new L.Icon({ - iconUrl: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAChSURBVO3BIU4DURgGwNkvL2B6AkQTLBqP4QCoSm7DDXoBLBZHDbfgICAIZjEV3YTn9uVHdMZZtcnCfI13bIzxg0emg6Nm6QVbYz3jylEsXRrvwommb49X67jFkz80fR9Mb1YxTzqiWBSLYlEsikWxKBbFolgUi2JRLIpFsSgWxaJY03fHHOu40dH07bAzWCx9Ge/TiWbpHgdsjPGNB2f/yS+7xRCyiiZPJQAAAABJRU5ErkJggg==", - iconSize: [32, 32], - iconAnchor: [16, 16] - }) + _addTool: function(tool) { + this._toolArray.push(tool); + L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: this._toolArray + } + }); }, - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, - - _onHandleDrag: function() { - this._handled._updateCorner(this._corner, this.getLatLng()); + _initImageDimensions: function() { + var map = this._map, + originalImageWidth = L.DomUtil.getStyle(this._image, "width"), + originalImageHeight = L.DomUtil.getStyle(this._image, "height"), + aspectRatio = + parseInt(originalImageWidth) / parseInt(originalImageHeight), + imageHeight = this.options.height, + imageWidth = parseInt(aspectRatio * imageHeight), + center = map.latLngToContainerPoint(map.getCenter()), + offset = new L.Point(imageWidth, imageHeight).divideBy(2); - this._handled.fire("update"); - this._handled.editing._showToolbar(); - } -}); + if (this.options.corners) { + this._corners = this.options.corners; + } else { + this._corners = [ + map.containerPointToLatLng(center.subtract(offset)), + map.containerPointToLatLng( + center.add(new L.Point(offset.x, -offset.y)) + ), + map.containerPointToLatLng( + center.add(new L.Point(-offset.x, offset.y)) + ), + map.containerPointToLatLng(center.add(offset)) + ]; + } + }, -L.RotateScaleHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotateScale', - icon: new L.Icon({ - iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, + _initEvents: function() { + this._events = ["click"]; - _onHandleDrag: function() { - var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], - newLatLng = this.getLatLng(), + for (var i = 0, l = this._events.length; i < l; i++) { + L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); + } + }, - angle = this._calculateAngle(formerLatLng, newLatLng), - scale = this._calculateScalingFactor(formerLatLng, newLatLng); + /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ + _fireMouseEvent: function(event) { + if (!this.hasEventListeners(event.type)) { return; } - overlay.editing._rotateBy(angle); + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(event), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); - /* - checks whether the "edgeMinWidth" property is set and tracks the minimum edge length; - this enables preventing scaling to zero, but we might also add an overall scale limit - */ - if (this._handled.hasOwnProperty('edgeMinWidth')){ - var edgeMinWidth = this._handled.edgeMinWidth, - w = L.latLng(overlay._corners[0]).distanceTo(overlay._corners[1]), - h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); - if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { - overlay.editing._scaleBy(scale); - } - } + this.fire(event.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: event + }); + }, - overlay.fire('update'); + _updateCorner: function(corner, latlng) { + this._corners[corner] = latlng; + this._reset(); + }, - this._handled.editing._showToolbar(); + // fires a reset after all corner positions are updated instead of after each one (above). Use for translating + _updateCorners: function(latlngObj) { + var i = 0; + for (var k in latlngObj) { + this._corners[i] = latlngObj[k]; + i += 1; + } - }, + this._reset(); + }, - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, + _updateCornersFromPoints: function(pointsObj) { + var map = this._map; + var i = 0; + for (var k in pointsObj) { + this._corners[i] = map.layerPointToLatLng(pointsObj[k]); + i += 1; + } - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + this._reset(); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ + /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ + _getTranslateString: function(point) { + // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate + // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care + // (same speed either way), Opera 12 doesn't support translate3d - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + var is3d = L.Browser.webkit3d, + open = "translate" + (is3d ? "3d" : "") + "(", + close = (is3d ? ",0" : "") + ")"; - return newAngle - initialAngle; - }, + return open + point.x + "px," + point.y + "px" + close; + }, - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + _reset: function() { + var map = this._map, + image = this._image, + latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), + transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), + topLeft = latLngToLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, + /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ + image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; + }, - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; + /* + * Calculates the transform string that will be correct *at the end* of zooming. + * Leaflet then generates a CSS3 animation between the current transform and + * future transform which makes the transition appear smooth. + */ + _animateZoom: function(event) { + var map = this._map, + image = this._image, + latLngToNewLayerPoint = function(latlng) { + return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); + }, + transformMatrix = this._calculateProjectiveTransform( + latLngToNewLayerPoint + ), + topLeft = latLngToNewLayerPoint(this._corners[0]), + warp = L.DomUtil.getMatrixString(transformMatrix), + translation = this._getTranslateString(topLeft); - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); + /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ + image._leaflet_pos = topLeft; -L.RotateHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: new L.Icon({ - iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, - - _onHandleDrag: function() { - var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], - newLatLng = this.getLatLng(), - angle = this._calculateAngle(formerLatLng, newLatLng); + if (!L.Browser.gecko) { + image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + } + }, - overlay.editing._rotateBy(angle); + getCorners: function() { + return this._corners; + }, - overlay.fire('update'); + getCorner: function(i) { + return this._corners[i]; + }, - this._handled.editing._showToolbar(); - }, + /* + * Calculates the centroid of the image. + * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral + */ + getCenter: function(ll2c, c2ll) { + var map = this._map, + latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, + cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, + nw = latLngToCartesian.call(map, this._corners[0]), + ne = latLngToCartesian.call(map, this._corners[1]), + se = latLngToCartesian.call(map, this._corners[2]), + sw = latLngToCartesian.call(map, this._corners[3]), + nmid = nw.add(ne.subtract(nw).divideBy(2)), + smid = sw.add(se.subtract(sw).divideBy(2)); - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, + return cartesianToLatLng.call( + map, + nmid.add(smid.subtract(nmid).divideBy(2)) + ); + }, - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + // Use for translation calculations - for translation the delta for 1 corner applies to all 4 + _calcCornerPointDelta: function() { + return this._dragStartPoints[0].subtract(this._dragPoints[0]); + }, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + _calcCenterTwoCornerPoints: function(topLeft, topRight) { + var toolPoint = { x: "", y: "" }; - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; + toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; - return newAngle - initialAngle; - }, + return toolPoint; + }, - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + _calculateProjectiveTransform: function(latLngToCartesian) { + /* Setting reasonable but made-up image defaults + * allow us to place images on the map before + * they've finished downloading. */ + var offset = latLngToCartesian(this._corners[0]), + w = this._image.offsetWidth || 500, + h = this._image.offsetHeight || 375, + c = [], + j; + /* Convert corners to container points (i.e. cartesian coordinates). */ + for (j = 0; j < this._corners.length; j++) { + c.push(latLngToCartesian(this._corners[j])._subtract(offset)); + } - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + /* + * This matrix describes the action of the CSS transform on each corner of the image. + * It maps from the coordinate system centered at the upper left corner of the image + * to the region bounded by the latlngs in this._corners. + * For example: + * 0, 0, c[0].x, c[0].y + * says that the upper-left corner of the image maps to the first latlng in this._corners. + */ + return L.MatrixUtil.general2DProjection( + 0, 0, c[0].x, c[0].y, + w, 0, c[1].x, c[1].y, + 0, h, c[2].x, c[2].y, + w, h, c[3].x, c[3].y + ); + } +}); - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); +L.distortableImageOverlay = function(id, options) { + return new L.DistortableImageOverlay(id, options); +}; - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); -L.ScaleHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: new L.Icon({ - iconUrl:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTkiIGhlaWdodD0iNDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iIj48cmVjdCBpZD0iYmFja2dyb3VuZHJlY3QiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9IjAiIHk9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgY2xhc3M9IiIgc3R5bGU9IiIvPjxnIGNsYXNzPSJjdXJyZW50TGF5ZXIiIHN0eWxlPSIiPjx0aXRsZT5MYXllciAxPC90aXRsZT48cGF0aCBkPSJNNDU5LjA0OTE1OTUzMDQ3MTM0LDg2LjkyNjIzNDUxMjU1MDAyIFYwIGgtODUuNzE0NTczMzU2MzEyMDkgdjI3LjA0MzcxNzQwMzkwNDQ1MiBIODUuNzE0NTczMzU2MzEyMDMgVjAgSDAgdjg2LjkyNjIzNDUxMjU1MDAyIGgyNS43MTQzNzIwMDY4OTM2MjYgdjI4OS43NTQxMTUwNDE4MzM0IEgwIHY4Ni45MjYyMzQ1MTI1NTAwMiBoODUuNzE0NTczMzU2MzEyMDkgdi0yNy4wNDM3MTc0MDM5MDQ0NTIgaDI4NS43MTUyNDQ1MjEwNDAzIHYyNy4wNDM3MTc0MDM5MDQ0NTIgaDg1LjcxNDU3MzM1NjMxMjA5IHYtODYuOTI2MjM0NTEyNTUwMDIgaC0yMy44MDk2MDM3MTAwODY2OSBWODYuOTI2MjM0NTEyNTUwMDIgSDQ1OS4wNDkxNTk1MzA0NzEzNCB6TTM4NC43NjMxOTU5NTUwMDA5LDEyLjU1NjAxMTY1MTgxMjc4MSBoNjEuOTA0OTY5NjQ2MjI1Mzk2IHY2Mi43ODAwNTgyNTkwNjM5MSBoLTYxLjkwNDk2OTY0NjIyNTM5NiBWMTIuNTU2MDExNjUxODEyNzgxIHpNMTIuMzgwOTkzOTI5MjQ1MDUsMTIuNTU2MDExNjUxODEyNzgxIGg2MS45MDQ5Njk2NDYyMjUzOTYgdjYyLjc4MDA1ODI1OTA2MzkxIEgxMi4zODA5OTM5MjkyNDUwNSBWMTIuNTU2MDExNjUxODEyNzgxIHpNNzQuMjg1OTYzNTc1NDcwNTMsNDUxLjA1MDU3MjQxNTEyMDY2IEgxMi4zODA5OTM5MjkyNDUwNSB2LTYyLjc4MDA1ODI1OTA2MzkxIGg2MS45MDQ5Njk2NDYyMjUzOTYgVjQ1MS4wNTA1NzI0MTUxMjA2NiB6TTQ0NS43MTU3ODE0NTI4MjI3NCw0NTEuMDUwNTcyNDE1MTIwNjYgaC02Mi44NTczNTM3OTQ2Mjg4NjQgdi02Mi43ODAwNTgyNTkwNjM5MSBoNjIuODU3MzUzNzk0NjI4ODY0IFY0NTEuMDUwNTcyNDE1MTIwNjYgek00MDcuNjIwNDE1NTE2Njg0MjYsMzc2LjY4MDM0OTU1NDM4MzQ0IGgtMzYuMTkwNTk3NjM5MzMxNzcgdjMyLjgzODc5OTcwNDc0MTEyIEg4NS43MTQ1NzMzNTYzMTIwMyB2LTMyLjgzODc5OTcwNDc0MTEyIEg0OS41MjM5NzU3MTY5ODAzMiBWODYuOTI2MjM0NTEyNTUwMDIgaDM2LjE5MDU5NzYzOTMzMTc3IFY1MC4yMjQwNDY2MDcyNTExMjUgaDI4Ny42MjAwMTI4MTc4NDcyIHYzNi43MDIxODc5MDUyOTg5IGgzNC4yODU4MjkzNDI1MjQ4MzUgVjM3Ni42ODAzNDk1NTQzODM0NCB6IiBpZD0ic3ZnXzIiIGNsYXNzPSIiIGZpbGw9IiMxYTFhZWIiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L3N2Zz4=', - iconSize: [32, 32], - iconAnchor: [16, 16]} - ) - }, +L.DistortableCollection = L.FeatureGroup.extend({ + include: L.Mixin.Events, - _onHandleDrag: function() { - var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], - newLatLng = this.getLatLng(), + onAdd: function(map) { + L.FeatureGroup.prototype.onAdd.call(this, map); - scale = this._calculateScalingFactor(formerLatLng, newLatLng); + this._map = map; - overlay.editing._scaleBy(scale); + L.DomEvent.on(document, "keydown", this._onKeyDown, this); + L.DomEvent.on(map, "click", this._deselectAll, this); - overlay.fire('update'); + /** + * the box zoom override works, but there is a bug involving click event propogation. + * keeping uncommented for now so that it isn't used as a multi-select mechanism + */ - this._handled.editing._showToolbar(); - }, + // L.DomEvent.on(map, "boxzoomend", this._addSelections, this); - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, + var lastSelected; - /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + this.eachLayer(function(layer) { + L.DomEvent.on(layer._image, "mousedown", this._deselectOthers, this); + L.DomEvent.on(layer, "dragstart", this._dragStartMultiple, this); + L.DomEvent.on(layer, "drag", this._dragMultiple, this); - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + if (layer.options.selected) { + layer.editing._deselect(); + lastSelected = layer.editing; + } + }, this); - initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), - newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); + if (lastSelected) { lastSelected._select(); } - return newAngle - initialAngle; - }, + }, - /* Takes two latlngs and calculates the scaling difference. */ - _calculateScalingFactor: function(latlngA, latlngB) { - var map = this._handled._map, + onRemove: function() { + var map = this._map; - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), - formerPoint = map.latLngToLayerPoint(latlngA), - newPoint = map.latLngToLayerPoint(latlngB), + L.DomEvent.off(document, "keydown", this._onKeyDown, this); + L.DomEvent.off(map, "click", this._deselectAll, this); + // L.DomEvent.off(map, "boxzoomend", this._addSelections, this); - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); + this.eachLayer(function(layer) { + L.DomEvent.off(layer._image, "mousedown", this._deselectOthers, this); + L.DomEvent.off(layer, "dragstart", this._dragStartMultiple, this); + L.DomEvent.off(layer, "drag", this._dragMultiple, this); + }, this); + }, - return Math.sqrt(newRadiusSquared / formerRadiusSquared); - }, + isSelected: function(overlay) { + return L.DomUtil.hasClass(overlay.getElement(), "selected"); + }, - /* Distance between two points in cartesian space, squared (distance formula). */ - _d2: function(a, b) { - var dx = a.x - b.x, - dy = a.y - b.y; + _toggleMultiSelect: function(event, edit) { + if (edit._mode === "lock") { return; } - return Math.pow(dx, 2) + Math.pow(dy, 2); - } -}); + if (event.metaKey || event.ctrlKey) { + L.DomUtil.toggleClass(event.target, "selected"); + } + }, -L.DistortableImageOverlay = L.ImageOverlay.extend({ - include: L.Mixin.Events, + _deselectOthers: function(event) { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (layer._image !== event.target) { + edit._deselect(); + } else { + this._toggleMultiSelect(event, edit); + } + }, this); - options: { - alt: "", - height: 200, - crossOrigin: true, - // todo: find ideal number to prevent distortions during RotateScale, and make it dynamic (remove hardcoding) - edgeMinWidth: 520 + L.DomEvent.stopPropagation(event); }, - initialize: function(url, options) { - this._toolArray = L.DistortableImage.EditToolbarDefaults; - this.edgeMinWidth = this.options.edgeMinWidth; - this._url = url; - this._rotation = this.options.rotation; + _addSelections: function(e) { + var box = e.boxZoomBounds, + i = 0; - L.Util.setOptions(this, options); + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit.toolbar) { + edit._hideToolbar(); + } + for (i = 0; i < 4; i++) { + if (box.contains(layer.getCorner(i)) && edit._mode !== "lock") { + L.DomUtil.addClass(layer.getElement(), "selected"); + break; + } + } + }); }, - onAdd: function(map) { - /* Copied from L.ImageOverlay */ - this._map = map; + _onKeyDown: function(e) { + if (e.key === "Escape") { + this._deselectAll(e); + } + if (e.key === "Backspace") { + this._removeFromGroup(e); + } + }, - if (!this._image) { this._initImage(); } - if (!this._events) { this._initEvents(); } + _dragStartMultiple: function(event) { + var overlay = event.target, + i; - map._panes.overlayPane.appendChild(this._image); + if (!this.isSelected(overlay)) { return; } - map.on("viewreset", this._reset, this); - /* End copied from L.ImageOverlay */ + this.eachLayer(function(layer) { + var edit = layer.editing; + edit._deselect(); - /* Use provided corners if available */ - if (this.options.corners) { - this._corners = this.options.corners; - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on("zoomanim", this._animateZoom, this); + for (i = 0; i < 4; i++) { + layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( + layer.getCorner(i) + ); } + }); + }, - /* This reset happens before image load; it allows - * us to place the image on the map earlier with - * "guessed" dimensions. */ - this._reset(); + _dragMultiple: function(event) { + var overlay = event.target, + map = this._map, + i; + + if (!this.isSelected(overlay)) { return; } + + overlay._dragPoints = {}; + + for (i = 0; i < 4; i++) { + overlay._dragPoints[i] = map.latLngToLayerPoint(overlay.getCorner(i)); } - /* Have to wait for the image to load because - * we need to access its width and height. */ - L.DomEvent.on(this._image, "load", function() { - this._initImageDimensions(); - this._reset(); - /* Initialize default corners if not already set */ - if (!this._corners) { - if (map.options.zoomAnimation && L.Browser.any3d) { - map.on("zoomanim", this._animateZoom, this); + var cpd = overlay._calcCornerPointDelta(); + + this._updateCollectionFromPoints(cpd, overlay); + }, + + _deselectAll: function(event) { + this.eachLayer(function(layer) { + var edit = layer.editing; + L.DomUtil.removeClass(layer.getElement(), "selected"); + edit._deselect(); + }); + + L.DomEvent.stopPropagation(event); + }, + + _removeFromGroup: function(e) { + this.eachLayer(function(layer) { + var edit = layer.editing; + if (edit._selected && edit._mode !== "lock") { + var choice = edit.confirmDelete(); + if (choice) { + edit._selected = false; + this.removeLayer(layer); + } else { + L.DomEvent.stopPropagation(e); + return; } } }, this); - - this.fire("add"); }, + /** + * images in 'lock' mode are included in this feature group collection for functionalities + * such as export, but are filtered out for editing / dragging here + */ + _calcCollectionFromPoints: function(cpd, overlay) { + var layersToMove = [], + p = new L.Transformation(1, -cpd.x, 1, -cpd.y); - onRemove: function(map) { - this.fire("remove"); + this.eachLayer(function(layer) { + if ( + layer !== overlay && + layer.editing._mode !== "lock" && + this.isSelected(layer) + ) { + layer._cpd = {}; - L.ImageOverlay.prototype.onRemove.call(this, map); + layer._cpd.val0 = p.transform(layer._dragStartPoints[0]); + layer._cpd.val1 = p.transform(layer._dragStartPoints[1]); + layer._cpd.val2 = p.transform(layer._dragStartPoints[2]); + layer._cpd.val3 = p.transform(layer._dragStartPoints[3]); + + layersToMove.push(layer); + } + }, this); + + return layersToMove; }, - _initImage: function() { - L.ImageOverlay.prototype._initImage.call(this); + /** + * cpd === cornerPointDelta + */ + _updateCollectionFromPoints: function(cpd, overlay) { + var layersToMove = this._calcCollectionFromPoints(cpd, overlay); + + layersToMove.forEach(function(layer) { + layer._updateCornersFromPoints(layer._cpd); + layer.fire("update"); + }, this); + } +}); + +L.distortableCollection = function(id, options) { + return new L.DistortableCollection(id, options); +}; +L.EXIF = function getEXIFdata(img) { + if (Object.keys(EXIF.getAllTags(img)).length !== 0) { + console.log(EXIF.getAllTags(img)); + var GPS = EXIF.getAllTags(img), + altitude; - L.extend(this._image, { - alt: this.options.alt - }); - }, + /* If the lat/lng is available. */ + if ( + typeof GPS.GPSLatitude !== "undefined" && + typeof GPS.GPSLongitude !== "undefined" + ) { + // sadly, encoded in [degrees,minutes,seconds] + // primitive value = GPS.GPSLatitude[x].numerator + var lat = + GPS.GPSLatitude[0] + + GPS.GPSLatitude[1] / 60 + + GPS.GPSLatitude[2] / 3600; + var lng = + GPS.GPSLongitude[0] + + GPS.GPSLongitude[1] / 60 + + GPS.GPSLongitude[2] / 3600; - _addTool: function(tool) { - this._toolArray.push(tool); - L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: this._toolArray + if (GPS.GPSLatitudeRef !== "N") { + lat = lat * -1; } - }); - }, + if (GPS.GPSLongitudeRef === "W") { + lng = lng * -1; + } + } - _initImageDimensions: function() { - var map = this._map, - originalImageWidth = L.DomUtil.getStyle(this._image, "width"), - originalImageHeight = L.DomUtil.getStyle(this._image, "height"), - aspectRatio = - parseInt(originalImageWidth) / parseInt(originalImageHeight), - imageHeight = this.options.height, - imageWidth = parseInt(aspectRatio * imageHeight), - center = map.latLngToContainerPoint(map.getCenter()), - offset = new L.Point(imageWidth, imageHeight).divideBy(2); + // Attempt to use GPS compass heading; will require + // some trig to calc corner points, which you can find below: - if (this.options.corners) { - this._corners = this.options.corners; + var angle = 0; + // "T" refers to "True north", so -90. + if (GPS.GPSImgDirectionRef === "T") { + angle = + (Math.PI / 180) * + (GPS.GPSImgDirection.numerator / GPS.GPSImgDirection.denominator - 90); + } + // "M" refers to "Magnetic north" + else if (GPS.GPSImgDirectionRef === "M") { + angle = + (Math.PI / 180) * + (GPS.GPSImgDirection.numerator / GPS.GPSImgDirection.denominator - 90); } else { - this._corners = [ - map.containerPointToLatLng(center.subtract(offset)), - map.containerPointToLatLng( - center.add(new L.Point(offset.x, -offset.y)) - ), - map.containerPointToLatLng( - center.add(new L.Point(-offset.x, offset.y)) - ), - map.containerPointToLatLng(center.add(offset)) - ]; + console.log("No compass data found"); } - }, - _initEvents: function() { - this._events = ["click"]; + console.log("Orientation:", GPS.Orientation); - for (var i = 0, l = this._events.length; i < l; i++) { - L.DomEvent.on(this._image, this._events[i], this._fireMouseEvent, this); + /* If there is orientation data -- i.e. landscape/portrait etc */ + if (GPS.Orientation === 6) { + //CCW + angle += (Math.PI / 180) * -90; + } else if (GPS.Orientation === 8) { + //CW + angle += (Math.PI / 180) * 90; + } else if (GPS.Orientation === 3) { + //180 + angle += (Math.PI / 180) * 180; } - }, - /* See src/layer/vector/Path.SVG.js in the Leaflet source. */ - _fireMouseEvent: function(event) { - if (!this.hasEventListeners(event.type)) { return; } + /* If there is altitude data */ + if ( + typeof GPS.GPSAltitude !== "undefined" && + typeof GPS.GPSAltitudeRef !== "undefined" + ) { + // Attempt to use GPS altitude: + // (may eventually need to find EXIF field of view for correction) + if ( + typeof GPS.GPSAltitude !== "undefined" && + typeof GPS.GPSAltitudeRef !== "undefined" + ) { + altitude = + GPS.GPSAltitude.numerator / GPS.GPSAltitude.denominator + + GPS.GPSAltitudeRef; + } else { + altitude = 0; // none + } + } + } else { + alert("EXIF initialized. Press again to view data in console."); + } +}; - var map = this._map, - containerPoint = map.mouseEventToContainerPoint(event), - layerPoint = map.containerPointToLayerPoint(containerPoint), - latlng = map.layerPointToLatLng(layerPoint); +L.EditHandle = L.Marker.extend({ + initialize: function(overlay, corner, options) { + var markerOptions, + latlng = overlay._corners[corner]; - this.fire(event.type, { - latlng: latlng, - layerPoint: layerPoint, - containerPoint: containerPoint, - originalEvent: event - }); - }, + L.setOptions(this, options); - _updateCorner: function(corner, latlng) { - this._corners[corner] = latlng; - this._reset(); - }, + this._handled = overlay; + this._corner = corner; - // fires a reset after all corner positions are updated instead of after each one (above). Use for translating - _updateCorners: function(latlngObj) { - var i = 0; - for (var k in latlngObj) { - this._corners[i] = latlngObj[k]; - i += 1; + markerOptions = { + draggable: true, + zIndexOffset: 10 + }; + + if (options && options.hasOwnProperty("draggable")) { + markerOptions.draggable = options.draggable; } - this._reset(); + L.Marker.prototype.initialize.call(this, latlng, markerOptions); }, - _updateCornersFromPoints: function(pointsObj) { - var map = this._map; - var i = 0; - for (var k in pointsObj) { - this._corners[i] = map.layerPointToLatLng(pointsObj[k]); - i += 1; - } + onAdd: function(map) { + L.Marker.prototype.onAdd.call(this, map); + this._bindListeners(); - this._reset(); + this.updateHandle(); }, - /* Copied from Leaflet v0.7 https://github.com/Leaflet/Leaflet/blob/66282f14bcb180ec87d9818d9f3c9f75afd01b30/src/dom/DomUtil.js#L189-L199 */ - /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.0 */ - _getTranslateString: function(point) { - // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate - // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care - // (same speed either way), Opera 12 doesn't support translate3d + onRemove: function(map) { + this._unbindListeners(); + L.Marker.prototype.onRemove.call(this, map); + }, + + _onHandleDragStart: function() { + this._handled.fire("editstart"); + }, - var is3d = L.Browser.webkit3d, - open = "translate" + (is3d ? "3d" : "") + "(", - close = (is3d ? ",0" : "") + ")"; + _onHandleDragEnd: function() { + this._fireEdit(); + }, - return open + point.x + "px," + point.y + "px" + close; + _fireEdit: function() { + this._handled.edited = true; + this._handled.fire("edit"); }, - _reset: function() { - var map = this._map, - image = this._image, - latLngToLayerPoint = L.bind(map.latLngToLayerPoint, map), - transformMatrix = this._calculateProjectiveTransform(latLngToLayerPoint), - topLeft = latLngToLayerPoint(this._corners[0]), - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); - - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; + _bindListeners: function() { + this.on( + { + dragstart: this._onHandleDragStart, + drag: this._onHandleDrag, + dragend: this._onHandleDragEnd + }, + this + ); - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); + this._handled._map.on("zoomend", this.updateHandle, this); - /* Set origin to the upper-left corner rather than the center of the image, which is the default. */ - image.style[L.DomUtil.TRANSFORM + "-origin"] = "0 0 0"; + this._handled.on("update", this.updateHandle, this); }, - /* - * Calculates the transform string that will be correct *at the end* of zooming. - * Leaflet then generates a CSS3 animation between the current transform and - * future transform which makes the transition appear smooth. - */ - _animateZoom: function(event) { - var map = this._map, - image = this._image, - latLngToNewLayerPoint = function(latlng) { - return map._latLngToNewLayerPoint(latlng, event.zoom, event.center); + _unbindListeners: function() { + this.off( + { + dragstart: this._onHandleDragStart, + drag: this._onHandleDrag, + dragend: this._onHandleDragEnd }, - transformMatrix = this._calculateProjectiveTransform( - latLngToNewLayerPoint - ), - topLeft = latLngToNewLayerPoint(this._corners[0]), - warp = L.DomUtil.getMatrixString(transformMatrix), - translation = this._getTranslateString(topLeft); + this + ); + + this._handled._map.off("zoomend", this.updateHandle, this); + this._handled.off("update", this.updateHandle, this); + } +}); + +L.LockHandle = L.EditHandle.extend({ + options: { + TYPE: 'lock', + icon: new L.Icon({ + iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAD8SURBVO3BPU7CYAAA0AdfjIcQlRCQBG7C3gk2uIPG2RC3Dk16Gz0FTO1WZs/gwGCMP/2+xsSl7+n1er1Iz9LtRQjaPeMeO+TinLDCJV78YqjdA04YodKuxhUaPGoRxMmxwRQZSt87Yo4KExGCeAUyLLFB4bMacxywEClIU2KDKXbInTUYo8JCgoFuGoxQO5uiwY1EA91VmDqrcKeDoX8WdNNgjApvmGGLXKIgXY0xGkxQYItrrFFIEKQ5Yo4KEx9yrDFDhlKkIF6NOQ5Y+KpAhiXWKEQI4pxwiwoLPyuxwQw75FoE7fZYocFEuwI7jHCBV39gL92TXq/Xi/AOcmczZmaIMScAAAAASUVORK5CYII=', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, + + /* cannot be dragged */ + _onHandleDrag: function() { + }, - /* See L.DomUtil.setPosition. Mainly for the purposes of L.Draggable. */ - image._leaflet_pos = topLeft; + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + L.DomUtil.removeClass(this._handled.getElement(), 'selected'); + } - if (!L.Browser.gecko) { - image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(" "); - } - }, +}); - getCorners: function() { - return this._corners; +L.DistortHandle = L.EditHandle.extend({ + options: { + TYPE: "distort", + icon: new L.Icon({ + iconUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAChSURBVO3BIU4DURgGwNkvL2B6AkQTLBqP4QCoSm7DDXoBLBZHDbfgICAIZjEV3YTn9uVHdMZZtcnCfI13bIzxg0emg6Nm6QVbYz3jylEsXRrvwommb49X67jFkz80fR9Mb1YxTzqiWBSLYlEsikWxKBbFolgUi2JRLIpFsSgWxaJY03fHHOu40dH07bAzWCx9Ge/TiWbpHgdsjPGNB2f/yS+7xRCyiiZPJQAAAABJRU5ErkJggg==", + iconSize: [32, 32], + iconAnchor: [16, 16] + }) }, - getCorner: function(i) { - return this._corners[i]; - }, + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, - /* - * Calculates the centroid of the image. - * See http://stackoverflow.com/questions/6149175/logical-question-given-corners-find-center-of-quadrilateral - */ - getCenter: function(ll2c, c2ll) { - var map = this._map, - latLngToCartesian = ll2c ? ll2c : map.latLngToLayerPoint, - cartesianToLatLng = c2ll ? c2ll : map.layerPointToLatLng, - nw = latLngToCartesian.call(map, this._corners[0]), - ne = latLngToCartesian.call(map, this._corners[1]), - se = latLngToCartesian.call(map, this._corners[2]), - sw = latLngToCartesian.call(map, this._corners[3]), - nmid = nw.add(ne.subtract(nw).divideBy(2)), - smid = sw.add(se.subtract(sw).divideBy(2)); + _onHandleDrag: function() { + this._handled._updateCorner(this._corner, this.getLatLng()); - return cartesianToLatLng.call( - map, - nmid.add(smid.subtract(nmid).divideBy(2)) - ); - }, + this._handled.fire("update"); + this._handled.editing._showToolbar(); + } +}); - // Use for translation calculations - for translation the delta for 1 corner applies to all 4 - _calcCornerPointDelta: function() { - return this._dragStartPoints[0].subtract(this._dragPoints[0]); - }, +L.RotateScaleHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotateScale', + icon: new L.Icon({ + iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, - _calcCenterTwoCornerPoints: function(topLeft, topRight) { - var toolPoint = { x: "", y: "" }; + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = this._handled._corners[this._corner], + newLatLng = this.getLatLng(), - toolPoint.x = topRight.x + (topLeft.x - topRight.x) / 2; - toolPoint.y = topRight.y + (topLeft.y - topRight.y) / 2; + angle = this._calculateAngle(formerLatLng, newLatLng), + scale = this._calculateScalingFactor(formerLatLng, newLatLng); - return toolPoint; - }, + overlay.editing._rotateBy(angle); - _calculateProjectiveTransform: function(latLngToCartesian) { - /* Setting reasonable but made-up image defaults - * allow us to place images on the map before - * they've finished downloading. */ - var offset = latLngToCartesian(this._corners[0]), - w = this._image.offsetWidth || 500, - h = this._image.offsetHeight || 375, - c = [], - j; - /* Convert corners to container points (i.e. cartesian coordinates). */ - for (j = 0; j < this._corners.length; j++) { - c.push(latLngToCartesian(this._corners[j])._subtract(offset)); - } + /* + checks whether the "edgeMinWidth" property is set and tracks the minimum edge length; + this enables preventing scaling to zero, but we might also add an overall scale limit + */ + if (this._handled.hasOwnProperty('edgeMinWidth')){ + var edgeMinWidth = this._handled.edgeMinWidth, + w = L.latLng(overlay._corners[0]).distanceTo(overlay._corners[1]), + h = L.latLng(overlay._corners[1]).distanceTo(overlay._corners[2]); + if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { + overlay.editing._scaleBy(scale); + } + } - /* - * This matrix describes the action of the CSS transform on each corner of the image. - * It maps from the coordinate system centered at the upper left corner of the image - * to the region bounded by the latlngs in this._corners. - * For example: - * 0, 0, c[0].x, c[0].y - * says that the upper-left corner of the image maps to the first latlng in this._corners. - */ - return L.MatrixUtil.general2DProjection( - 0, 0, c[0].x, c[0].y, - w, 0, c[1].x, c[1].y, - 0, h, c[2].x, c[2].y, - w, h, c[3].x, c[3].y - ); - } -}); + overlay.fire('update'); -L.distortableImageOverlay = function(id, options) { - return new L.DistortableImageOverlay(id, options); -}; + this._handled.editing._showToolbar(); + + }, + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), -L.DistortableCollection = L.FeatureGroup.extend({ - include: L.Mixin.Events, + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); - onAdd: function(map) { - L.FeatureGroup.prototype.onAdd.call(this, map); + return newAngle - initialAngle; + }, - this._map = map; + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, - L.DomEvent.on(document, "keydown", this._onKeyDown, this); - L.DomEvent.on(map, "click", this._deselectAll, this); + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), - /** - * the box zoom override works, but there is a bug involving click event propogation. - * keeping uncommented for now so that it isn't used as a multi-select mechanism - */ + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); - // L.DomEvent.on(map, "boxzoomend", this._addSelections, this); + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, - this.eachLayer(function(layer) { - L.DomEvent.on(layer._image, "mousedown", this._deselectOthers, this); - L.DomEvent.on(layer, "dragstart", this._dragStartMultiple, this); - L.DomEvent.on(layer, "drag", this._dragMultiple, this); - }, this); - }, + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; - onRemove: function() { - var map = this._map; + return Math.pow(dx, 2) + Math.pow(dy, 2); + } +}); - L.DomEvent.off(document, "keydown", this._onKeyDown, this); - L.DomEvent.off(map, "click", this._deselectAll, this); - // L.DomEvent.off(map, "boxzoomend", this._addSelections, this); +L.RotateHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: new L.Icon({ + iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAHiSURBVMXBa3HbShgA0PMp/1sCCo8oCEpgTaCXgIXAJiDzyCJoAUTm4UVQAns1Y8+snWnTvJyeE16hkjDgDrfoNTMKcpC9UPiLSo8JyetkjEHxjPCMyoS199kFoz8Iv1HpMaN3qWDCHoegOKkkRwnJpRmroHgiPFEZ8IBekzEGxQtUEhKSS/fB7Ew4U+lxcGkVZG9QWWPSFAxBcdK59KApuA+yNwp2uEdx1GN25sZJZULSfAtm77SlbNjju6MvG75u+WHRWVR6rDVjMPsgwYyVZl3pLTpHkyYHOx8syMiayaJzlDTZ9YyaZNFVkiYH2ZUEBcVJJXVImuz6Js3Qofe59pq7DoOTILu+g+a288mCouk7/1iH4qTS+2QdDppbV1ZJmrnDXnPnc5UOs2Z0fUmTuyBr+krvSioJyUmQO0dZM7mepMkWnaNRkyrJB6uskTSjxY3Fll8bvmJwlDb83FJ8gMqAB80uyBY3Trb82PAfvjj6vuHnluIdKgMeNXOwctK5NKBoHitrb1RJeHRp5Ux4ojLg0aWMHGQvUOkxIWkKVsHsTPiNSo8HDC5lZIsgO6n0uMUdRvQuFQxB8UR4RmXC2vvsgtEfhL+o9JiQvE7GGBTPCK9QSUjoMWgKDthjDrIX+h/k0I7gth6N5gAAAABJRU5ErkJggg==', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, + + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = this._handled._corners[this._corner], + newLatLng = this.getLatLng(), + angle = this._calculateAngle(formerLatLng, newLatLng); - this.eachLayer(function(layer) { - L.DomEvent.off(layer._image, "mousedown", this._deselectOthers, this); - L.DomEvent.off(layer, "dragstart", this._dragStartMultiple, this); - L.DomEvent.off(layer, "drag", this._dragMultiple, this); - }, this); - }, + overlay.editing._rotateBy(angle); - isSelected: function(overlay) { - return L.DomUtil.hasClass(overlay.getElement(), "selected"); - }, + overlay.fire('update'); - _toggleMultiSelect: function(event, edit) { - if (edit._mode === "lock") { return; } + this._handled.editing._showToolbar(); + }, - if (event.metaKey || event.ctrlKey) { - L.DomUtil.toggleClass(event.target, "selected"); - } - }, + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, - _deselectOthers: function(event) { - this.eachLayer(function(layer) { - var edit = layer.editing; - if (layer._image !== event.target) { - edit._deselect(); - } else { - this._toggleMultiSelect(event, edit); - } - }, this); + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, - L.DomEvent.stopPropagation(event); - }, + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), - _addSelections: function(e) { - var box = e.boxZoomBounds, - i = 0; + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); - this.eachLayer(function(layer) { - var edit = layer.editing; - if (edit.toolbar) { - edit._hideToolbar(); - } - for (i = 0; i < 4; i++) { - if (box.contains(layer.getCorner(i)) && edit._mode !== "lock") { - L.DomUtil.addClass(layer.getElement(), "selected"); - break; - } - } - }); - }, + return newAngle - initialAngle; + }, + + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, - _onKeyDown: function(e) { - if (e.key === "Escape") { - this._deselectAll(e); - } - if (e.key === "Backspace") { - this._removeFromGroup(e); - } - }, + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), - _dragStartMultiple: function(event) { - var overlay = event.target, - i; + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); - if (!this.isSelected(overlay)) { return; } + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, - this.eachLayer(function(layer) { - var edit = layer.editing; - edit._deselect(); + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; - for (i = 0; i < 4; i++) { - layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( - layer.getCorner(i) - ); - } - }); - }, + return Math.pow(dx, 2) + Math.pow(dy, 2); + } +}); - _dragMultiple: function(event) { - var overlay = event.target, - map = this._map, - i; +L.ScaleHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: new L.Icon({ + iconUrl:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTkiIGhlaWdodD0iNDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iIj48cmVjdCBpZD0iYmFja2dyb3VuZHJlY3QiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9IjAiIHk9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgY2xhc3M9IiIgc3R5bGU9IiIvPjxnIGNsYXNzPSJjdXJyZW50TGF5ZXIiIHN0eWxlPSIiPjx0aXRsZT5MYXllciAxPC90aXRsZT48cGF0aCBkPSJNNDU5LjA0OTE1OTUzMDQ3MTM0LDg2LjkyNjIzNDUxMjU1MDAyIFYwIGgtODUuNzE0NTczMzU2MzEyMDkgdjI3LjA0MzcxNzQwMzkwNDQ1MiBIODUuNzE0NTczMzU2MzEyMDMgVjAgSDAgdjg2LjkyNjIzNDUxMjU1MDAyIGgyNS43MTQzNzIwMDY4OTM2MjYgdjI4OS43NTQxMTUwNDE4MzM0IEgwIHY4Ni45MjYyMzQ1MTI1NTAwMiBoODUuNzE0NTczMzU2MzEyMDkgdi0yNy4wNDM3MTc0MDM5MDQ0NTIgaDI4NS43MTUyNDQ1MjEwNDAzIHYyNy4wNDM3MTc0MDM5MDQ0NTIgaDg1LjcxNDU3MzM1NjMxMjA5IHYtODYuOTI2MjM0NTEyNTUwMDIgaC0yMy44MDk2MDM3MTAwODY2OSBWODYuOTI2MjM0NTEyNTUwMDIgSDQ1OS4wNDkxNTk1MzA0NzEzNCB6TTM4NC43NjMxOTU5NTUwMDA5LDEyLjU1NjAxMTY1MTgxMjc4MSBoNjEuOTA0OTY5NjQ2MjI1Mzk2IHY2Mi43ODAwNTgyNTkwNjM5MSBoLTYxLjkwNDk2OTY0NjIyNTM5NiBWMTIuNTU2MDExNjUxODEyNzgxIHpNMTIuMzgwOTkzOTI5MjQ1MDUsMTIuNTU2MDExNjUxODEyNzgxIGg2MS45MDQ5Njk2NDYyMjUzOTYgdjYyLjc4MDA1ODI1OTA2MzkxIEgxMi4zODA5OTM5MjkyNDUwNSBWMTIuNTU2MDExNjUxODEyNzgxIHpNNzQuMjg1OTYzNTc1NDcwNTMsNDUxLjA1MDU3MjQxNTEyMDY2IEgxMi4zODA5OTM5MjkyNDUwNSB2LTYyLjc4MDA1ODI1OTA2MzkxIGg2MS45MDQ5Njk2NDYyMjUzOTYgVjQ1MS4wNTA1NzI0MTUxMjA2NiB6TTQ0NS43MTU3ODE0NTI4MjI3NCw0NTEuMDUwNTcyNDE1MTIwNjYgaC02Mi44NTczNTM3OTQ2Mjg4NjQgdi02Mi43ODAwNTgyNTkwNjM5MSBoNjIuODU3MzUzNzk0NjI4ODY0IFY0NTEuMDUwNTcyNDE1MTIwNjYgek00MDcuNjIwNDE1NTE2Njg0MjYsMzc2LjY4MDM0OTU1NDM4MzQ0IGgtMzYuMTkwNTk3NjM5MzMxNzcgdjMyLjgzODc5OTcwNDc0MTEyIEg4NS43MTQ1NzMzNTYzMTIwMyB2LTMyLjgzODc5OTcwNDc0MTEyIEg0OS41MjM5NzU3MTY5ODAzMiBWODYuOTI2MjM0NTEyNTUwMDIgaDM2LjE5MDU5NzYzOTMzMTc3IFY1MC4yMjQwNDY2MDcyNTExMjUgaDI4Ny42MjAwMTI4MTc4NDcyIHYzNi43MDIxODc5MDUyOTg5IGgzNC4yODU4MjkzNDI1MjQ4MzUgVjM3Ni42ODAzNDk1NTQzODM0NCB6IiBpZD0ic3ZnXzIiIGNsYXNzPSIiIGZpbGw9IiMxYTFhZWIiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L3N2Zz4=', + iconSize: [32, 32], + iconAnchor: [16, 16]} + ) + }, - if (!this.isSelected(overlay)) { return; } + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = this._handled._corners[this._corner], + newLatLng = this.getLatLng(), - overlay._dragPoints = {}; + scale = this._calculateScalingFactor(formerLatLng, newLatLng); - for (i = 0; i < 4; i++) { - overlay._dragPoints[i] = map.latLngToLayerPoint(overlay.getCorner(i)); - } + overlay.editing._scaleBy(scale); - var cpd = overlay._calcCornerPointDelta(); + overlay.fire('update'); - this._updateCollectionFromPoints(cpd, overlay); - }, + this._handled.editing._showToolbar(); + }, - _deselectAll: function(event) { - this.eachLayer(function(layer) { - var edit = layer.editing; - L.DomUtil.removeClass(layer.getElement(), "selected"); - edit._deselect(); - }); + updateHandle: function() { + this.setLatLng(this._handled._corners[this._corner]); + }, - L.DomEvent.stopPropagation(event); - }, + /* Takes two latlngs and calculates the angle between them. */ + _calculateAngle: function(latlngA, latlngB) { + var map = this._handled._map, - _removeFromGroup: function(e) { - this.eachLayer(function(layer) { - var edit = layer.editing; - if (edit._selected && edit._mode !== "lock") { - var choice = edit.confirmDelete(); - if (choice) { - edit._selected = false; - this.removeLayer(layer); - } else { - L.DomEvent.stopPropagation(e); - return; - } - } - }, this); - }, - /** - * images in 'lock' mode are included in this feature group collection for functionalities - * such as export, but are filtered out for editing / dragging here - */ - _calcCollectionFromPoints: function(cpd, overlay) { - var layersToMove = [], - p = new L.Transformation(1, -cpd.x, 1, -cpd.y); + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), - this.eachLayer(function(layer) { - if ( - layer !== overlay && - layer.editing._mode !== "lock" && - this.isSelected(layer) - ) { - layer._cpd = {}; + initialAngle = Math.atan2(centerPoint.y - formerPoint.y, centerPoint.x - formerPoint.x), + newAngle = Math.atan2(centerPoint.y - newPoint.y, centerPoint.x - newPoint.x); - layer._cpd.val0 = p.transform(layer._dragStartPoints[0]); - layer._cpd.val1 = p.transform(layer._dragStartPoints[1]); - layer._cpd.val2 = p.transform(layer._dragStartPoints[2]); - layer._cpd.val3 = p.transform(layer._dragStartPoints[3]); + return newAngle - initialAngle; + }, - layersToMove.push(layer); - } - }, this); + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var map = this._handled._map, - return layersToMove; - }, + centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), - /** - * cpd === cornerPointDelta - */ - _updateCollectionFromPoints: function(cpd, overlay) { - var layersToMove = this._calcCollectionFromPoints(cpd, overlay); + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); - layersToMove.forEach(function(layer) { - layer._updateCornersFromPoints(layer._cpd); - layer.fire("update"); - }, this); - } + return Math.sqrt(newRadiusSquared / formerRadiusSquared); + }, + + /* Distance between two points in cartesian space, squared (distance formula). */ + _d2: function(a, b) { + var dx = a.x - b.x, + dy = a.y - b.y; + + return Math.pow(dx, 2) + Math.pow(dy, 2); + } }); -L.distortableCollection = function(id, options) { - return new L.DistortableCollection(id, options); -}; L.DistortableImage = L.DistortableImage || {}; var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ @@ -1264,16 +1274,64 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Interaction modes. */ this._mode = this._overlay.options.mode || "distort"; + this._selected = this._overlay.options.selected || false; this._transparent = false; this._outlined = false; - this._selected = false; }, /* Run on image selection. */ addHooks: function() { var overlay = this._overlay, - map = overlay._map, - i; + map = overlay._map; + + this._initHandles(); + + this._initMode(); + + if (this._selected) { this._initToolbar(); } + + this._overlay._dragStartPoints = { + 0: new L.point(0, 0), + 1: new L.point(0, 0), + 2: new L.point(0, 0), + 3: new L.point(0, 0) + }; + + L.DomEvent.on(map, "click", this._deselect, this); + L.DomEvent.on(overlay._image, "click", this._select, this); + + /* Enable hotkeys. */ + L.DomEvent.on(window, "keydown", this._onKeyDown, this); + + // overlay.fire("select"); + }, + + /* Run on image deselection. */ + removeHooks: function() { + var overlay = this._overlay, + map = overlay._map; + + L.DomEvent.off(map, "click", this._deselect, this); + L.DomEvent.off(overlay._image, "click", this._select, this); + + // First, check if dragging exists - it may be off due to locking + if (this.dragging) { this.dragging.disable(); } + delete this.dragging; + + if (this.toolbar) { this._hideToolbar(); } + if (this.editing) { this.editing.disable(); } + + map.removeLayer(this._handles[this._mode]); + + /* Disable hotkeys. */ + L.DomEvent.off(window, "keydown", this._onKeyDown, this); + + overlay.fire("deselect"); + }, + + _initHandles: function() { + var overlay = this._overlay, + i; this._lockHandles = new L.LayerGroup(); for (i = 0; i < 4; i++) { @@ -1309,6 +1367,11 @@ L.DistortableImage.Edit = L.Handler.extend({ scale: this._scaleHandles, rotate: this._rotateHandles }; + }, + + _initMode: function() { + var overlay = this._overlay, + map = overlay._map; if (this._mode === 'lock') { @@ -1316,60 +1379,26 @@ L.DistortableImage.Edit = L.Handler.extend({ } else { this._mode = 'distort'; map.addLayer(this._distortHandles); - this._distortHandles.eachLayer(function (layer) { - layer.setOpacity(0); - layer.dragging.disable(); - layer.options.draggable = false; - }); + if (!this._selected) { + this._distortHandles.eachLayer(function (layer) { + layer.setOpacity(0); + layer.dragging.disable(); + layer.options.draggable = false; + }); + } this._enableDragging(); } - - this._initToolbar(); - - this._overlay._dragStartPoints = { - 0: new L.point(0, 0), - 1: new L.point(0, 0), - 2: new L.point(0, 0), - 3: new L.point(0, 0) - }; - - L.DomEvent.on(map, "click", this._deselect, this); - L.DomEvent.on(overlay._image, 'click', this._select, this); - - /* Enable hotkeys. */ - L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - - // overlay.fire("select"); }, - removeHooks: function () { - var overlay = this._overlay, - map = overlay._map; - - L.DomEvent.off(map, "click", this._deselect, this); - L.DomEvent.off(overlay._image, 'click', this._select, this); - - // First, check if dragging exists - it may be off due to locking - if (this.dragging) { this.dragging.disable(); } - delete this.dragging; - - if (this.toolbar) { this._hideToolbar(); } - if (this.editing) { this.editing.disable(); } - - map.removeLayer(this._handles[this._mode]); - - /* Disable hotkeys. */ - L.DomEvent.off(window, "keydown", this._onKeyDown, this); - - // overlay.fire("deselect"); - }, _initToolbar: function () { this._showToolbar(); - try { - this.toolbar._container.style.opacity = 0; + if (!this._selected) { + try { + this.toolbar._container.style.opacity = 0; + } + catch (e) { } } - catch (e) { } }, confirmDelete: function() { @@ -1564,7 +1593,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._showToolbar(); this._showMarkers(); - L.DomEvent.stopPropagation(event); + if (event) { L.DomEvent.stopPropagation(event); } }, _deselect: function() { @@ -1576,55 +1605,56 @@ L.DistortableImage.Edit = L.Handler.extend({ _hideToolbar: function() { var map = this._overlay._map; + if (this.toolbar) { map.removeLayer(this.toolbar); this.toolbar = false; } }, - _showMarkers: function() { - if (this._mode === 'lock') { return; } - var currentHandle = this._handles[this._mode]; - currentHandle.eachLayer(function (layer) { - layer.setOpacity(1); - layer.dragging.enable(); - layer.options.draggable = true; - }); - }, + _showMarkers: function() { + if (this._mode === "lock") { return; } - _hideMarkers: function() { - var currentHandle = this._handles[this._mode]; - currentHandle.eachLayer(function (layer) { - var drag = layer.dragging, - opts = layer.options; + var currentHandle = this._handles[this._mode]; - layer.setOpacity(0); - if (drag) { drag.disable(); } - if (opts.draggable) { opts.draggable = false; } - }); + currentHandle.eachLayer(function(layer) { + var drag = layer.dragging, + opts = layer.options; - }, + layer.setOpacity(1); + if (drag) { drag.enable(); } + if (opts.draggable) { opts.draggable = true; } + }); + }, - // TODO: toolbar for multiple image selection - _showToolbar: function() { - var overlay = this._overlay, - // target = event.target, - map = overlay._map; + _hideMarkers: function() { + if (!this._handles) { this._initHandles(); } // workaround for race condition w feature group - /* Ensure that there is only ever one toolbar attached to each image. */ - this._hideToolbar(); + var currentHandle = this._handles[this._mode]; - var point; - point = overlay._image._leaflet_pos; + currentHandle.eachLayer(function(layer) { + var drag = layer.dragging, + opts = layer.options; - //Find the topmost point on the image. - var corners = overlay.getCorners(); - var maxLat = -Infinity; - for(var i = 0; i < corners.length; i++) { - if(corners[i].lat > maxLat) { - maxLat = corners[i].lat; - } - } + layer.setOpacity(0); + if (drag) { drag.disable(); } + if (opts.draggable) { opts.draggable = false; } + }); + }, + + // TODO: toolbar for multiple image selection + _showToolbar: function() { + var overlay = this._overlay, + map = overlay._map; + + //Find the topmost point on the image. + var corners = overlay.getCorners(); + var maxLat = -Infinity; + for (var i = 0; i < corners.length; i++) { + if (corners[i].lat > maxLat) { + maxLat = corners[i].lat; + } + } //Longitude is based on the centroid of the image. var raised_point = overlay.getCenter(); @@ -1749,6 +1779,30 @@ L.DistortableImageOverlay.addInitHook(function() { }); }); +L.DomUtil = L.DomUtil || {}; +L.DistortableImage = L.DistortableImage || {}; + +L.DistortableImage.Keymapper = L.Control.extend({ + initialize: function(options) { + L.Control.prototype.initialize.call(this, options); + }, + onAdd: function() { + var el_wrapper = L.DomUtil.create("div", "l-container"); + el_wrapper.innerHTML = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; + return el_wrapper; + } +}); L.Map.mergeOptions({ boxSelector: true, boxZoom: false }); // used for multiple image select. Temporarily disabled until click diff --git a/examples/index.html b/examples/index.html index 8dda41dc8..2c9174771 100644 --- a/examples/index.html +++ b/examples/index.html @@ -84,6 +84,7 @@ // add a toolbarType field with values 'popup' (default) or 'control' // add a keymapper: false <== hides the keymapper // add a keymapper_position field with combinations of 'top', 'bottom', 'left' or 'right' + selected: true, corners: [ L.latLng(51.52, -0.1), L.latLng(51.52, -0.14), diff --git a/examples/select.html b/examples/select.html index 9e55a934f..2a0efec12 100644 --- a/examples/select.html +++ b/examples/select.html @@ -60,30 +60,30 @@ img2 = L.distortableImageOverlay( 'example.png', { corners: [ - L.latLng(51.51,-0.10), - L.latLng(51.51,-0.14), - L.latLng(51.49,-0.11), - L.latLng(51.49,-0.15) + L.latLng(51.51,-0.16), + L.latLng(51.51,-0.20), + L.latLng(51.49,-0.17), + L.latLng(51.49,-0.21) ], }).addTo(map); img3 = L.distortableImageOverlay( 'example.png', { corners: [ - L.latLng(51.51,-0.10), - L.latLng(51.51,-0.14), - L.latLng(51.49,-0.11), - L.latLng(51.49,-0.15) + L.latLng(51.50,-0.09), + L.latLng(51.50,-0.13), + L.latLng(51.48,-0.10), + L.latLng(51.48,-0.14) ], }).addTo(map); img4 = L.distortableImageOverlay( 'example.png', { corners: [ - L.latLng(51.51,-0.10), - L.latLng(51.51,-0.14), - L.latLng(51.49,-0.11), - L.latLng(51.49,-0.15) + L.latLng(51.51,-0.03), + L.latLng(51.51,-0.07), + L.latLng(51.49,-0.04), + L.latLng(51.49,-0.08) ], }).addTo(map); diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index ed964c56d..0d1528abb 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -16,11 +16,21 @@ L.DistortableCollection = L.FeatureGroup.extend({ // L.DomEvent.on(map, "boxzoomend", this._addSelections, this); + var lastSelected; + this.eachLayer(function(layer) { L.DomEvent.on(layer._image, "mousedown", this._deselectOthers, this); L.DomEvent.on(layer, "dragstart", this._dragStartMultiple, this); L.DomEvent.on(layer, "drag", this._dragMultiple, this); + + if (layer.options.selected) { + layer.editing._deselect(); + lastSelected = layer.editing; + } }, this); + + if (lastSelected) { lastSelected._select(); } + }, onRemove: function() { diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index a55631b01..8effb6deb 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -25,16 +25,64 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Interaction modes. */ this._mode = this._overlay.options.mode || "distort"; + this._selected = this._overlay.options.selected || false; this._transparent = false; this._outlined = false; - this._selected = false; }, /* Run on image selection. */ addHooks: function() { var overlay = this._overlay, - map = overlay._map, - i; + map = overlay._map; + + this._initHandles(); + + this._initMode(); + + if (this._selected) { this._initToolbar(); } + + this._overlay._dragStartPoints = { + 0: new L.point(0, 0), + 1: new L.point(0, 0), + 2: new L.point(0, 0), + 3: new L.point(0, 0) + }; + + L.DomEvent.on(map, "click", this._deselect, this); + L.DomEvent.on(overlay._image, "click", this._select, this); + + /* Enable hotkeys. */ + L.DomEvent.on(window, "keydown", this._onKeyDown, this); + + // overlay.fire("select"); + }, + + /* Run on image deselection. */ + removeHooks: function() { + var overlay = this._overlay, + map = overlay._map; + + L.DomEvent.off(map, "click", this._deselect, this); + L.DomEvent.off(overlay._image, "click", this._select, this); + + // First, check if dragging exists - it may be off due to locking + if (this.dragging) { this.dragging.disable(); } + delete this.dragging; + + if (this.toolbar) { this._hideToolbar(); } + if (this.editing) { this.editing.disable(); } + + map.removeLayer(this._handles[this._mode]); + + /* Disable hotkeys. */ + L.DomEvent.off(window, "keydown", this._onKeyDown, this); + + overlay.fire("deselect"); + }, + + _initHandles: function() { + var overlay = this._overlay, + i; this._lockHandles = new L.LayerGroup(); for (i = 0; i < 4; i++) { @@ -70,6 +118,11 @@ L.DistortableImage.Edit = L.Handler.extend({ scale: this._scaleHandles, rotate: this._rotateHandles }; + }, + + _initMode: function() { + var overlay = this._overlay, + map = overlay._map; if (this._mode === 'lock') { @@ -77,60 +130,26 @@ L.DistortableImage.Edit = L.Handler.extend({ } else { this._mode = 'distort'; map.addLayer(this._distortHandles); - this._distortHandles.eachLayer(function (layer) { - layer.setOpacity(0); - layer.dragging.disable(); - layer.options.draggable = false; - }); + if (!this._selected) { + this._distortHandles.eachLayer(function (layer) { + layer.setOpacity(0); + layer.dragging.disable(); + layer.options.draggable = false; + }); + } this._enableDragging(); } - - this._initToolbar(); - - this._overlay._dragStartPoints = { - 0: new L.point(0, 0), - 1: new L.point(0, 0), - 2: new L.point(0, 0), - 3: new L.point(0, 0) - }; - - L.DomEvent.on(map, "click", this._deselect, this); - L.DomEvent.on(overlay._image, 'click', this._select, this); - - /* Enable hotkeys. */ - L.DomEvent.on(window, 'keydown', this._onKeyDown, this); - - // overlay.fire("select"); }, - removeHooks: function () { - var overlay = this._overlay, - map = overlay._map; - - L.DomEvent.off(map, "click", this._deselect, this); - L.DomEvent.off(overlay._image, 'click', this._select, this); - - // First, check if dragging exists - it may be off due to locking - if (this.dragging) { this.dragging.disable(); } - delete this.dragging; - - if (this.toolbar) { this._hideToolbar(); } - if (this.editing) { this.editing.disable(); } - - map.removeLayer(this._handles[this._mode]); - - /* Disable hotkeys. */ - L.DomEvent.off(window, "keydown", this._onKeyDown, this); - - // overlay.fire("deselect"); - }, _initToolbar: function () { this._showToolbar(); - try { - this.toolbar._container.style.opacity = 0; + if (!this._selected) { + try { + this.toolbar._container.style.opacity = 0; + } + catch (e) { } } - catch (e) { } }, confirmDelete: function() { @@ -325,7 +344,7 @@ L.DistortableImage.Edit = L.Handler.extend({ this._showToolbar(); this._showMarkers(); - L.DomEvent.stopPropagation(event); + if (event) { L.DomEvent.stopPropagation(event); } }, _deselect: function() { @@ -337,55 +356,56 @@ L.DistortableImage.Edit = L.Handler.extend({ _hideToolbar: function() { var map = this._overlay._map; + if (this.toolbar) { map.removeLayer(this.toolbar); this.toolbar = false; } }, - _showMarkers: function() { - if (this._mode === 'lock') { return; } - var currentHandle = this._handles[this._mode]; - currentHandle.eachLayer(function (layer) { - layer.setOpacity(1); - layer.dragging.enable(); - layer.options.draggable = true; - }); - }, + _showMarkers: function() { + if (this._mode === "lock") { return; } - _hideMarkers: function() { - var currentHandle = this._handles[this._mode]; - currentHandle.eachLayer(function (layer) { - var drag = layer.dragging, - opts = layer.options; + var currentHandle = this._handles[this._mode]; - layer.setOpacity(0); - if (drag) { drag.disable(); } - if (opts.draggable) { opts.draggable = false; } - }); + currentHandle.eachLayer(function(layer) { + var drag = layer.dragging, + opts = layer.options; - }, + layer.setOpacity(1); + if (drag) { drag.enable(); } + if (opts.draggable) { opts.draggable = true; } + }); + }, - // TODO: toolbar for multiple image selection - _showToolbar: function() { - var overlay = this._overlay, - // target = event.target, - map = overlay._map; + _hideMarkers: function() { + if (!this._handles) { this._initHandles(); } // workaround for race condition w feature group - /* Ensure that there is only ever one toolbar attached to each image. */ - this._hideToolbar(); + var currentHandle = this._handles[this._mode]; - var point; - point = overlay._image._leaflet_pos; + currentHandle.eachLayer(function(layer) { + var drag = layer.dragging, + opts = layer.options; - //Find the topmost point on the image. - var corners = overlay.getCorners(); - var maxLat = -Infinity; - for(var i = 0; i < corners.length; i++) { - if(corners[i].lat > maxLat) { - maxLat = corners[i].lat; - } - } + layer.setOpacity(0); + if (drag) { drag.disable(); } + if (opts.draggable) { opts.draggable = false; } + }); + }, + + // TODO: toolbar for multiple image selection + _showToolbar: function() { + var overlay = this._overlay, + map = overlay._map; + + //Find the topmost point on the image. + var corners = overlay.getCorners(); + var maxLat = -Infinity; + for (var i = 0; i < corners.length; i++) { + if (corners[i].lat > maxLat) { + maxLat = corners[i].lat; + } + } //Longitude is based on the centroid of the image. var raised_point = overlay.getCenter(); From f15fce06e63ba7a82a4e1f579f722ff0985de490 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 4 May 2019 20:58:46 -0400 Subject: [PATCH 13/18] Add testing for lock handles --- dist/leaflet.distortableimage.js | 2 +- src/edit/DistortableImage.Edit.js | 2 +- test/src/DistortableCollectionSpec.js | 27 +++++++++++++++++++++++ test/src/edit/DistortableImageEditSpec.js | 24 ++++++++++---------- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index cde2f0c94..e648bb1fc 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1628,7 +1628,7 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _hideMarkers: function() { - if (!this._handles) { this._initHandles(); } // workaround for race condition w feature group + if (!this._handles) { this._initHandles(); } // workaround for race condition w/ feature group var currentHandle = this._handles[this._mode]; diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 8effb6deb..49eef116e 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -379,7 +379,7 @@ L.DistortableImage.Edit = L.Handler.extend({ }, _hideMarkers: function() { - if (!this._handles) { this._initHandles(); } // workaround for race condition w feature group + if (!this._handles) { this._initHandles(); } // workaround for race condition w/ feature group var currentHandle = this._handles[this._mode]; diff --git a/test/src/DistortableCollectionSpec.js b/test/src/DistortableCollectionSpec.js index cdf002919..e1e7b3c00 100644 --- a/test/src/DistortableCollectionSpec.js +++ b/test/src/DistortableCollectionSpec.js @@ -59,6 +59,33 @@ describe("L.DistortableCollection", function () { }); }); + describe("#_deselectAll", function () { + it("Should hide all images' handles unless they're lock handles", function() { + var edit1 = overlay.editing, + edit2 = overlay2.editing; + + // turn on lock handles for one of the DistortableImages + edit2._toggleLock(); + + // then trigger _deselectAll + map.fire('click'); + + var distortHandleState = []; + edit1._handles["distort"].eachLayer(function (handle) { + distortHandleState.push(handle._icon.style.opacity) + }); + + var lockHandleState = []; + edit2._handles["lock"].eachLayer(function (handle) { + lockHandleState.push(handle._icon.style.opacity) + }); + + expect(distortHandleState).to.deep.equal(["0", "0", "0", "0"]); + // opacity for lockHandles is unset because we never altered it to hide it as part of deselection + expect(lockHandleState).to.deep.equal(["", "", "", ""]); + }); + }); + describe("#_toggleMultiSelect", function () { it("Should allow selection of multiple images on command + click", function() { chai.simulateCommandMousedown(overlay.getElement()); diff --git a/test/src/edit/DistortableImageEditSpec.js b/test/src/edit/DistortableImageEditSpec.js index 4ac2bce0d..8ea9d3dd2 100644 --- a/test/src/edit/DistortableImageEditSpec.js +++ b/test/src/edit/DistortableImageEditSpec.js @@ -23,35 +23,35 @@ describe("L.DistortableImage.Edit", function() { }); it("Should keep handles on the map in sync with the corners of the image.", function() { - var corners = overlay._corners; - - overlay.editing.enable(); - - overlay.editing._selected = true; + var corners = overlay.getCorners(), + edit = overlay.editing; + edit.enable(); + edit._selected = true; overlay._updateCorner(0, new L.LatLng(41.7934, -87.6252)); + overlay.fire('update'); /* Warp handles are currently on the map; they should have been updated. */ - overlay.editing._distortHandles.eachLayer(function(handle) { + edit._distortHandles.eachLayer(function(handle) { expect(handle.getLatLng()).to.be.closeToLatLng(corners[handle._corner]); }); - overlay.editing._toggleRotateScale(); + edit._toggleRotateScale(); - /* After we toggle modes, the rotateHandles are on the map and should be synced. */ - overlay.editing._rotateScaleHandles.eachLayer(function(handle) { + /* After we toggle modes, the rotateScaleHandles are on the map and should be synced. */ + edit._rotateScaleHandles.eachLayer(function(handle) { expect(handle.getLatLng()).to.be.closeToLatLng(corners[handle._corner]); }); }); it.skip("Should keep image in sync with the map while dragging.", function() { - var corners = overlay._corners, + var edit = overlay.editing, dragging; - overlay.editing.enable(); + edit.enable(); - dragging = overlay.editing.dragging; + dragging = edit.dragging; /* _reset is not called by #onAdd, for some reason... */ overlay._reset(); From 4b9b84b178d680b5b9184dd9dc1b3225844d9878 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sat, 4 May 2019 22:00:19 -0400 Subject: [PATCH 14/18] Complete testing for lock mode --- test/SpecHelper.js | 18 ++-- test/src/DistortableCollectionSpec.js | 67 +++++++++----- test/src/edit/DistortableImageEditSpec.js | 101 +++++++++++++++++++++- 3 files changed, 158 insertions(+), 28 deletions(-) diff --git a/test/SpecHelper.js b/test/SpecHelper.js index 5de56c93d..23cb8d2da 100644 --- a/test/SpecHelper.js +++ b/test/SpecHelper.js @@ -12,11 +12,19 @@ beforeEach(function() { * 2) the booleans after the list of 0s simulate the presence (or lack of) the following keys (in order) during the mouse event: 'ctrlKey', 'altKey', 'shiftKey', 'metaKey' */ chai.simulateCommandMousedown = function simulateCommandMousedownFn(el) { - if (document.createEvent) { - var e = document.createEvent('MouseEvents'); - e.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, true, false, false, true, 0, null); - return el.dispatchEvent(e); - } + if (document.createEvent) { + var e = document.createEvent('MouseEvents'); + e.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, true, false, false, true, 0, null); + return el.dispatchEvent(e); + } + }; + + chai.simulateClick = function simulateClickFn(el) { + if (document.createEvent) { + var e = document.createEvent('MouseEvents'); + e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + return el.dispatchEvent(e); + } }; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ diff --git a/test/src/DistortableCollectionSpec.js b/test/src/DistortableCollectionSpec.js index e1e7b3c00..3f2966906 100644 --- a/test/src/DistortableCollectionSpec.js +++ b/test/src/DistortableCollectionSpec.js @@ -45,23 +45,24 @@ describe("L.DistortableCollection", function () { }); describe("#_deselectAll", function () { - it("Should deselect all images on map click", function() { - L.DomUtil.addClass(overlay.getElement(), "selected"); - L.DomUtil.addClass(overlay2.getElement(), "selected"); + it("Should remove the 'selected' class from all images", function() { + var img = overlay.getElement(), + img2 = overlay2.getElement(); - map.fire('click'); + L.DomUtil.addClass(img, "selected"); + L.DomUtil.addClass(img2, "selected"); - var classStr = L.DomUtil.getClass(overlay.getElement()); - var classStr2 = L.DomUtil.getClass(overlay2.getElement()); + map.fire('click'); + var classStr = L.DomUtil.getClass(img); expect(classStr).to.not.include("selected"); + + var classStr2 = L.DomUtil.getClass(img2); expect(classStr2).to.not.include("selected"); }); - }); - describe("#_deselectAll", function () { it("Should hide all images' handles unless they're lock handles", function() { - var edit1 = overlay.editing, + var edit = overlay.editing, edit2 = overlay2.editing; // turn on lock handles for one of the DistortableImages @@ -71,7 +72,7 @@ describe("L.DistortableCollection", function () { map.fire('click'); var distortHandleState = []; - edit1._handles["distort"].eachLayer(function (handle) { + edit._handles["distort"].eachLayer(function (handle) { distortHandleState.push(handle._icon.style.opacity) }); @@ -84,27 +85,53 @@ describe("L.DistortableCollection", function () { // opacity for lockHandles is unset because we never altered it to hide it as part of deselection expect(lockHandleState).to.deep.equal(["", "", "", ""]); }); + + it("Should remove all images' individual toolbar instances regardless of lock handles", function() { + var edit = overlay.editing, + edit2 = overlay2.editing, + img = overlay.getElement(), + img2 = overlay2.getElement(); + + // turn on lock handles for one of the DistortableImages + edit2._toggleLock(); + + // select both images to initially create individual toolbar instances + chai.simulateClick(img); + chai.simulateClick(img2); + + expect(edit.toolbar).to.not.be.false + expect(edit2.toolbar).to.not.be.false + + // then trigger _deselectAll + map.fire('click'); + + expect(edit.toolbar).to.be.false + expect(edit2.toolbar).to.be.false + }); }); describe("#_toggleMultiSelect", function () { - it("Should allow selection of multiple images on command + click", function() { - chai.simulateCommandMousedown(overlay.getElement()); - chai.simulateCommandMousedown(overlay2.getElement()); + it("Should allow multiple image selection on command + click", function() { + var img = overlay.getElement(), + img2 = overlay2.getElement(); - var classStr = L.DomUtil.getClass(overlay.getElement()); - var classStr2 = L.DomUtil.getClass(overlay2.getElement()); + chai.simulateCommandMousedown(img); + chai.simulateCommandMousedown(img2); + var classStr = L.DomUtil.getClass(img); expect(classStr).to.include("selected"); + + var classStr2 = L.DomUtil.getClass(img2); expect(classStr2).to.include("selected"); }); - it("But it should not allow selection of a locked image", function() { - L.DomUtil.removeClass(overlay.getElement(), "selected"); - overlay.editing._mode = "lock"; + it("But it should not allow a locked image to be part of multiple image selection", function() { + var img = overlay.getElement(); - chai.simulateCommandMousedown(overlay.getElement()); - var classStr = L.DomUtil.getClass(overlay.getElement()); + overlay.editing._toggleLock(); + chai.simulateCommandMousedown(img); + var classStr = L.DomUtil.getClass(img); expect(classStr).to.not.include("selected"); }); }); diff --git a/test/src/edit/DistortableImageEditSpec.js b/test/src/edit/DistortableImageEditSpec.js index 8ea9d3dd2..0e380a884 100644 --- a/test/src/edit/DistortableImageEditSpec.js +++ b/test/src/edit/DistortableImageEditSpec.js @@ -16,6 +16,11 @@ describe("L.DistortableImage.Edit", function() { /* Forces the image to load before any tests are run. */ L.DomEvent.on(overlay._image, 'load', function() { done (); }); + + afterEach(function () { + L.DomUtil.remove(overlay); + }); + }); it("Should be initialized along with each instance of L.DistortableImageOverlay.", function() { @@ -24,12 +29,14 @@ describe("L.DistortableImage.Edit", function() { it("Should keep handles on the map in sync with the corners of the image.", function() { var corners = overlay.getCorners(), - edit = overlay.editing; + edit = overlay.editing, + img = overlay.getElement(); edit.enable(); - edit._selected = true; - overlay._updateCorner(0, new L.LatLng(41.7934, -87.6252)); + // this test applies to a selected image + chai.simulateClick(img); + overlay._updateCorner(0, new L.LatLng(41.7934, -87.6252)); overlay.fire('update'); /* Warp handles are currently on the map; they should have been updated. */ @@ -63,4 +70,92 @@ describe("L.DistortableImage.Edit", function() { map.setView([41.7896,-87.6996]); }); + + describe("#_select", function () { + it("It should initialize an image's individual toolbar instance", function () { + var edit = overlay.editing, + img = overlay.getElement(); + + edit.enable(); + + expect(edit.toolbar).to.not.exist + + // triggers _select + chai.simulateClick(img); + + expect(edit.toolbar).to.exist + }); + + it("It should show an image's handles by updating their opacity", function () { + var edit = overlay.editing, + img = overlay.getElement(); + + edit.enable(); + chai.simulateClick(img); + + var handleState = []; + edit._handles["distort"].eachLayer(function (handle) { + handleState.push(handle._icon.style.opacity) + }); + + // opacity for lockHandles is unset because we never altered it to hide it as part of deselection + expect(handleState).to.deep.equal(["1", "1", "1", "1"]); + }); + }); + + describe("#_deselect", function () { + it("It should hide an unlocked image's handles by updating their opacity", function () { + var edit = overlay.editing; + + edit.enable(); + // then trigger _deselect + map.fire('click'); + + var handleState = []; + edit._handles["distort"].eachLayer(function (handle) { + handleState.push(handle._icon.style.opacity) + }); + + // opacity for lockHandles is unset because we never altered it to hide it as part of deselection + expect(handleState).to.deep.equal(["0", "0", "0", "0"]); + }); + + it("But it should not hide a locked image's handles", function () { + var edit = overlay.editing; + + edit.enable(); + // switch to lock handles + edit._toggleLock(); + // then trigger _deselect + map.fire('click'); + + var lockHandleState = []; + edit._handles["lock"].eachLayer(function (handle) { + lockHandleState.push(handle._icon.style.opacity) + }); + + // opacity for lockHandles is unset because we never altered it to hide it as part of deselection + expect(lockHandleState).to.deep.equal(["", "", "", ""]); + }); + + it("Should remove an image's individual toolbar instance regardless of lock handles", function () { + var edit = overlay.editing, + img = overlay.getElement(); + + edit.enable(); + // switch to lock handles + edit._toggleLock(); + // select the image to initially create its individual toolbar instance + chai.simulateClick(img); + + expect(edit.toolbar).to.not.be.false + + // then trigger _deselect + map.fire('click'); + + expect(edit.toolbar).to.be.false + }); + }); + + }); From a6df69c16ece6ba0f980afd3c0a0dc8aaf3a8ebb Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sun, 5 May 2019 00:05:16 -0400 Subject: [PATCH 15/18] Fix rebase --- Gruntfile.js | 5 +- dist/leaflet.distortableimage.js | 92 ++++++++++++------------ src/DistortableImageOverlay.js | 1 + src/edit/DistortableImage.Edit.js | 27 +++---- src/edit/DistortableImage.EditToolbar.js | 18 ++--- 5 files changed, 76 insertions(+), 67 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b94118096..ce5e68a92 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -79,6 +79,8 @@ module.exports = function(grunt) { dist: { src: [ 'src/util/*.js', + 'src/DistortableImageOverlay.js', + 'src/DistortableCollection.js', 'src/edit/getEXIFdata.js', 'src/edit/EditHandle.js', 'src/edit/LockHandle.js', @@ -86,10 +88,9 @@ module.exports = function(grunt) { 'src/edit/RotateScaleHandle.js', 'src/edit/RotateHandle.js', 'src/edit/ScaleHandle.js', - 'src/DistortableImageOverlay.js', - 'src/DistortableCollection.js', 'src/edit/DistortableImage.EditToolbar.js', 'src/edit/DistortableImage.Edit.js', + 'src/edit/tools/DistortableImage.Keymapper.js', 'src/edit/BoxSelectHandle.js' ], dest: 'dist/leaflet.distortableimage.js', diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index e648bb1fc..c5e5f35ee 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -155,6 +155,7 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ this.edgeMinWidth = this.options.edgeMinWidth; this._url = url; this._rotation = this.options.rotation; + L.DistortableImage._options = options; L.Util.setOptions(this, options); }, @@ -1219,25 +1220,25 @@ L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ ToggleEditable, ToggleRotateScale, ToggleExport, - EnableEXIF, - ToggleOrder - ] + EnableEXIF, + ToggleOrder + ] }, - + // todo: move to some sort of util class, these methods could be useful in future - _rotateToolbarAngleDeg: function(angle) { + _rotateToolbarAngleDeg: function (angle) { var div = this._container, divStyle = div.style; var oldTransform = divStyle.transform; - + divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; - divStyle.transformOrigin = "1080% 650%"; + divStyle.transformOrigin = "1080% 650%"; this._rotateToolbarIcons(angle); }, - - _rotateToolbarIcons: function(angle) { + + _rotateToolbarIcons: function (angle) { var icons = document.querySelectorAll(".fa"); for (var i = 0; i < icons.length; i++) { @@ -1277,12 +1278,26 @@ L.DistortableImage.Edit = L.Handler.extend({ this._selected = this._overlay.options.selected || false; this._transparent = false; this._outlined = false; + + /* generate instance counts */ + this.instance_count = L.DistortableImage.Edit.prototype.instances = + L.DistortableImage.Edit.prototype.instances ? L.DistortableImage.Edit.prototype.instances + 1 : 1; }, /* Run on image selection. */ addHooks: function() { var overlay = this._overlay, - map = overlay._map; + map = overlay._map, + keymapper_position; + + /* instantiate and render keymapper for one instance only*/ + if (this.instance_count === 1 && overlay.options.keymapper !== false) { + keymapper_position = overlay.options.keymapper_position || 'topright'; + map.addControl(new L.DistortableImage.Keymapper({ position: keymapper_position })); + } + + /* bring the selected image into view */ + overlay.bringToFront(); this._initHandles(); @@ -1302,8 +1317,6 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Enable hotkeys. */ L.DomEvent.on(window, "keydown", this._onKeyDown, this); - - // overlay.fire("select"); }, /* Run on image deselection. */ @@ -1325,8 +1338,6 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Disable hotkeys. */ L.DomEvent.off(window, "keydown", this._onKeyDown, this); - - overlay.fire("deselect"); }, _initHandles: function() { @@ -1373,7 +1384,6 @@ L.DistortableImage.Edit = L.Handler.extend({ var overlay = this._overlay, map = overlay._map; - if (this._mode === 'lock') { map.addLayer(this._lockHandles); } else { @@ -1393,12 +1403,6 @@ L.DistortableImage.Edit = L.Handler.extend({ _initToolbar: function () { this._showToolbar(); - if (!this._selected) { - try { - this.toolbar._container.style.opacity = 0; - } - catch (e) { } - } }, confirmDelete: function() { @@ -1779,29 +1783,29 @@ L.DistortableImageOverlay.addInitHook(function() { }); }); -L.DomUtil = L.DomUtil || {}; -L.DistortableImage = L.DistortableImage || {}; - -L.DistortableImage.Keymapper = L.Control.extend({ - initialize: function(options) { - L.Control.prototype.initialize.call(this, options); - }, - onAdd: function() { - var el_wrapper = L.DomUtil.create("div", "l-container"); - el_wrapper.innerHTML = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; - return el_wrapper; - } +L.DomUtil = L.DomUtil || {}; +L.DistortableImage = L.DistortableImage || {}; + +L.DistortableImage.Keymapper = L.Control.extend({ + initialize: function(options) { + L.Control.prototype.initialize.call(this, options); + }, + onAdd: function() { + var el_wrapper = L.DomUtil.create("div", "l-container"); + el_wrapper.innerHTML = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; + return el_wrapper; + } }); L.Map.mergeOptions({ boxSelector: true, boxZoom: false }); diff --git a/src/DistortableImageOverlay.js b/src/DistortableImageOverlay.js index 06b0bfd92..48ed17d16 100644 --- a/src/DistortableImageOverlay.js +++ b/src/DistortableImageOverlay.js @@ -14,6 +14,7 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ this.edgeMinWidth = this.options.edgeMinWidth; this._url = url; this._rotation = this.options.rotation; + L.DistortableImage._options = options; L.Util.setOptions(this, options); }, diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index 49eef116e..3d27688ef 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -28,12 +28,26 @@ L.DistortableImage.Edit = L.Handler.extend({ this._selected = this._overlay.options.selected || false; this._transparent = false; this._outlined = false; + + /* generate instance counts */ + this.instance_count = L.DistortableImage.Edit.prototype.instances = + L.DistortableImage.Edit.prototype.instances ? L.DistortableImage.Edit.prototype.instances + 1 : 1; }, /* Run on image selection. */ addHooks: function() { var overlay = this._overlay, - map = overlay._map; + map = overlay._map, + keymapper_position; + + /* instantiate and render keymapper for one instance only*/ + if (this.instance_count === 1 && overlay.options.keymapper !== false) { + keymapper_position = overlay.options.keymapper_position || 'topright'; + map.addControl(new L.DistortableImage.Keymapper({ position: keymapper_position })); + } + + /* bring the selected image into view */ + overlay.bringToFront(); this._initHandles(); @@ -53,8 +67,6 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Enable hotkeys. */ L.DomEvent.on(window, "keydown", this._onKeyDown, this); - - // overlay.fire("select"); }, /* Run on image deselection. */ @@ -76,8 +88,6 @@ L.DistortableImage.Edit = L.Handler.extend({ /* Disable hotkeys. */ L.DomEvent.off(window, "keydown", this._onKeyDown, this); - - overlay.fire("deselect"); }, _initHandles: function() { @@ -124,7 +134,6 @@ L.DistortableImage.Edit = L.Handler.extend({ var overlay = this._overlay, map = overlay._map; - if (this._mode === 'lock') { map.addLayer(this._lockHandles); } else { @@ -144,12 +153,6 @@ L.DistortableImage.Edit = L.Handler.extend({ _initToolbar: function () { this._showToolbar(); - if (!this._selected) { - try { - this.toolbar._container.style.opacity = 0; - } - catch (e) { } - } }, confirmDelete: function() { diff --git a/src/edit/DistortableImage.EditToolbar.js b/src/edit/DistortableImage.EditToolbar.js index 2cb2af2da..f2084bc8a 100644 --- a/src/edit/DistortableImage.EditToolbar.js +++ b/src/edit/DistortableImage.EditToolbar.js @@ -154,25 +154,25 @@ L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ ToggleEditable, ToggleRotateScale, ToggleExport, - EnableEXIF, - ToggleOrder - ] + EnableEXIF, + ToggleOrder + ] }, - + // todo: move to some sort of util class, these methods could be useful in future - _rotateToolbarAngleDeg: function(angle) { + _rotateToolbarAngleDeg: function (angle) { var div = this._container, divStyle = div.style; var oldTransform = divStyle.transform; - + divStyle.transform = oldTransform + "rotate(" + angle + "deg)"; - divStyle.transformOrigin = "1080% 650%"; + divStyle.transformOrigin = "1080% 650%"; this._rotateToolbarIcons(angle); }, - - _rotateToolbarIcons: function(angle) { + + _rotateToolbarIcons: function (angle) { var icons = document.querySelectorAll(".fa"); for (var i = 0; i < icons.length; i++) { From bbb18a1e2c9d01f74072f70197f7e059f01786bb Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sun, 5 May 2019 01:11:10 -0400 Subject: [PATCH 16/18] Improve documentation --- README.md | 33 +++++++++++++++++++++++++++++++-- examples/index.html | 1 - 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4198f13a0..cab4ec163 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ L.tileLayer('https://{s}.tiles.mapbox.com/v3/anishshah101.ipm9j6em/{z}/{x}/{y}.p // create an image img = L.distortableImageOverlay( 'example.png', { + // 'corners' is the only required option for this class corners: [ L.latLng(51.52,-0.10), L.latLng(51.52,-0.14), @@ -70,6 +71,27 @@ img = L.distortableImageOverlay( L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); ``` +Options available to pass during `L.DistortableImageOverlay` initialization: +- corners + +- [selected](#selection) + +- mode + +- [fullResolutionSrc](#Full-resolution%20download) + +- [keymapper](#keymapper) + +- [suppressToolbar](#suppressToolbar) + + +## Selected +(*optional*, default: false, value: *boolean*) + +By default, your image will initially appear on the screen as "unselected", meaning its toolbar and editing handles will not be visible. Interacting with the image, such as by clicking it, will make these components visible. + +Some developers prefer that an image initially appears as "selected" instead of "unselected". In this case, we provide an option to pass `selected: true`. + ## Keymapper (_optional_, default: true, value: _boolean_) @@ -86,8 +108,6 @@ We've added a GPU-accelerated means to generate a full resolution version of the When instantiating a Distortable Image, pass in a `fullResolutionSrc` option set to the url of the higher resolution image. This image will be used in full-res exporting. - - ```js // create basic map setup from above @@ -110,6 +130,15 @@ L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); ``` +## suppressToolbar +(*optional*, default: false, value: *boolean*) + +To initialize an image without its toolbar, pass it `suppressToolbar: true`. + +Typically, editing actions are triggered through our toolbar interface or our predefined keybindings. If disabling the toolbar, the developer will need to implement their own toolbar UI or just use the keybindings. + +This option will override other options related to the toolbar, such as [`selected: true`](#Selected) + ## Multiple Images To test the multi-image interface, open `select.html`. Currently it supports multiple image selection and translations; image distortions still use the single-image interface. diff --git a/examples/index.html b/examples/index.html index 2c9174771..063559f9a 100644 --- a/examples/index.html +++ b/examples/index.html @@ -80,7 +80,6 @@ // create an image img = L.distortableImageOverlay("example.png", { - // add a suppressToolbar: 'true' here to disable toolbars // add a toolbarType field with values 'popup' (default) or 'control' // add a keymapper: false <== hides the keymapper // add a keymapper_position field with combinations of 'top', 'bottom', 'left' or 'right' From b447319ff92fd763e0865b344f56f6c912f10ef4 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Sun, 5 May 2019 02:41:06 -0400 Subject: [PATCH 17/18] fix typo --- test/src/edit/DistortableImageEditSpec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/src/edit/DistortableImageEditSpec.js b/test/src/edit/DistortableImageEditSpec.js index 0e380a884..c2d5dffb7 100644 --- a/test/src/edit/DistortableImageEditSpec.js +++ b/test/src/edit/DistortableImageEditSpec.js @@ -86,7 +86,7 @@ describe("L.DistortableImage.Edit", function() { expect(edit.toolbar).to.exist }); - it("It should show an image's handles by updating their opacity", function () { + it("It should show an unlocked image's handles by updating their opacity", function () { var edit = overlay.editing, img = overlay.getElement(); @@ -98,7 +98,6 @@ describe("L.DistortableImage.Edit", function() { handleState.push(handle._icon.style.opacity) }); - // opacity for lockHandles is unset because we never altered it to hide it as part of deselection expect(handleState).to.deep.equal(["1", "1", "1", "1"]); }); }); @@ -116,7 +115,6 @@ describe("L.DistortableImage.Edit", function() { handleState.push(handle._icon.style.opacity) }); - // opacity for lockHandles is unset because we never altered it to hide it as part of deselection expect(handleState).to.deep.equal(["0", "0", "0", "0"]); }); From 2041b2bed7ca4dc7bc15d1251345ebc168b0e573 Mon Sep 17 00:00:00 2001 From: sashadev-sky Date: Tue, 7 May 2019 16:20:17 -0400 Subject: [PATCH 18/18] rebase --- dist/leaflet.distortableimage.js | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index c5e5f35ee..fa17a5fc1 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1783,29 +1783,29 @@ L.DistortableImageOverlay.addInitHook(function() { }); }); -L.DomUtil = L.DomUtil || {}; -L.DistortableImage = L.DistortableImage || {}; - -L.DistortableImage.Keymapper = L.Control.extend({ - initialize: function(options) { - L.Control.prototype.initialize.call(this, options); - }, - onAdd: function() { - var el_wrapper = L.DomUtil.create("div", "l-container"); - el_wrapper.innerHTML = "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; - return el_wrapper; - } +L.DomUtil = L.DomUtil || {}; +L.DistortableImage = L.DistortableImage || {}; + +L.DistortableImage.Keymapper = L.Control.extend({ + initialize: function(options) { + L.Control.prototype.initialize.call(this, options); + }, + onAdd: function() { + var el_wrapper = L.DomUtil.create("div", "l-container"); + el_wrapper.innerHTML = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
Keymappings
j, k: Send up / down (stacking order)
l: Lock
o: Outline
s: Scale
t: Transparency
d , r: RotateScale
esc: Deselect All
delete , backspace: Delete
caps: Rotate
"; + return el_wrapper; + } }); L.Map.mergeOptions({ boxSelector: true, boxZoom: false });