leaflet-distortableimage
Advanced tools
Comparing version 0.3.0 to 0.4.0
@@ -33,3 +33,9 @@ L.DomUtil = L.extend(L.DomUtil, { | ||
return open + rotateString + ')'; | ||
}, | ||
toggleClass: function(el, className) { | ||
var c = className; | ||
return this.hasClass(el, c) ? this.removeClass(el, c) : this.addClass(el, c); | ||
} | ||
}); | ||
@@ -130,3 +136,90 @@ | ||
}; | ||
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: | ||
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"); | ||
} | ||
console.log("Orientation:", GPS.Orientation); | ||
/* 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; | ||
} | ||
/* 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."); | ||
} | ||
}; | ||
L.EditHandle = L.Marker.extend({ | ||
initialize: function(overlay, corner, options) { | ||
@@ -218,2 +311,3 @@ var markerOptions, | ||
this.setLatLng(this._handled._corners[this._corner]); | ||
L.DomUtil.removeClass(this._handled.getElement(), 'selected'); | ||
} | ||
@@ -262,4 +356,5 @@ | ||
overlay.editing._hideToolbar(); | ||
overlay.editing._rotateBy(angle); | ||
overlay.editing._scaleBy(scale); | ||
@@ -277,7 +372,6 @@ /* | ||
} | ||
} else { | ||
overlay.editing._scaleBy(scale); | ||
} | ||
} | ||
overlay.fire('update'); | ||
}, | ||
@@ -335,3 +429,3 @@ | ||
}, | ||
_onHandleDrag: function() { | ||
@@ -341,5 +435,6 @@ var overlay = this._handled, | ||
newLatLng = this.getLatLng(), | ||
angle = this._calculateAngle(formerLatLng, newLatLng); | ||
overlay.editing._hideToolbar(); | ||
overlay.editing._rotateBy(angle); | ||
@@ -460,10 +555,12 @@ | ||
height: 200, | ||
crossOrigin: true | ||
crossOrigin: true, | ||
edgeMinWidth: 500, | ||
}, | ||
initialize: function(url, options) { | ||
this._url = url; | ||
this._rotation = this.options.rotation; | ||
this._toolArray = L.DistortableImage.EditToolbarDefaults; | ||
this._url = url; | ||
this._rotation = this.options.rotation; | ||
L.setOptions(this, options); | ||
L.setOptions(this, options); | ||
}, | ||
@@ -484,4 +581,4 @@ | ||
/* Use provided corners if available */ | ||
if (this.options.corners) { | ||
this._corners = this.options.corners; | ||
if (this.options.corners) { | ||
this._corners = this.options.corners; | ||
if (map.options.zoomAnimation && L.Browser.any3d) { | ||
@@ -491,4 +588,4 @@ map.on('zoomanim', this._animateZoom, this); | ||
/* This reset happens before image load; it allows | ||
* us to place the image on the map earlier with | ||
/* This reset happens before image load; it allows | ||
* us to place the image on the map earlier with | ||
* "guessed" dimensions. */ | ||
@@ -498,3 +595,3 @@ this._reset(); | ||
/* Have to wait for the image to load because | ||
/* Have to wait for the image to load because | ||
* we need to access its width and height. */ | ||
@@ -505,3 +602,3 @@ L.DomEvent.on(this._image, 'load', function() { | ||
/* Initialize default corners if not already set */ | ||
if (!this._corners) { | ||
if (!this._corners) { | ||
if (map.options.zoomAnimation && L.Browser.any3d) { | ||
@@ -511,5 +608,5 @@ map.on('zoomanim', this._animateZoom, this); | ||
} | ||
}, this); | ||
}, this); | ||
this.fire('add'); | ||
this.fire('add'); | ||
}, | ||
@@ -531,2 +628,11 @@ | ||
_addTool: function(tool) { | ||
this._toolArray.push(tool); | ||
L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ | ||
options: { | ||
actions: this._toolArray | ||
} | ||
}); | ||
}, | ||
_initImageDimensions: function() { | ||
@@ -587,3 +693,24 @@ var map = this._map, | ||
// 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 */ | ||
@@ -625,3 +752,3 @@ /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.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 | ||
* Leaflet then generates a CSS3 animation between the current transform and | ||
* future transform which makes the transition appear smooth. | ||
@@ -635,12 +762,12 @@ */ | ||
}, | ||
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) { | ||
@@ -674,8 +801,13 @@ image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); | ||
// 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]); | ||
}, | ||
_calculateProjectiveTransform: function(latLngToCartesian) { | ||
/* Setting reasonable but made-up image defaults | ||
* allow us to place images on the map before | ||
/* 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, | ||
w = this._image.offsetWidth || 500, | ||
h = this._image.offsetHeight || 375, | ||
@@ -688,3 +820,3 @@ c = [], | ||
} | ||
/* | ||
@@ -707,2 +839,184 @@ * This matrix describes the action of the CSS transform on each corner of the image. | ||
L.distortableImageOverlay = function(id, options) { | ||
return new L.DistortableImageOverlay(id, options); | ||
}; | ||
L.DistortableCollection = L.FeatureGroup.extend({ | ||
include: L.Mixin.Events, | ||
onAdd: function(map) { | ||
L.FeatureGroup.prototype.onAdd.call(this, map); | ||
this._map = map; | ||
L.DomEvent.on(document, "keydown", this._onKeyDown, this); | ||
L.DomEvent.on(map, "click", this._deselectAll, this); | ||
/** | ||
* 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 | ||
*/ | ||
// L.DomEvent.on(map, "boxzoomend", this._addSelections, this); | ||
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); | ||
}, | ||
onRemove: function() { | ||
var map = this._map; | ||
L.DomEvent.off(document, "keydown", this._onKeyDown, this); | ||
L.DomEvent.off(map, "click", this._deselectAll, this); | ||
// L.DomEvent.off(map, "boxzoomend", this._addSelections, this); | ||
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); | ||
}, | ||
isSelected: function (overlay) { | ||
return L.DomUtil.hasClass(overlay.getElement(), "selected"); | ||
}, | ||
_toggleMultiSelect: function (event, edit) { | ||
if (edit._mode === 'lock') { return; } | ||
if (event.metaKey || event.ctrlKey) { | ||
L.DomUtil.toggleClass(event.target, 'selected'); | ||
} | ||
}, | ||
_deselectOthers: function(event) { | ||
this.eachLayer(function (layer) { | ||
var edit = layer.editing; | ||
if (layer._image !== event.target) { | ||
edit._hideMarkers(); | ||
edit._hideToolbar(); | ||
} else { | ||
edit._showMarkers(); | ||
this._toggleMultiSelect(event, edit); | ||
} | ||
}, this); | ||
}, | ||
_addSelections: function(e) { | ||
var box = e.boxZoomBounds, | ||
i = 0; | ||
this.eachLayer(function(layer) { | ||
var edit = layer.editing; | ||
if (edit.toolbar) { edit._hideToolbar(); } | ||
for (i = 0; i < 4; i++) { | ||
if (box.contains(layer.getCorners()[i]) && edit._mode !== "lock") { | ||
L.DomUtil.addClass(layer.getElement(), "selected"); | ||
break; | ||
} | ||
} | ||
}); | ||
}, | ||
_onKeyDown: function (e) { | ||
if (e.key === 'Escape') { | ||
this._deselectAll(); | ||
} | ||
}, | ||
_dragStartMultiple: function(event) { | ||
var overlay = event.target, | ||
i; | ||
if (!this.isSelected(overlay)) { return; } | ||
this.eachLayer(function(layer) { | ||
for (i = 0; i < 4; i++) { | ||
if (layer !== overlay) { | ||
layer.editing._hideToolbar(); | ||
} | ||
layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( | ||
layer.getCorners()[i] | ||
); | ||
} | ||
}); | ||
}, | ||
_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.getCorners()[i]); | ||
} | ||
var cpd = overlay._calcCornerPointDelta(); | ||
this._updateCollectionFromPoints(cpd, overlay); | ||
}, | ||
_deselectAll: function() { | ||
this.eachLayer(function(layer) { | ||
var edit = layer.editing; | ||
L.DomUtil.removeClass(layer.getElement(), "selected"); | ||
if (edit.toolbar) { edit._hideToolbar(); } | ||
edit._hideMarkers(); | ||
}); | ||
}, | ||
/** | ||
* 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); | ||
this.eachLayer(function(layer) { | ||
if ( | ||
layer !== overlay && | ||
layer.editing._mode !== "lock" && | ||
this.isSelected(layer) | ||
) { | ||
layer._cpd = {}; | ||
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; | ||
}, | ||
/** | ||
* 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.DistortableImage = L.DistortableImage || {}; | ||
@@ -720,6 +1034,6 @@ | ||
ToggleTransparency = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-adjust"></span>', | ||
tooltip: 'Toggle Image Transparency', | ||
title: 'Toggle Image Transparency' | ||
title: 'Toggle Image Transparency' | ||
}}, | ||
@@ -736,3 +1050,3 @@ | ||
ToggleOutline = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-square-o"></span>', | ||
@@ -752,3 +1066,3 @@ tooltip: 'Toggle Image Outline', | ||
RemoveOverlay = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-trash"></span>', | ||
@@ -772,3 +1086,3 @@ tooltip: 'Delete image', | ||
tooltip: 'Lock / Unlock editing', | ||
title: 'Lock / Unlock editing' | ||
title: 'Lock / Unlock editing' | ||
}}, | ||
@@ -792,3 +1106,3 @@ | ||
tooltip: 'Rotate', | ||
title: 'Rotate' | ||
title: 'Rotate' | ||
}; | ||
@@ -816,3 +1130,3 @@ | ||
}, | ||
addHooks: function () | ||
@@ -823,6 +1137,39 @@ { | ||
editing._toggleExport(); | ||
this.disable(); | ||
this.disable(); | ||
} | ||
}); | ||
}), | ||
ToggleOrder = EditOverlayAction.extend({ | ||
options: { | ||
toolbarIcon: { | ||
html: '<span class="fa fa-sort"></span>', | ||
tooltip: 'Change order', | ||
title: 'Toggle order' | ||
} | ||
}, | ||
addHooks: function () | ||
{ | ||
var editing = this._overlay.editing; | ||
editing._toggleOrder(); | ||
this.disable(); | ||
} | ||
}), | ||
EnableEXIF = EditOverlayAction.extend({ | ||
options: { | ||
toolbarIcon: { | ||
html: '<span class="fa fa-compass"></span>', | ||
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({ | ||
@@ -836,3 +1183,5 @@ options: { | ||
ToggleRotateDistort, | ||
ToggleExport | ||
ToggleExport, | ||
EnableEXIF, | ||
ToggleOrder | ||
] | ||
@@ -849,13 +1198,16 @@ } | ||
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 | ||
46: "_removeOverlay", // delete windows / delete + fn mac | ||
8: "_removeOverlay", // backspace windows / delete mac | ||
83: '_toggleScale', // s | ||
84: '_toggleTransparency', // t | ||
20: '_toggleRotate' // CAPS | ||
} | ||
@@ -866,2 +1218,3 @@ }, | ||
this._overlay = overlay; | ||
this._toggledImage = false; | ||
@@ -874,3 +1227,3 @@ /* Interaction modes. */ | ||
/* Run on image seletion. */ | ||
/* Run on image selection. */ | ||
addHooks: function() { | ||
@@ -881,5 +1234,2 @@ var overlay = this._overlay, | ||
/* bring the selected image into view */ | ||
overlay.bringToFront(); | ||
this._lockHandles = new L.LayerGroup(); | ||
@@ -918,3 +1268,3 @@ for (i = 0; i < 4; i++) { | ||
if (this._mode === 'lock') { | ||
if (this._mode === 'lock') { | ||
map.addLayer(this._lockHandles); | ||
@@ -924,8 +1274,20 @@ } else { | ||
map.addLayer(this._distortHandles); | ||
this._distortHandles.eachLayer(function (layer) { | ||
layer.setOpacity(0); | ||
layer.dragging.disable(); | ||
layer.options.draggable = false; | ||
}); | ||
this._enableDragging(); | ||
} | ||
//overlay.on('click', this._showToolbar, this); | ||
L.DomEvent.on(overlay._image, 'click', this._showToolbar, this); | ||
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. */ | ||
@@ -935,6 +1297,5 @@ L.DomEvent.on(window, 'keydown', this._onKeyDown, this); | ||
overlay.fire('select'); | ||
}, | ||
/* Run on image deseletion. */ | ||
/* Run on image deselection. */ | ||
removeHooks: function() { | ||
@@ -944,11 +1305,12 @@ var overlay = this._overlay, | ||
// L.DomEvent.off(window, 'keydown', this._onKeyDown, this); | ||
L.DomEvent.off(map, "click", this._deselect, this); | ||
L.DomEvent.off(overlay._image, 'click', this._select, this); | ||
L.DomEvent.off(overlay._image, 'click', this._showToolbar, this); | ||
// First, check if dragging exists; | ||
// it may be off due to locking | ||
// 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]); | ||
@@ -964,5 +1326,4 @@ | ||
return window.confirm("Are you sure you want to delete?"); | ||
}, | ||
}, | ||
_rotateBy: function(angle) { | ||
@@ -1010,4 +1371,7 @@ var overlay = this._overlay, | ||
/* Hide toolbars while dragging; click will re-show it */ | ||
this.dragging.on('dragstart', this._hideToolbar, this); | ||
/* Hide toolbars and markers while dragging; click will re-show it */ | ||
this.dragging.on('dragstart', function () { | ||
overlay.fire('dragstart'); | ||
this._hideToolbar(); | ||
}, this); | ||
@@ -1031,2 +1395,3 @@ /* | ||
overlay.fire('update'); | ||
overlay.fire('drag'); | ||
@@ -1042,3 +1407,3 @@ this.fire('drag'); | ||
if (handlerName !== undefined && this._overlay.options.suppressToolbar !== true) { | ||
this[handlerName].call(this); | ||
this[handlerName].call(this); | ||
} | ||
@@ -1063,5 +1428,3 @@ }, | ||
map.removeLayer(this._handles[this._mode]); | ||
this._mode = 'scale'; | ||
map.addLayer(this._handles[this._mode]); | ||
@@ -1074,5 +1437,3 @@ }, | ||
map.removeLayer(this._handles[this._mode]); | ||
this._mode = 'rotateStandalone'; | ||
map.addLayer(this._handles[this._mode]); | ||
@@ -1106,2 +1467,10 @@ }, | ||
_sendUp: function() { | ||
this._overlay.bringToFront(); | ||
}, | ||
_sendDown: function() { | ||
this._overlay.bringToBack(); | ||
}, | ||
_toggleLock: function() { | ||
@@ -1124,2 +1493,14 @@ var map = this._overlay._map; | ||
_select: function (event) { | ||
this._showToolbar(event); | ||
this._showMarkers(); | ||
L.DomEvent.stopPropagation(event); | ||
}, | ||
_deselect: function (event) { | ||
this._hideToolbar(event); | ||
this._hideMarkers(); | ||
}, | ||
_hideToolbar: function() { | ||
@@ -1133,5 +1514,27 @@ var map = this._overlay._map; | ||
_showMarkers: function() { | ||
if (this._mode === 'lock') { return; } | ||
this._distortHandles.eachLayer(function (layer) { | ||
layer.setOpacity(1); | ||
layer.dragging.enable(); | ||
layer.options.draggable = true; | ||
}); | ||
}, | ||
_hideMarkers: function() { | ||
this._distortHandles.eachLayer(function (layer) { | ||
var drag = layer.dragging, | ||
opts = layer.options; | ||
layer.setOpacity(0); | ||
if (drag) { drag.disable(); } | ||
if (opts.draggable) { opts.draggable = false; } | ||
}); | ||
}, | ||
// TODO: toolbar for multiple image selection | ||
_showToolbar: function(event) { | ||
var overlay = this._overlay, | ||
target = event.target, | ||
target = event.target, | ||
map = overlay._map; | ||
@@ -1141,15 +1544,16 @@ | ||
this._hideToolbar(); | ||
var point; | ||
if (event.containerPoint) { point = event.containerPoint; } | ||
else { point = target._leaflet_pos; } | ||
var raised_point = map.containerPointToLatLng(new L.Point(point.x,point.y-20)); | ||
raised_point.lng = overlay.getCenter().lng; | ||
if (this._overlay.options.suppressToolbar !== true) { | ||
this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); | ||
overlay.fire('toolbar:created'); | ||
this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); | ||
overlay.fire('toolbar:created'); | ||
} | ||
}, | ||
L.DomEvent.stopPropagation(event); | ||
}, | ||
_removeOverlay: function () { | ||
@@ -1160,2 +1564,3 @@ var overlay = this._overlay; | ||
if (choice) { | ||
this._hideToolbar(); | ||
overlay._map.removeLayer(overlay); | ||
@@ -1166,3 +1571,15 @@ overlay.fire('delete'); | ||
} | ||
}, | ||
}, | ||
// compare this to using overlay zIndex | ||
_toggleOrder: function () { | ||
if (this._toggledImage) { | ||
this._overlay.bringToFront(); | ||
this._toggledImage = false; | ||
} | ||
else { | ||
this._overlay.bringToBack(); | ||
this._toggledImage = true; | ||
} | ||
}, | ||
@@ -1257,1 +1674,93 @@ // Based on https://github.com/publiclab/mapknitter/blob/8d94132c81b3040ae0d0b4627e685ff75275b416/app/assets/javascripts/mapknitter/Map.js#L47-L82 | ||
}); | ||
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); |
module.exports = function(grunt) { | ||
require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); | ||
// require('exif-js'); | ||
@@ -31,2 +32,4 @@ grunt.initConfig({ | ||
warpWebGl: false, | ||
EXIF: false, | ||
alert: false, | ||
@@ -81,2 +84,3 @@ // Mocha | ||
'src/util/*.js', | ||
'src/edit/getEXIFdata.js', | ||
'src/edit/EditHandle.js', | ||
@@ -89,4 +93,6 @@ 'src/edit/LockHandle.js', | ||
'src/DistortableImageOverlay.js', | ||
'src/DistortableCollection.js', | ||
'src/edit/DistortableImage.EditToolbar.js', | ||
'src/edit/DistortableImage.Edit.js', | ||
'src/edit/BoxSelectHandle.js' | ||
], | ||
@@ -93,0 +99,0 @@ dest: 'dist/leaflet.distortableimage.js', |
{ | ||
"name": "leaflet-distortableimage", | ||
"version": "0.3.0", | ||
"version": "0.4.0", | ||
"description": "Leaflet plugin enabling image overlays to be distorted, stretched, and warped (built for Public Lab's MapKnitter: http://publiclab.org).", | ||
@@ -36,2 +36,3 @@ "scripts": { | ||
"chai": "^4.1.2", | ||
"exif-js": "^2.3.0", | ||
"font-awesome": "^4.2.0", | ||
@@ -38,0 +39,0 @@ "glfx": "0.0.4", |
114
README.md
@@ -1,2 +0,2 @@ | ||
Leaflet.DistortableImage | ||
Leaflet.DistortableImage | ||
=================== | ||
@@ -6,14 +6,14 @@ | ||
A Leaflet extension to distort images -- "rubbersheeting" -- for the [MapKnitter.org](http://mapknitter.org) ([src](https://github.com/publiclab/mapknitter)) image georectification service by [Public Lab](http://publiclab.org). Leaflet.DistortableImage allows for perspectival distortions of images, client-side, using CSS3 transformations in the DOM. | ||
A Leaflet extension to distort images -- "rubbersheeting" -- for the [MapKnitter.org](http://mapknitter.org) ([src](https://github.com/publiclab/mapknitter)) image georectification service by [Public Lab](http://publiclab.org). Leaflet.DistortableImage allows for perspectival distortions of images, client-side, using CSS3 transformations in the DOM. | ||
Advantages include: | ||
* it can handle over 100 images smoothly, even on a smartphone. | ||
* images can be right-clicked and downloaded individually in their original state | ||
* It can handle over 100 images smoothly, even on a smartphone. | ||
* Images can be right-clicked and downloaded individually in their original state | ||
* CSS3 transforms are GPU-accelerated in most (all?) browsers, for a very smooth UI | ||
* no need to server-side generate raster GeoTiffs, tilesets, etc in order to view distorted imagery layers | ||
* images use DOM event handling for real-time distortion | ||
* [full resolution download option](https://github.com/publiclab/Leaflet.DistortableImage/pull/100) for large images, using WebGL acceleration | ||
* No need to server-side generate raster GeoTiffs, tilesets, etc. in order to view distorted imagery layers | ||
* Images use DOM event handling for real-time distortion | ||
* [Full resolution download option](https://github.com/publiclab/Leaflet.DistortableImage/pull/100) for large images, using WebGL acceleration | ||
[Download as zip](https://github.com/publiclab/Leaflet.DistortableImage/releases) or clone to get a copy of the repo. | ||
[Download as zip](https://github.com/publiclab/Leaflet.DistortableImage/releases) or clone the repo to get a local copy. | ||
@@ -38,7 +38,9 @@ This plugin has basic functionality, and is in production as part of MapKnitter, but there are [plenty of outstanding issues to resolve](https://github.com/publiclab/Leaflet.DistortableImage/issues). Please consider helping out! | ||
## Usage | ||
## Basic Usage | ||
The most simple implementation to get started: | ||
```js | ||
// basic Leaflet map setup | ||
map = new L.map('map').setView([51.505, -0.09], 13); | ||
map = L.map('map').setView([51.505, -0.09], 13); | ||
L.tileLayer('https://{s}.tiles.mapbox.com/v3/anishshah101.ipm9j6em/{z}/{x}/{y}.png', { | ||
@@ -53,3 +55,3 @@ maxZoom: 18, | ||
// create an image | ||
img = new L.DistortableImageOverlay( | ||
img = L.distortableImageOverlay( | ||
'example.png', { | ||
@@ -62,7 +64,7 @@ corners: [ | ||
], | ||
fullResolutionSrc: 'large.jpg' // optionally pass in a higher resolution image to use in full-res exporting | ||
} | ||
).addTo(map); | ||
L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); // enable editing | ||
// enable editing | ||
L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); | ||
@@ -78,20 +80,16 @@ ``` | ||
<script src="../node_modules/webgl-distort/dist/webgl-distort.js"></script> | ||
<script src="../node_modules/glfx-js/dist/glfx.js"></script> | ||
<script src="../node_modules/glfx/glfx.js"></script> | ||
``` | ||
## Usage | ||
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 | ||
// basic Leaflet map setup | ||
map = new L.map('map').setView([51.505, -0.09], 13); | ||
L.tileLayer('https://{s}.tiles.mapbox.com/v3/anishshah101.ipm9j6em/{z}/{x}/{y}.png', { | ||
maxZoom: 18, | ||
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' + | ||
'<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' + | ||
'Imagery © <a href="http://mapbox.com">Mapbox</a>', | ||
id: 'examples.map-i86knfo3' | ||
}).addTo(map); | ||
// create an image | ||
img = new L.DistortableImageOverlay( | ||
// create basic map setup from above | ||
// create an image - note the optional | ||
// fullResolutionSrc option is now passed in | ||
img = L.distortableImageOverlay( | ||
'example.png', { | ||
@@ -104,12 +102,69 @@ corners: [ | ||
], | ||
fullResolutionSrc: 'large.jpg' // optionally pass in a higher resolution image to use in full-res exporting | ||
fullResolutionSrc: 'large.jpg' | ||
} | ||
).addTo(map); | ||
L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); // enable editing | ||
L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); | ||
``` | ||
**** | ||
## 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. | ||
- Multiple images can be selected using <kbd>cmd</kbd> + `click` to toggle individual image selection. | ||
- Click on the map or hit the <kbd>esc</kbd> key to quickly deselect all images. | ||
Our `DistortableCollection` class allows working with multiple images simultaneously. Say we instantiated 3 images, saved them to the variables `img`, `img2`, and `img3`, and enabled editing on all of them. To access the UI and functionalities available in the multiple image interface, pass them to the collection class: | ||
```js | ||
// OPTION 1: Pass in images immediately | ||
new L.DistortableCollection([img, img2, img3]).addTo(map); | ||
// OPTION 2: Instantiate an empty collection and pass in images later | ||
var imageFeatureGroup = new L.DistortableCollection().addTo(map); | ||
imageFeatureGroup.addLayer(img); | ||
imageFeatureGroup.addLayer(img2); | ||
imageFeatureGroup.addLayer(img3); | ||
``` | ||
## Image-ordering | ||
For multiple images, we've also added a `ToggleOrder` action, that switches overlapping images back and forth into view by employing [`bringToFront()`](https://leafletjs.com/reference-1.4.0.html#popup-bringtofront) and [`bringToBack()`](https://leafletjs.com/reference-1.4.0.html#popup-bringtoback) from the Leaflet API. | ||
```js | ||
ToggleOrder = EditOverlayAction.extend({ | ||
options: { | ||
toolbarIcon: { | ||
html: '<span class="fa fa-sort"></span>', | ||
tooltip: 'Change order', | ||
title: 'Toggle order' | ||
} | ||
}, | ||
addHooks: function () | ||
{ | ||
var editing = this._overlay.editing; | ||
editing._toggleOrder(); // toggles images into view | ||
this.disable(); | ||
} | ||
}); | ||
``` | ||
### Corners | ||
The corners are stored in `img._corners` as `L.latLng` objects, so after instantiating the image and moving it around, you can always access them like this: | ||
```js | ||
img = L.distortableImageOverlay(...); | ||
img.addTo(map); | ||
// move the image around | ||
JSON.stringify(img._corners) | ||
=> "[{"lat":51.52,"lng":-0.1},{"lat":51.52,"lng":-0.14},{"lat":51.5,"lng":-0.1},{"lat":51.5,"lng":-0.14}]" | ||
``` | ||
## Setup | ||
@@ -136,1 +191,2 @@ | ||
Many more at https://github.com/publiclab/Leaflet.DistortableImage/graphs/contributors | ||
|
@@ -7,10 +7,12 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ | ||
height: 200, | ||
crossOrigin: true | ||
crossOrigin: true, | ||
edgeMinWidth: 500, | ||
}, | ||
initialize: function(url, options) { | ||
this._url = url; | ||
this._rotation = this.options.rotation; | ||
this._toolArray = L.DistortableImage.EditToolbarDefaults; | ||
this._url = url; | ||
this._rotation = this.options.rotation; | ||
L.setOptions(this, options); | ||
L.setOptions(this, options); | ||
}, | ||
@@ -31,4 +33,4 @@ | ||
/* Use provided corners if available */ | ||
if (this.options.corners) { | ||
this._corners = this.options.corners; | ||
if (this.options.corners) { | ||
this._corners = this.options.corners; | ||
if (map.options.zoomAnimation && L.Browser.any3d) { | ||
@@ -38,4 +40,4 @@ map.on('zoomanim', this._animateZoom, this); | ||
/* This reset happens before image load; it allows | ||
* us to place the image on the map earlier with | ||
/* This reset happens before image load; it allows | ||
* us to place the image on the map earlier with | ||
* "guessed" dimensions. */ | ||
@@ -45,3 +47,3 @@ this._reset(); | ||
/* Have to wait for the image to load because | ||
/* Have to wait for the image to load because | ||
* we need to access its width and height. */ | ||
@@ -52,3 +54,3 @@ L.DomEvent.on(this._image, 'load', function() { | ||
/* Initialize default corners if not already set */ | ||
if (!this._corners) { | ||
if (!this._corners) { | ||
if (map.options.zoomAnimation && L.Browser.any3d) { | ||
@@ -58,5 +60,5 @@ map.on('zoomanim', this._animateZoom, this); | ||
} | ||
}, this); | ||
}, this); | ||
this.fire('add'); | ||
this.fire('add'); | ||
}, | ||
@@ -78,2 +80,11 @@ | ||
_addTool: function(tool) { | ||
this._toolArray.push(tool); | ||
L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ | ||
options: { | ||
actions: this._toolArray | ||
} | ||
}); | ||
}, | ||
_initImageDimensions: function() { | ||
@@ -134,3 +145,24 @@ var map = this._map, | ||
// 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 */ | ||
@@ -172,3 +204,3 @@ /* since L.DomUtil.getTranslateString() is deprecated in Leaflet v1.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 | ||
* Leaflet then generates a CSS3 animation between the current transform and | ||
* future transform which makes the transition appear smooth. | ||
@@ -182,12 +214,12 @@ */ | ||
}, | ||
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) { | ||
@@ -221,8 +253,13 @@ image.style[L.DomUtil.TRANSFORM] = [translation, warp].join(' '); | ||
// 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]); | ||
}, | ||
_calculateProjectiveTransform: function(latLngToCartesian) { | ||
/* Setting reasonable but made-up image defaults | ||
* allow us to place images on the map before | ||
/* 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, | ||
w = this._image.offsetWidth || 500, | ||
h = this._image.offsetHeight || 375, | ||
@@ -235,3 +272,3 @@ c = [], | ||
} | ||
/* | ||
@@ -253,1 +290,8 @@ * This matrix describes the action of the CSS transform on each corner of the image. | ||
}); | ||
L.distortableImageOverlay = function(id, options) { | ||
return new L.DistortableImageOverlay(id, options); | ||
}; | ||
@@ -8,13 +8,16 @@ L.DistortableImage = L.DistortableImage || {}; | ||
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 | ||
46: "_removeOverlay", // delete windows / delete + fn mac | ||
8: "_removeOverlay", // backspace windows / delete mac | ||
83: '_toggleScale', // s | ||
84: '_toggleTransparency', // t | ||
20: '_toggleRotate' // CAPS | ||
} | ||
@@ -25,2 +28,3 @@ }, | ||
this._overlay = overlay; | ||
this._toggledImage = false; | ||
@@ -33,3 +37,3 @@ /* Interaction modes. */ | ||
/* Run on image seletion. */ | ||
/* Run on image selection. */ | ||
addHooks: function() { | ||
@@ -40,5 +44,2 @@ var overlay = this._overlay, | ||
/* bring the selected image into view */ | ||
overlay.bringToFront(); | ||
this._lockHandles = new L.LayerGroup(); | ||
@@ -77,3 +78,3 @@ for (i = 0; i < 4; i++) { | ||
if (this._mode === 'lock') { | ||
if (this._mode === 'lock') { | ||
map.addLayer(this._lockHandles); | ||
@@ -83,8 +84,20 @@ } else { | ||
map.addLayer(this._distortHandles); | ||
this._distortHandles.eachLayer(function (layer) { | ||
layer.setOpacity(0); | ||
layer.dragging.disable(); | ||
layer.options.draggable = false; | ||
}); | ||
this._enableDragging(); | ||
} | ||
//overlay.on('click', this._showToolbar, this); | ||
L.DomEvent.on(overlay._image, 'click', this._showToolbar, this); | ||
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. */ | ||
@@ -94,6 +107,5 @@ L.DomEvent.on(window, 'keydown', this._onKeyDown, this); | ||
overlay.fire('select'); | ||
}, | ||
/* Run on image deseletion. */ | ||
/* Run on image deselection. */ | ||
removeHooks: function() { | ||
@@ -103,11 +115,12 @@ var overlay = this._overlay, | ||
// L.DomEvent.off(window, 'keydown', this._onKeyDown, this); | ||
L.DomEvent.off(map, "click", this._deselect, this); | ||
L.DomEvent.off(overlay._image, 'click', this._select, this); | ||
L.DomEvent.off(overlay._image, 'click', this._showToolbar, this); | ||
// First, check if dragging exists; | ||
// it may be off due to locking | ||
// 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]); | ||
@@ -123,5 +136,4 @@ | ||
return window.confirm("Are you sure you want to delete?"); | ||
}, | ||
}, | ||
_rotateBy: function(angle) { | ||
@@ -169,4 +181,7 @@ var overlay = this._overlay, | ||
/* Hide toolbars while dragging; click will re-show it */ | ||
this.dragging.on('dragstart', this._hideToolbar, this); | ||
/* Hide toolbars and markers while dragging; click will re-show it */ | ||
this.dragging.on('dragstart', function () { | ||
overlay.fire('dragstart'); | ||
this._hideToolbar(); | ||
}, this); | ||
@@ -190,2 +205,3 @@ /* | ||
overlay.fire('update'); | ||
overlay.fire('drag'); | ||
@@ -201,3 +217,3 @@ this.fire('drag'); | ||
if (handlerName !== undefined && this._overlay.options.suppressToolbar !== true) { | ||
this[handlerName].call(this); | ||
this[handlerName].call(this); | ||
} | ||
@@ -222,5 +238,3 @@ }, | ||
map.removeLayer(this._handles[this._mode]); | ||
this._mode = 'scale'; | ||
map.addLayer(this._handles[this._mode]); | ||
@@ -233,5 +247,3 @@ }, | ||
map.removeLayer(this._handles[this._mode]); | ||
this._mode = 'rotateStandalone'; | ||
map.addLayer(this._handles[this._mode]); | ||
@@ -265,2 +277,10 @@ }, | ||
_sendUp: function() { | ||
this._overlay.bringToFront(); | ||
}, | ||
_sendDown: function() { | ||
this._overlay.bringToBack(); | ||
}, | ||
_toggleLock: function() { | ||
@@ -283,2 +303,14 @@ var map = this._overlay._map; | ||
_select: function (event) { | ||
this._showToolbar(event); | ||
this._showMarkers(); | ||
L.DomEvent.stopPropagation(event); | ||
}, | ||
_deselect: function (event) { | ||
this._hideToolbar(event); | ||
this._hideMarkers(); | ||
}, | ||
_hideToolbar: function() { | ||
@@ -292,5 +324,27 @@ var map = this._overlay._map; | ||
_showMarkers: function() { | ||
if (this._mode === 'lock') { return; } | ||
this._distortHandles.eachLayer(function (layer) { | ||
layer.setOpacity(1); | ||
layer.dragging.enable(); | ||
layer.options.draggable = true; | ||
}); | ||
}, | ||
_hideMarkers: function() { | ||
this._distortHandles.eachLayer(function (layer) { | ||
var drag = layer.dragging, | ||
opts = layer.options; | ||
layer.setOpacity(0); | ||
if (drag) { drag.disable(); } | ||
if (opts.draggable) { opts.draggable = false; } | ||
}); | ||
}, | ||
// TODO: toolbar for multiple image selection | ||
_showToolbar: function(event) { | ||
var overlay = this._overlay, | ||
target = event.target, | ||
target = event.target, | ||
map = overlay._map; | ||
@@ -300,15 +354,16 @@ | ||
this._hideToolbar(); | ||
var point; | ||
if (event.containerPoint) { point = event.containerPoint; } | ||
else { point = target._leaflet_pos; } | ||
var raised_point = map.containerPointToLatLng(new L.Point(point.x,point.y-20)); | ||
raised_point.lng = overlay.getCenter().lng; | ||
if (this._overlay.options.suppressToolbar !== true) { | ||
this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); | ||
overlay.fire('toolbar:created'); | ||
this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); | ||
overlay.fire('toolbar:created'); | ||
} | ||
}, | ||
L.DomEvent.stopPropagation(event); | ||
}, | ||
_removeOverlay: function () { | ||
@@ -319,2 +374,3 @@ var overlay = this._overlay; | ||
if (choice) { | ||
this._hideToolbar(); | ||
overlay._map.removeLayer(overlay); | ||
@@ -325,3 +381,15 @@ overlay.fire('delete'); | ||
} | ||
}, | ||
}, | ||
// compare this to using overlay zIndex | ||
_toggleOrder: function () { | ||
if (this._toggledImage) { | ||
this._overlay.bringToFront(); | ||
this._toggledImage = false; | ||
} | ||
else { | ||
this._overlay.bringToBack(); | ||
this._toggledImage = true; | ||
} | ||
}, | ||
@@ -415,2 +483,2 @@ // Based on https://github.com/publiclab/mapknitter/blob/8d94132c81b3040ae0d0b4627e685ff75275b416/app/assets/javascripts/mapknitter/Map.js#L47-L82 | ||
}); | ||
}); | ||
}); |
@@ -13,6 +13,6 @@ L.DistortableImage = L.DistortableImage || {}; | ||
ToggleTransparency = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-adjust"></span>', | ||
tooltip: 'Toggle Image Transparency', | ||
title: 'Toggle Image Transparency' | ||
title: 'Toggle Image Transparency' | ||
}}, | ||
@@ -29,3 +29,3 @@ | ||
ToggleOutline = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-square-o"></span>', | ||
@@ -45,3 +45,3 @@ tooltip: 'Toggle Image Outline', | ||
RemoveOverlay = EditOverlayAction.extend({ | ||
options: { toolbarIcon: { | ||
options: { toolbarIcon: { | ||
html: '<span class="fa fa-trash"></span>', | ||
@@ -65,3 +65,3 @@ tooltip: 'Delete image', | ||
tooltip: 'Lock / Unlock editing', | ||
title: 'Lock / Unlock editing' | ||
title: 'Lock / Unlock editing' | ||
}}, | ||
@@ -85,3 +85,3 @@ | ||
tooltip: 'Rotate', | ||
title: 'Rotate' | ||
title: 'Rotate' | ||
}; | ||
@@ -109,3 +109,3 @@ | ||
}, | ||
addHooks: function () | ||
@@ -116,6 +116,39 @@ { | ||
editing._toggleExport(); | ||
this.disable(); | ||
this.disable(); | ||
} | ||
}); | ||
}), | ||
ToggleOrder = EditOverlayAction.extend({ | ||
options: { | ||
toolbarIcon: { | ||
html: '<span class="fa fa-sort"></span>', | ||
tooltip: 'Change order', | ||
title: 'Toggle order' | ||
} | ||
}, | ||
addHooks: function () | ||
{ | ||
var editing = this._overlay.editing; | ||
editing._toggleOrder(); | ||
this.disable(); | ||
} | ||
}), | ||
EnableEXIF = EditOverlayAction.extend({ | ||
options: { | ||
toolbarIcon: { | ||
html: '<span class="fa fa-compass"></span>', | ||
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({ | ||
@@ -129,5 +162,7 @@ options: { | ||
ToggleRotateDistort, | ||
ToggleExport | ||
ToggleExport, | ||
EnableEXIF, | ||
ToggleOrder | ||
] | ||
} | ||
}); |
L.EditHandle = L.Marker.extend({ | ||
initialize: function(overlay, corner, options) { | ||
@@ -3,0 +4,0 @@ var markerOptions, |
@@ -17,4 +17,5 @@ L.LockHandle = L.EditHandle.extend({ | ||
this.setLatLng(this._handled._corners[this._corner]); | ||
L.DomUtil.removeClass(this._handled.getElement(), 'selected'); | ||
} | ||
}); |
@@ -19,4 +19,5 @@ L.RotateAndScaleHandle = L.EditHandle.extend({ | ||
overlay.editing._hideToolbar(); | ||
overlay.editing._rotateBy(angle); | ||
overlay.editing._scaleBy(scale); | ||
@@ -34,7 +35,6 @@ /* | ||
} | ||
} else { | ||
overlay.editing._scaleBy(scale); | ||
} | ||
} | ||
overlay.fire('update'); | ||
}, | ||
@@ -41,0 +41,0 @@ |
@@ -10,3 +10,3 @@ L.RotateHandle = L.EditHandle.extend({ | ||
}, | ||
_onHandleDrag: function() { | ||
@@ -16,5 +16,6 @@ var overlay = this._handled, | ||
newLatLng = this.getLatLng(), | ||
angle = this._calculateAngle(formerLatLng, newLatLng); | ||
overlay.editing._hideToolbar(); | ||
overlay.editing._rotateBy(angle); | ||
@@ -21,0 +22,0 @@ |
@@ -33,3 +33,9 @@ L.DomUtil = L.extend(L.DomUtil, { | ||
return open + rotateString + ')'; | ||
}, | ||
toggleClass: function(el, className) { | ||
var c = className; | ||
return this.hasClass(el, c) ? this.removeClass(el, c) : this.addClass(el, c); | ||
} | ||
}); |
@@ -36,2 +36,4 @@ // Karma configuration | ||
"src/util/*.js", | ||
"src/edit/getEXIFdata.js", | ||
"src/edit/BoxSelectHandle.js", | ||
"src/edit/EditHandle.js", | ||
@@ -43,2 +45,3 @@ "src/edit/LockHandle.js", | ||
"src/edit/ScaleHandle.js", | ||
"src/DistortableCollection.js", | ||
"src/DistortableImageOverlay.js", | ||
@@ -48,6 +51,7 @@ "src/edit/DistortableImage.EditToolbar.js", | ||
"test/SpecHelper.js", | ||
"test/src/*Spec.js", | ||
"test/src/**/*Spec.js" | ||
], | ||
// so that karma can serve examples/example.jpg | ||
// so that karma can serve examples/example.png | ||
proxies: { | ||
@@ -54,0 +58,0 @@ "/examples/": "/base/examples/" |
@@ -6,3 +6,3 @@ describe("L.DistortableImage.Edit", function() { | ||
beforeEach(function(done) { | ||
map = new L.Map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
@@ -9,0 +9,0 @@ overlay = new L.DistortableImageOverlay('/examples/example.png', { |
@@ -7,3 +7,3 @@ describe("L.RotateAndScaleHandle", function() { | ||
beforeEach(function(done) { | ||
map = new L.Map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
distortable = new L.DistortableImageOverlay('/examples/example.png', { | ||
@@ -24,2 +24,6 @@ corners: [ | ||
it.skip("Should not distort the image during scaling", function () { | ||
}); | ||
describe("_calculateRotation", function() { | ||
@@ -26,0 +30,0 @@ it("Should return 0 when given the same latlng twice.", function() { |
@@ -7,3 +7,3 @@ describe("L.RotateHandle", function() { | ||
beforeEach(function(done) { | ||
map = new L.Map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
distortable = new L.DistortableImageOverlay('/examples/example.jpg', { | ||
@@ -10,0 +10,0 @@ corners: [ |
@@ -7,3 +7,3 @@ describe("L.ScaleHandle", function() { | ||
beforeEach(function(done) { | ||
map = new L.Map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); | ||
distortable = new L.DistortableImageOverlay('/examples/example.jpg', { | ||
@@ -10,0 +10,0 @@ corners: [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1485850
47
3388
186
22