diff --git a/Gruntfile.js b/Gruntfile.js index ce5e68a92..c2aded3d5 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,99 +3,145 @@ module.exports = function(grunt) { require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - jshint: { - options: { - node: true, - browser: true, - esnext: true, - bitwise: true, - curly: true, - eqeqeq: true, - immed: true, - indent: 4, - latedef: true, - newcap: true, - noarg: true, - regexp: true, - undef: true, - unused: 'strict', - trailing: true, - smarttabs: true, - globals: { - L: false, - $: false, - LeafletToolbar: false, - warpWebGl: false, - EXIF: false, - alert: false, - - // Mocha - - describe: false, - it: false, - before: false, - after: false, - beforeEach: false, - afterEach: false, - chai: false, - expect: false, - sinon: false - } - }, - source: { - src: [ 'src/**/*.js', 'package.json' ] - }, - grunt: { - src: [ 'Gruntfile.js' ] - } + pkg: grunt.file.readJSON("package.json"), + + jshint: { + options: { + node: true, + browser: true, + esnext: true, + bitwise: true, + curly: true, + eqeqeq: true, + immed: true, + indent: 4, + latedef: true, + newcap: true, + noarg: true, + regexp: true, + undef: true, + unused: "strict", + trailing: true, + smarttabs: true, + globals: { + L: false, + $: false, + LeafletToolbar: false, + warpWebGl: false, + EXIF: false, + alert: false, + + // Mocha + + describe: false, + it: false, + before: false, + after: false, + beforeEach: false, + afterEach: false, + chai: false, + expect: false, + sinon: false + } + }, + source: { + src: ["src/**/*.js", "package.json"] }, + grunt: { + src: ["Gruntfile.js"] + } + }, - karma: { - development: { - configFile: 'test/karma.conf.js', - }, - test: { - configFile: 'test/karma.conf.js', + karma: { + development: { + configFile: "test/karma.conf.js" + }, + test: { + configFile: "test/karma.conf.js" + } + }, + + // Minify SVGs from svg directory, output to svg-min + svgmin: { + dist: { + files: [ + { + attrs: "fill", + expand: true, + cwd: "assets/icons/svg", + src: ["*.svg"], + dest: "assets/icons/svg-min/", + ext: ".svg" } + ] }, + options: { + plugins: [ + { removeViewBox: false }, + { removeEmptyAttrs: false }, + { removeTitle: true } // addtitle will add it back in later + ] + } + }, - watch: { - options : { - livereload: true + svg_sprite: { + options: { + // Task-specific options go here. + }, + dist: { + expand: true, + cwd: "assets/icons/svg-min/", + src: ["*.svg"], + dest: "assets/icons/", + options: { + log: "info", + shape: { + dimension: { + maxWidth: 18, + maxHeight: 18 + } }, - source: { - files: [ - 'src/**/*.js', - 'test/**/*.js', - 'Gruntfile.js' - ], - tasks: [ 'build:js' ] + mode: { + symbol: { + sprite: "sprite.symbol.svg", + example: true + } } + } + } + }, + + watch: { + options: { + livereload: true }, + source: { + files: ["src/**/*.js", "test/**/*.js", "Gruntfile.js"], + tasks: ["build:js"] + } + }, - concat: { - dist: { - src: [ - 'src/util/*.js', - 'src/DistortableImageOverlay.js', - 'src/DistortableCollection.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/edit/DistortableImage.EditToolbar.js', - 'src/edit/DistortableImage.Edit.js', - 'src/edit/tools/DistortableImage.Keymapper.js', - 'src/edit/BoxSelectHandle.js' - ], - dest: 'dist/leaflet.distortableimage.js', - } + concat: { + dist: { + src: [ + "src/util/*.js", + "src/DistortableImageOverlay.js", + "src/DistortableCollection.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/edit/DistortableImage.EditToolbar.js", + "src/edit/DistortableImage.Edit.js", + "src/edit/tools/DistortableImage.Keymapper.js", + "src/edit/BoxSelectHandle.js" + ], + dest: "dist/leaflet.distortableimage.js" } + } }); /* Run tests once. */ @@ -107,6 +153,8 @@ module.exports = function(grunt) { grunt.registerTask('build', [ 'jshint', 'karma:development:start', + 'svgmin', + 'svg_sprite', 'coverage', 'concat:dist' ]); diff --git a/README.md b/README.md index 9dafde5f4..62d671fbd 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,8 @@ Here's a screenshot: ## Setup 1. From the root directory, run `npm install` or `sudo npm install` -2. Open examples/index.html in a browser + +2. Open `examples/index.html` in a browser ## Demo @@ -64,17 +65,16 @@ img = L.distortableImageOverlay( L.latLng(51.50,-0.14), L.latLng(51.50,-0.10) ], - } -).addTo(map); + }).addTo(map); // enable editing L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); ``` Options available to pass during `L.DistortableImageOverlay` initialization: -- corners +- [corners](#corners) -- [selected](#selection) +- [selected](#selected) - [mode](#mode) @@ -84,6 +84,33 @@ Options available to pass during `L.DistortableImageOverlay` initialization: - [suppressToolbar](#Suppress-Toolbar) +## Corners + +The corners are stored as `L.latLng` objects +on the image, and can be accessed using our `getCorners()` method after the image is instantiated and added to the map. + +Useful usage example: + +```js +// instantiate, add to map and enable +img = L.distortableImageOverlay(...); +img.addTo(map); +L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); + +// grab the initial corner positions +JSON.stringify(img.getCorners()) +=> "[{"lat":51.52,"lng":-0.14},{"lat":51.52,"lng":-0.1},{"lat":51.5,"lng":-0.14},{"lat":51.5,"lng":-0.1}]" + +// ...move the image around... + +// check the new corner positions. +JSON.stringify(img.getCorners()) +=> "[{"lat":51.50685099607552,"lng":-0.06058305501937867},{"lat":51.50685099607552,"lng":-0.02058595418930054},{"lat":51.486652692081925,"lng":-0.06058305501937867},{"lat":51.486652692081925,"lng":-0.02058595418930054}]" + +// note there is an added level of precision after dragging the image for debugging purposes + +``` +We further added a `getCorner(idx)` method used the same way as its plural counterpart but with an index passed to it. ## Selected @@ -97,16 +124,23 @@ Some developers prefer that an image initially appears as "selected" instead of `mode` (*optional*, default: "distort", value: *string*) -Each primary editing mode corresponds to a separate editing tool. +Each primary editing mode corresponds to a separate editing handle. -This option sets the image's initial editing mode, meaning the corresponding editing tool will always appear first when you interact with the image. +This option sets the image's initial editing mode, meaning the corresponding editing handle will always appear first when you interact with the image. Values available to pass to `mode` are: -- "distort" (the default) -- "lock" -- "rotate" -- "scale" -- "rotateScale" + +- #### distort (_default_) + +- #### rotate + +- #### scale + +- #### rotateScale: + +- #### lock: + + - mode which prevents any image actions (including those triggered from the toolbar, user gestures, and hotkeys) until the toolbar action [ToggleLock](#ToggleLock-(l)) is explicitly triggered (or its hotkey l) In the below example, the image will be initialiazed with "rotateScale" handles: @@ -158,8 +192,7 @@ img = L.distortableImageOverlay( L.latLng(51.50,-0.10) ], fullResolutionSrc: 'large.jpg' - } -).addTo(map); + }).addTo(map); L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); @@ -198,76 +231,67 @@ imageFeatureGroup.addLayer(img3); ```
-## Default Toolbar Actions - -- "ToggleLock" +## Default Toolbar Actions (& Keybindings) -- "ToggleOrder" +#### ToggleLock (l) -- "ToggleOutline" +- Toggles between [lock mode](#lock) and [distort mode](#distort-(_default_)) -- "ToggleTransparency" +#### ToggleRotateScale (r) -- "ToggleRotateScale" -- "EnableEXIF" +#### ToggleOrder (j, k) -- "Export" +- For multiple images, 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. -- "Delete" +#### ToggleOutline (o) -## Addons - -- "ToggleRotate" +#### ToggleTransparency (t) -- "ToggleScale" +#### EnableEXIF (WIP) -### Image-ordering +#### Restore -For multiple images, the `ToggleOrder` action 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. +- Restores the image to its original proportions and scale, but keeps its current rotation angle and location intact. +#### Export -## Quick API Reference +#### Delete (delete, backscpace) -- [`getCorners()`](corners) and [`getCorner(idx)`](corners) +- Permanently deletes the image from the map. -- `getCenter()` - Calculates the centroid of the image +## Addons -## Corners +- **ToggleRotate** (caps lock): -The corners are stored as `L.latLng` objects -on the image, and can be accessed using our `getCorners()` method after the image is instantiated and added to the map. +- **ToggleScale** (s): -Useful usage example: +## Quick API Reference -```js -// instantiate, add to map and enable -img = L.distortableImageOverlay(...); -img.addTo(map); -L.DomEvent.on(img._image, 'load', img.editing.enable, img.editing); +- [`getCorners()`](#corners) and [`getCorner(idx)`](#corners) -// grab the initial corner positions -JSON.stringify(img.getCorners()) -=> "[{"lat":51.52,"lng":-0.14},{"lat":51.52,"lng":-0.1},{"lat":51.5,"lng":-0.14},{"lat":51.5,"lng":-0.1}]" +- `getCenter()` - Calculates the centroid of the image -// ...move the image around... +## Contributing -// check the new corner positions. -JSON.stringify(img.getCorners()) -=> "[{"lat":51.50685099607552,"lng":-0.06058305501937867},{"lat":51.50685099607552,"lng":-0.02058595418930054},{"lat":51.486652692081925,"lng":-0.06058305501937867},{"lat":51.486652692081925,"lng":-0.02058595418930054}]" +1) This project uses `grunt` to do a lot of things, including concatenate source files from `/src/` to `/DistortableImageOverlay.js`: -// note there is an added level of precision after dragging the image for debugging purposes +```Bash +#you may need to install grunt-cli first: +$ npm install -g grunt-cli +#run in root dir, and it'll watch for changes and concatenate them on the fly +$ grunt ``` -We further added a `getCorner(idx)` method used the same way as its plural counterpart but with an index passed to it. -## Contributing -1. This project uses `grunt` to do a lot of things, including concatenate source files from /src/ to /DistortableImageOverlay.js. But you may need to install grunt-cli: `npm install -g grunt-cli` first. -2. Run `grunt` in the root directory, and it will watch for changes and concatenate them on the fly. +2) To build all files from `/src/` into the `/dist/` folder, run: -To build all files from `/src/` into the `/dist/` folder, run `grunt concat:dist`. +```Bash +$ grunt concat:dist +``` +3. _Optional_: We use SVG for our icon system. Please visit our wiki [SVG Icon System](https://github.com/publiclab/Leaflet.DistortableImage/wiki/SVG-Icon-System) if you are interested in making updates to them or just simply learning about our workflow. **** ### Contributors diff --git a/assets/icons/svg-min/border_clear.svg b/assets/icons/svg-min/border_clear.svg new file mode 100644 index 000000000..6eeb92a15 --- /dev/null +++ b/assets/icons/svg-min/border_clear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/border_outer.svg b/assets/icons/svg-min/border_outer.svg new file mode 100644 index 000000000..9f937c629 --- /dev/null +++ b/assets/icons/svg-min/border_outer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/crop_rotate.svg b/assets/icons/svg-min/crop_rotate.svg new file mode 100644 index 000000000..4ecf1bca7 --- /dev/null +++ b/assets/icons/svg-min/crop_rotate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/delete_forever.svg b/assets/icons/svg-min/delete_forever.svg new file mode 100644 index 000000000..391b8edb2 --- /dev/null +++ b/assets/icons/svg-min/delete_forever.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/explore.svg b/assets/icons/svg-min/explore.svg new file mode 100644 index 000000000..9ae3c0900 --- /dev/null +++ b/assets/icons/svg-min/explore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/flip_to_back.svg b/assets/icons/svg-min/flip_to_back.svg new file mode 100644 index 000000000..83ed7a3cd --- /dev/null +++ b/assets/icons/svg-min/flip_to_back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/flip_to_front.svg b/assets/icons/svg-min/flip_to_front.svg new file mode 100644 index 000000000..0b1a4fcbb --- /dev/null +++ b/assets/icons/svg-min/flip_to_front.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/get_app.svg b/assets/icons/svg-min/get_app.svg new file mode 100644 index 000000000..10505986a --- /dev/null +++ b/assets/icons/svg-min/get_app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/lock.svg b/assets/icons/svg-min/lock.svg new file mode 100644 index 000000000..c4848937e --- /dev/null +++ b/assets/icons/svg-min/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/opacity-empty.svg b/assets/icons/svg-min/opacity-empty.svg new file mode 100644 index 000000000..0553aae3e --- /dev/null +++ b/assets/icons/svg-min/opacity-empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/opacity.svg b/assets/icons/svg-min/opacity.svg new file mode 100644 index 000000000..79261755c --- /dev/null +++ b/assets/icons/svg-min/opacity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/restore.svg b/assets/icons/svg-min/restore.svg new file mode 100644 index 000000000..96358036e --- /dev/null +++ b/assets/icons/svg-min/restore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/transform.svg b/assets/icons/svg-min/transform.svg new file mode 100644 index 000000000..a7289f392 --- /dev/null +++ b/assets/icons/svg-min/transform.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg-min/unlock.svg b/assets/icons/svg-min/unlock.svg new file mode 100644 index 000000000..97c3b7612 --- /dev/null +++ b/assets/icons/svg-min/unlock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/svg/border_clear.svg b/assets/icons/svg/border_clear.svg new file mode 100644 index 000000000..166ed0def --- /dev/null +++ b/assets/icons/svg/border_clear.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/border_outer.svg b/assets/icons/svg/border_outer.svg new file mode 100644 index 000000000..e1d39c735 --- /dev/null +++ b/assets/icons/svg/border_outer.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/crop_rotate.svg b/assets/icons/svg/crop_rotate.svg new file mode 100644 index 000000000..114a3bed8 --- /dev/null +++ b/assets/icons/svg/crop_rotate.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/delete_forever.svg b/assets/icons/svg/delete_forever.svg new file mode 100644 index 000000000..0104a72c2 --- /dev/null +++ b/assets/icons/svg/delete_forever.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/explore.svg b/assets/icons/svg/explore.svg new file mode 100644 index 000000000..6300a54ac --- /dev/null +++ b/assets/icons/svg/explore.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/flip_to_back.svg b/assets/icons/svg/flip_to_back.svg new file mode 100644 index 000000000..6e9f99f59 --- /dev/null +++ b/assets/icons/svg/flip_to_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/flip_to_front.svg b/assets/icons/svg/flip_to_front.svg new file mode 100644 index 000000000..a8dbc05b0 --- /dev/null +++ b/assets/icons/svg/flip_to_front.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/get_app.svg b/assets/icons/svg/get_app.svg new file mode 100644 index 000000000..4f6090f8e --- /dev/null +++ b/assets/icons/svg/get_app.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/lock.svg b/assets/icons/svg/lock.svg new file mode 100644 index 000000000..b2ae8e67f --- /dev/null +++ b/assets/icons/svg/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/opacity-empty.svg b/assets/icons/svg/opacity-empty.svg new file mode 100644 index 000000000..a5bdd598b --- /dev/null +++ b/assets/icons/svg/opacity-empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/opacity.svg b/assets/icons/svg/opacity.svg new file mode 100644 index 000000000..fd150cbde --- /dev/null +++ b/assets/icons/svg/opacity.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/restore.svg b/assets/icons/svg/restore.svg new file mode 100644 index 000000000..9e8049345 --- /dev/null +++ b/assets/icons/svg/restore.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/transform.svg b/assets/icons/svg/transform.svg new file mode 100644 index 000000000..d501b7de6 --- /dev/null +++ b/assets/icons/svg/transform.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/svg/unlock.svg b/assets/icons/svg/unlock.svg new file mode 100644 index 000000000..14213c1f5 --- /dev/null +++ b/assets/icons/svg/unlock.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/symbol/sprite.symbol.html b/assets/icons/symbol/sprite.symbol.html new file mode 100644 index 000000000..35a6ccf0d --- /dev/null +++ b/assets/icons/symbol/sprite.symbol.html @@ -0,0 +1,441 @@ + + + + + + + + SVG <symbol> sprite preview | svg-sprite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

SVG <symbol> sprite preview

+

This preview features two methods of using the generated sprite in conjunction with inline SVG. Please have a look at the HTML source for further details and be aware of the following constraints:

+ +
+
+ + + +

A) Inline SVG with embedded sprite

+ + + + +
+
+ + + +

B) Inline SVG with external sprite (IE 9-11 with polyfill only)

+ + + + +
+ + + diff --git a/assets/icons/symbol/sprite.symbol.svg b/assets/icons/symbol/sprite.symbol.svg new file mode 100644 index 000000000..8d45507f9 --- /dev/null +++ b/assets/icons/symbol/sprite.symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dist/leaflet.distortableimage.css b/dist/leaflet.distortableimage.css index 6e1986481..5861cb9e1 100644 --- a/dist/leaflet.distortableimage.css +++ b/dist/leaflet.distortableimage.css @@ -1,46 +1,7 @@ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialicons/v47/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); -} - -@font-face { - font-family: 'Material Icons Outlined'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v8/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2) format('woff2'); -} - -.material-icons { - font-family: 'Material Icons'; -} - -.material-icons-outlined { - font-family: 'Material Icons Outlined'; -} - -.material-icons, .material-icons-outlined { - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons.red { color: #c10d0d; } - -.material-icons.md-18, .material-icons-outlined.md-18 { - font-size: 18px; - line-height: inherit; +svg { + width: 18px; + height: 18px; + vertical-align: middle; } #imgcontainer { diff --git a/dist/leaflet.distortableimage.js b/dist/leaflet.distortableimage.js index 34b13127f..3fe5a58af 100644 --- a/dist/leaflet.distortableimage.js +++ b/dist/leaflet.distortableimage.js @@ -1,1830 +1,1961 @@ -L.DomUtil = L.extend(L.DomUtil, { - getMatrixString: function(m) { - var is3d = L.Browser.webkit3d || L.Browser.gecko3d || L.Browser.ie3d, - - /* - * Since matrix3d takes a 4*4 matrix, we add in an empty row and column, which act as the identity on the z-axis. - * See: - * http://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ - * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#M.C3.B6bius'_homogeneous_coordinates_in_projective_geometry - */ - matrix = [ - m[0], m[3], 0, m[6], - m[1], m[4], 0, m[7], - 0, 0, 1, 0, - m[2], m[5], 0, m[8] - ], - - str = is3d ? 'matrix3d(' + matrix.join(',') + ')' : ''; - - if (!is3d) { - console.log('Your browser must support 3D CSS transforms in order to use DistortableImageOverlay.'); - } - - return str; - }, - - getRotateString: function(angle, units) { - var is3d = L.Browser.webkit3d || L.Browser.gecko3d || L.Browser.ie3d, - open = 'rotate' + (is3d ? '3d' : '') + '(', - rotateString = (is3d ? '0, 0, 1, ' : '') + angle + units; - - return open + rotateString + ')'; - }, - - toggleClass: function(el, className) { - var c = className; - return this.hasClass(el, c) ? this.removeClass(el, c) : this.addClass(el, c); - } - -}); - -L.Map.include({ - _newLayerPointToLatLng: function(point, newZoom, newCenter) { - var topLeft = L.Map.prototype._getNewTopLeftPoint.call(this, newCenter, newZoom) - .add(L.Map.prototype._getMapPanePos.call(this)); - return this.unproject(point.add(topLeft), newZoom); - } -}); -L.MatrixUtil = { - - // Compute the adjugate of m - adj: function(m) { - return [ - m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4], - m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5], - m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3] - ]; - }, - - // multiply two 3*3 matrices - multmm: function(a, b) { - var c = [], - i; - - for (i = 0; i < 3; i++) { - for (var j = 0; j < 3; j++) { - var cij = 0; - for (var k = 0; k < 3; k++) { - cij += a[3*i + k]*b[3*k + j]; - } - c[3*i + j] = cij; - } - } - return c; - }, - - // multiply a 3*3 matrix and a 3-vector - multmv: function(m, v) { - return [ - m[0]*v[0] + m[1]*v[1] + m[2]*v[2], - m[3]*v[0] + m[4]*v[1] + m[5]*v[2], - m[6]*v[0] + m[7]*v[1] + m[8]*v[2] - ]; - }, - - // multiply a scalar and a 3*3 matrix - multsm: function(s, m) { - var matrix = []; - - for (var i = 0, l = m.length; i < l; i++) { - matrix.push(s*m[i]); - } - - return matrix; - }, - - basisToPoints: function(x1, y1, x2, y2, x3, y3, x4, y4) { - var m = [ - x1, x2, x3, - y1, y2, y3, - 1, 1, 1 - ], - v = L.MatrixUtil.multmv(L.MatrixUtil.adj(m), [x4, y4, 1]); - - return L.MatrixUtil.multmm(m, [ - v[0], 0, 0, - 0, v[1], 0, - 0, 0, v[2] - ]); - }, - - - project: function(m, x, y) { - var v = L.MatrixUtil.multmv(m, [x, y, 1]); - return [v[0]/v[2], v[1]/v[2]]; - }, - - general2DProjection: function( - x1s, y1s, x1d, y1d, - x2s, y2s, x2d, y2d, - x3s, y3s, x3d, y3d, - x4s, y4s, x4d, y4d - ) { - var s = L.MatrixUtil.basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s), - d = L.MatrixUtil.basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d), - m = L.MatrixUtil.multmm(d, L.MatrixUtil.adj(s)); - - /* - * Normalize to the unique matrix with m[8] == 1. - * See: http://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ - */ - return L.MatrixUtil.multsm(1/m[8], m); - } -}; -L.TrigUtil = { - - calcAngleDegrees: function(x, y) { - return Math.atan2(y, x) * 180 / Math.PI; - } - -}; -L.DistortableImageOverlay = L.ImageOverlay.extend({ - - 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.DistortableImage._options = options; - - 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 = 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(L.point(offset.x, -offset.y)) - ), - map.containerPointToLatLng( - center.add(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 - ); - }, - - _getCmPerPixel: function() { - var map = this._map; - - var dist = map.latLngToLayerPoint(this.getCorner(0)) - .distanceTo(map.latLngToLayerPoint(this.getCorner(1))); - - return (dist * 100) / this._image.width; - } - -}); - -L.distortableImageOverlay = function(id, options) { - return new L.DistortableImageOverlay(id, options); -}; - - - - -L.DistortableCollection = L.FeatureGroup.extend({ - 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); - - 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() { - 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.getElement() !== event.target) { - edit._deselect(); - } else { - this._toggleMultiSelect(event, edit); - } - }, this); - - L.DomEvent.stopPropagation(event); - }, - - _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.getCorner(i)) && edit._mode !== "lock") { - L.DomUtil.addClass(layer.getElement(), "selected"); - break; - } - } - }); - }, - - _getAvgCmPerPixel: function(imgs) { - var reduce = imgs.reduce(function(sum, img) { - return sum + img.cm_per_pixel; - }, 0); - return reduce / imgs.length; - }, - - _generateExportJson: function() { - var json = {}; - json.images = []; - - this.eachLayer(function(layer) { - if (this.isSelected(layer)) { - json.images.push({ - id: this.getLayerId(layer), - src: layer._image.src, - nodes: layer.getCorners(), - cm_per_pixel: layer._getCmPerPixel() - }); - } - }, this); - - json.avg_cm_per_pixel = this._getAvgCmPerPixel(json.images); - - return json; - }, - - _runExport: function(collection) { - collection = collection || this._generateExportJson(); - $.ajax({ - url: "http://export.mapknitter.org/export", - crossDomain: true, - type: "POST", - data: { - collection: JSON.stringify(collection.images), - scale: 30 - }, - success: function _getStatusJson(data) { - console.log(data); - $.ajax("http://export.mapknitter.org" + data, { - type: "GET", - crossDomain: true - }).done(function(data) { - console.log(data); - }); - } - }); - }, - - _onKeyDown: function(e) { - if (e.key === "Escape") { - this._deselectAll(e); - } - if (e.key === "Backspace") { - this._removeFromGroup(e); - } - }, - - _dragStartMultiple: function(event) { - var overlay = event.target, - i; - - if (!this.isSelected(overlay)) { return; } - - this.eachLayer(function(layer) { - var edit = layer.editing; - edit._deselect(); - - for (i = 0; i < 4; i++) { - layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( - layer.getCorner(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.getCorner(i)); - } - - 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); - }, - /** - * 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.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; - - /* 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) { - var markerOptions, - latlng = overlay._corners[corner]; - - L.setOptions(this, options); - - this._handled = overlay; - this._corner = corner; - - markerOptions = { - draggable: true, - zIndexOffset: 10 - }; - - if (options && options.hasOwnProperty("draggable")) { - markerOptions.draggable = options.draggable; - } - - L.Marker.prototype.initialize.call(this, latlng, markerOptions); - }, - - onAdd: function(map) { - L.Marker.prototype.onAdd.call(this, map); - this._bindListeners(); - - this.updateHandle(); - }, - - onRemove: function(map) { - this._unbindListeners(); - L.Marker.prototype.onRemove.call(this, map); - }, - - _onHandleDragStart: function() { - this._handled.fire("editstart"); - }, - - _onHandleDragEnd: function() { - this._fireEdit(); - }, - - _fireEdit: function() { - this._handled.edited = true; - this._handled.fire("edit"); - }, - - _bindListeners: function() { - this.on( - { - dragstart: this._onHandleDragStart, - drag: this._onHandleDrag, - dragend: this._onHandleDragEnd - }, - this - ); - - this._handled._map.on("zoomend", this.updateHandle, this); - - this._handled.on("update", this.updateHandle, this); - }, - - _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); - }, - - /* 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); - }, - - /* 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; - } -}); - -L.LockHandle = L.EditHandle.extend({ - options: { - TYPE: 'lock', - icon: 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: L.icon({ - iconUrl: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAChSURBVO3BIU4DURgGwNkvL2B6AkQTLBqP4QCoSm7DDXoBLBZHDbfgICAIZjEV3YTn9uVHdMZZtcnCfI13bIzxg0emg6Nm6QVbYz3jylEsXRrvwommb49X67jFkz80fR9Mb1YxTzqiWBSLYlEsikWxKBbFolgUi2JRLIpFsSgWxaJY03fHHOu40dH07bAzWCx9Ge/TiWbpHgdsjPGNB2f/yS+7xRCyiiZPJQAAAABJRU5ErkJggg==", - iconSize: [32, 32], - iconAnchor: [16, 16] - }) - }, - - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, - - _onHandleDrag: function() { - this._handled._updateCorner(this._corner, this.getLatLng()); - - this._handled.fire("update"); - this._handled.editing._showToolbar(); - } -}); - -L.RotateScaleHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotateScale', - icon: 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), - scale = this._calculateScalingFactor(formerLatLng, newLatLng); - - overlay.editing._rotateBy(angle); - - /* - 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); - } - } - - overlay.fire('update'); - - this._handled.editing._showToolbar(); - - }, - - updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); - }, - -}); - -L.RotateHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: 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]); - } - -}); - -L.ScaleHandle = L.EditHandle.extend({ - options: { - TYPE: 'rotate', - icon: 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]); - }, - - -}); - -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: 'opacity', - tooltip: 'Toggle Transparency' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleTransparency(); - this.disable(); - } - }), - - ToggleOutline = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var edit = overlay.editing, - icon = edit._outlined ? 'border_clear' : 'border_outer'; - - options = options || {}; - options.toolbarIcon = { - html: '' + icon + '', - tooltip: 'Toggle Outline' - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOutline(); - this.disable(); - } - }), - - Delete = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'delete_forever', - tooltip: 'Delete Image' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._removeOverlay(); - this.disable(); - } - }), - - ToggleLock = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var edit = overlay.editing, - icon, - tooltip; - - if (edit._mode === 'lock') { - icon = 'lock_open'; - tooltip = 'Unlock'; - } else { - icon = 'lock'; - tooltip = 'Lock'; - } - - options = options || {}; - options.toolbarIcon = { - html: '' + icon + '', - tooltip: tooltip - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleLock(); - this.disable(); - } - }), - - ToggleRotateScale = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var edit = overlay.editing, - icon, - tooltip; - - if (edit._mode === 'rotateScale') { - icon = 'transform'; - tooltip = 'Distort'; - } else { - icon = 'crop_rotate'; - tooltip = 'Rotate+Scale'; - } - - options = options || {}; - options.toolbarIcon = { - html: '' + icon + '', - tooltip: tooltip - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleRotateScale(); - this.disable(); - } - }), - - Export = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'get_app', - tooltip: 'Export Image' - } - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleExport(); - this.disable(); - } - }), - - ToggleOrder = EditOverlayAction.extend({ - initialize: function(map, overlay, options) { - var edit = overlay.editing, - icon, - tooltip; - - if (edit._toggledImage) { - icon = 'flip_to_front'; - tooltip = 'Stack to front'; - } else { - icon = 'flip_to_back'; - tooltip = 'Stack to back'; - } - - options = options || {}; - options.toolbarIcon = { - html: '' + icon + '', - tooltip: tooltip - }; - - EditOverlayAction.prototype.initialize.call(this, map, overlay, options); - }, - - addHooks: function() { - var editing = this._overlay.editing; - - editing._toggleOrder(); - this.disable(); - } - }), - - EnableEXIF = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'explore', - tooltip: 'Geolocate Image' - } - }, - - addHooks: function() { - var image = this._overlay.getElement(); - - EXIF.getData(image, L.EXIF(image)); - } - }); - -L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ - options: { - actions: [ - ToggleTransparency, - ToggleOutline, - ToggleLock, - ToggleRotateScale, - ToggleOrder, - EnableEXIF, - Export, - Delete - ] - }, - - // 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)"; - } - } -}); - -L.DistortableImage = L.DistortableImage || {}; - -L.DistortableImage.Edit = L.Handler.extend({ - options: { - opacity: 0.7, - outline: "1px solid red", - keymap: { - 'Backspace': '_removeOverlay', // backspace windows / delete mac - 'CapsLock': '_toggleRotate', - 'Escape': '_deselect', - 'd': '_toggleRotateScale', - 'r': '_toggleRotateScale', - 'j': '_toggleOrder', - 'k': '_toggleOrder', - 'l': '_toggleLock', - 'o': '_toggleOutline', - 's': '_toggleScale', - 't': '_toggleTransparency', - } - }, - - initialize: function(overlay) { - this._overlay = overlay; - this._toggledImage = false; - /* Different actions. */ - var actions = ["distort", "lock", "rotate", "scale", "rotateScale"]; - /* Interaction modes. */ - this._mode = actions[actions.indexOf(this._overlay.options.mode)] || "distort"; - 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, - 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(); - - this._appendHandlesandDragable(this._mode); - - if (this._selected) { this._initToolbar(); } - - this._overlay._dragStartPoints = { - 0: L.point(0, 0), - 1: L.point(0, 0), - 2: L.point(0, 0), - 3: 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); - }, - - /* 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); - }, - - _initHandles: function() { - var overlay = this._overlay, - i; - - this._lockHandles = L.layerGroup(); - for (i = 0; i < 4; i++) { - this._lockHandles.addLayer( - new L.LockHandle(overlay, i, { draggable: false }) - ); - } - - this._distortHandles = L.layerGroup(); - for (i = 0; i < 4; i++) { - this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); - } - - this._rotateHandles = L.layerGroup(); // individual rotate - for (i = 0; i < 4; i++) { - this._rotateHandles.addLayer(new L.RotateHandle(overlay, i)); - } - - this._scaleHandles = L.layerGroup(); - for (i = 0; i < 4; i++) { - this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); - } - - this._rotateScaleHandles = L.layerGroup(); // handle includes rotate AND scale - for (i = 0; i < 4; i++) { - this._rotateScaleHandles.addLayer(new L.RotateScaleHandle(overlay, i)); - } - - this._handles = { - lock: this._lockHandles, - distort: this._distortHandles, - rotateScale: this._rotateScaleHandles, - scale: this._scaleHandles, - rotate: this._rotateHandles - }; - }, - - _appendHandlesandDragable: function (mode) { - var overlay = this._overlay, - map = overlay._map; - - map.addLayer(this._handles[mode]); - - if (mode !== 'lock') { - if (!this._selected) { - this._handles[mode].eachLayer(function (layer) { - layer.setOpacity(0); - layer.dragging.disable(); - layer.options.draggable = false; - }); - } - - this._enableDragging(); - } - }, - - - _initToolbar: function () { - this._showToolbar(); - }, - - confirmDelete: function() { - return window.confirm("Are you sure you want to delete?"); - }, - - _rotateBy: function(angle) { - var overlay = this._overlay, - map = overlay._map, - center = map.latLngToLayerPoint(overlay.getCenter()), - i, - p, - q; - - for (i = 0; i < 4; i++) { - p = map.latLngToLayerPoint(overlay._corners[i]).subtract(center); - q = L.point( - Math.cos(angle) * p.x - Math.sin(angle) * p.y, - Math.sin(angle) * p.x + Math.cos(angle) * p.y - ); - overlay._corners[i] = map.layerPointToLatLng(q.add(center)); - } - - overlay._reset(); - }, - - _scaleBy: function(scale) { - var overlay = this._overlay, - map = overlay._map, - center = map.latLngToLayerPoint(overlay.getCenter()), - i, - p; - - for (i = 0; i < 4; i++) { - p = map - .latLngToLayerPoint(overlay._corners[i]) - .subtract(center) - .multiplyBy(scale) - .add(center); - overlay._corners[i] = map.layerPointToLatLng(p); - } - - overlay._reset(); - }, - - _enableDragging: function() { - var overlay = this._overlay, - map = overlay._map; - - this.dragging = new L.Draggable(overlay._image); - this.dragging.enable(); - - /* Hide toolbars and markers while dragging; click will re-show it */ - this.dragging.on("dragstart", function() { - overlay.fire("dragstart"); - this._hideToolbar(); - },this); - - /* - * Adjust default behavior of L.Draggable. - * By default, L.Draggable overwrites the CSS3 distort transform - * that we want when it calls L.DomUtil.setPosition. - */ - this.dragging._updatePosition = function() { - var delta = this._newPos.subtract( - map.latLngToLayerPoint(overlay._corners[0]) - ), - currentPoint, - i; - - this.fire("predrag"); - - for (i = 0; i < 4; i++) { - currentPoint = map.latLngToLayerPoint(overlay._corners[i]); - overlay._corners[i] = map.layerPointToLatLng(currentPoint.add(delta)); - } - - overlay._reset(); - overlay.fire("update"); - overlay.fire("drag"); - - this.fire("drag"); - }; - }, - - _onKeyDown: function(event) { - var keymap = this.options.keymap, - handlerName = keymap[event.key]; - - if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { - if (this._selected) { - this[handlerName].call(this); - } - } - }, - - _toggleRotateScale: function() { - var map = this._overlay._map; - - if (this._mode === "lock") { return; } - - map.removeLayer(this._handles[this._mode]); - - /* Switch mode. */ - if (this._mode === "rotateScale") { this._mode = "distort"; } - else { this._mode = "rotateScale"; } - - map.addLayer(this._handles[this._mode]); - - this._showToolbar(); - }, - - _toggleScale: function() { - var map = this._overlay._map; - - if (this._mode === "lock") { return; } - - map.removeLayer(this._handles[this._mode]); - - if (this._mode === "scale") { this._mode = "distort"; } - else { this._mode = "scale"; } - - map.addLayer(this._handles[this._mode]); - - }, - - _toggleRotate: function() { - var map = this._overlay._map; - - if (this._mode === "lock") { return; } - - map.removeLayer(this._handles[this._mode]); - if (this._mode === "rotate") { this._mode = "distort"; } - else { this._mode = "rotate"; } - - map.addLayer(this._handles[this._mode]); - }, - - _toggleTransparency: function() { - var image = this._overlay._image, - opacity; - - this._transparent = !this._transparent; - opacity = this._transparent ? this.options.opacity : 1; - - L.DomUtil.setOpacity(image, opacity); - image.setAttribute("opacity", opacity); - }, - - _toggleOutline: function() { - var image = this._overlay._image, - opacity, - outline; - - this._outlined = !this._outlined; - outline = this._outlined ? this.options.outline : "none"; - - L.DomUtil.setOpacity(image, opacity); - image.setAttribute("opacity", opacity); - - image.style.outline = outline; - - this._showToolbar(); - }, - - _sendUp: function() { - this._overlay.bringToFront(); - }, - - _sendDown: function() { - this._overlay.bringToBack(); - }, - - _toggleLock: function() { - var map = this._overlay._map; - - map.removeLayer(this._handles[this._mode]); - /* Switch mode. */ - if (this._mode === "lock") { - this._mode = "distort"; - this._enableDragging(); - } else { - this._mode = "lock"; - if (this.dragging) { this.dragging.disable(); } - delete this.dragging; - } - - map.addLayer(this._handles[this._mode]); - - this._showToolbar(); - }, - - _select: function(event) { - this._selected = true; - this._showToolbar(); - this._showMarkers(); - - if (event) { L.DomEvent.stopPropagation(event); } - }, - - _deselect: function() { - this._selected = false; - this._hideToolbar(); - if (this._mode !== "lock") { - this._hideMarkers(); - } - }, - - _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) { - var drag = layer.dragging, - opts = layer.options; - - layer.setOpacity(1); - if (drag) { drag.enable(); } - if (opts.draggable) { opts.draggable = true; } - }); - }, - - _hideMarkers: function() { - if (!this._handles) { this._initHandles(); } // workaround for race condition w/ feature group - - var mode = this._mode, - currentHandle = this._handles[mode]; - - currentHandle.eachLayer(function (layer) { - var drag = layer.dragging, - opts = layer.options; - - if (mode !== 'lock') { - 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(); - raised_point.lat = maxLat; - - if (this._overlay.options.suppressToolbar !== true) { - try { - this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); - overlay.fire('toolbar:created'); - } - catch (e) {} - } - }, - - _removeOverlay: function () { - var overlay = this._overlay, - eventParents = overlay._eventParents; - - if (this._mode === "lock") { return; } - - var choice = this.confirmDelete(); - if (!choice) { return; } - - this._hideToolbar(); - if (eventParents) { - var eP = eventParents[Object.keys(eventParents)[0]]; - eP.removeLayer(overlay); - } else { - overlay._map.removeLayer(overlay); - } - }, - - // compare this to using overlay zIndex - _toggleOrder: function () { - if (this._toggledImage) { - this._toggledImage = false; - this._overlay.bringToFront(); - } else { - this._toggledImage = true; - this._overlay.bringToBack(); - } - - this._showToolbar(); - }, - - // Based on https://github.com/publiclab/mapknitter/blob/8d94132c81b3040ae0d0b4627e685ff75275b416/app/assets/javascripts/mapknitter/Map.js#L47-L82 - _toggleExport: function() { - var map = this._overlay._map; - var overlay = this._overlay; - - // make a new image - var downloadable = new Image(); - - downloadable.id = downloadable.id || "tempId12345"; - $("body").append(downloadable); - - downloadable.onload = function onLoadDownloadableImage() { - var height = downloadable.height, - width = downloadable.width, - nw = map.latLngToLayerPoint(overlay._corners[0]), - ne = map.latLngToLayerPoint(overlay._corners[1]), - sw = map.latLngToLayerPoint(overlay._corners[2]), - se = map.latLngToLayerPoint(overlay._corners[3]); - - // I think this is to move the image to the upper left corner, - // jywarren: i think we may need these or the image goes off the edge of the canvas - // jywarren: but these seem to break the distortion math... - - // jywarren: i think it should be rejiggered so it - // finds the most negative values of x and y and then - // adds those to all coordinates - - //nw.x -= nw.x; - //ne.x -= nw.x; - //se.x -= nw.x; - //sw.x -= nw.x; - - //nw.y -= nw.y; - //ne.y -= nw.y; - //se.y -= nw.y; - //sw.y -= nw.y; - - // run once warping is complete - downloadable.onload = function() { - $(downloadable).remove(); - }; - - if (window && window.hasOwnProperty("warpWebGl")) { - warpWebGl( - downloadable.id, - [0, 0, width, 0, width, height, 0, height], - [nw.x, nw.y, ne.x, ne.y, se.x, se.y, sw.x, sw.y], - true // trigger download - ); - } - }; - - downloadable.src = overlay.options.fullResolutionSrc || overlay._image.src; - }, - - toggleIsolate: function() { - // this.isolated = !this.isolated; - // if (this.isolated) { - // $.each($L.images,function(i,img) { - // img.hidden = false; - // img.setOpacity(1); - // }); - // } else { - // $.each($L.images,function(i,img) { - // img.hidden = true; - // img.setOpacity(0); - // }); - // } - // this.hidden = false; - // this.setOpacity(1); - } -}); - -L.DistortableImageOverlay.addInitHook(function() { - this.editing = new L.DistortableImage.Edit(this); - - if (this.options.editable) { - L.DomEvent.on(this._image, "load", this.editing.enable, this.editing); - } - - this.on('remove', function () { - if (this.editing) { this.editing.disable(); } - }); -}); - +L.DomUtil = L.extend(L.DomUtil, { + getMatrixString: function(m) { + var is3d = L.Browser.webkit3d || L.Browser.gecko3d || L.Browser.ie3d, + + /* + * Since matrix3d takes a 4*4 matrix, we add in an empty row and column, which act as the identity on the z-axis. + * See: + * http://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ + * https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function#M.C3.B6bius'_homogeneous_coordinates_in_projective_geometry + */ + matrix = [ + m[0], m[3], 0, m[6], + m[1], m[4], 0, m[7], + 0, 0, 1, 0, + m[2], m[5], 0, m[8] + ], + + str = is3d ? 'matrix3d(' + matrix.join(',') + ')' : ''; + + if (!is3d) { + console.log('Your browser must support 3D CSS transforms in order to use DistortableImageOverlay.'); + } + + return str; + }, + + getRotateString: function(angle, units) { + var is3d = L.Browser.webkit3d || L.Browser.gecko3d || L.Browser.ie3d, + open = 'rotate' + (is3d ? '3d' : '') + '(', + rotateString = (is3d ? '0, 0, 1, ' : '') + angle + units; + + return open + rotateString + ')'; + }, + + toggleClass: function(el, className) { + var c = className; + return this.hasClass(el, c) ? this.removeClass(el, c) : this.addClass(el, c); + } + +}); + +L.ImageUtil = { + + getCmPerPixel: function(overlay) { + var map = overlay._map; + + var dist = map + .latLngToLayerPoint(overlay.getCorner(0)) + .distanceTo(map.latLngToLayerPoint(overlay.getCorner(1))); + + return (dist * 100) / overlay._image.width; + } + +}; +L.Map.include({ + _newLayerPointToLatLng: function(point, newZoom, newCenter) { + var topLeft = L.Map.prototype._getNewTopLeftPoint.call(this, newCenter, newZoom) + .add(L.Map.prototype._getMapPanePos.call(this)); + return this.unproject(point.add(topLeft), newZoom); + } +}); +L.MatrixUtil = { + + // Compute the adjugate of m + adj: function(m) { + return [ + m[4]*m[8]-m[5]*m[7], m[2]*m[7]-m[1]*m[8], m[1]*m[5]-m[2]*m[4], + m[5]*m[6]-m[3]*m[8], m[0]*m[8]-m[2]*m[6], m[2]*m[3]-m[0]*m[5], + m[3]*m[7]-m[4]*m[6], m[1]*m[6]-m[0]*m[7], m[0]*m[4]-m[1]*m[3] + ]; + }, + + // multiply two 3*3 matrices + multmm: function(a, b) { + var c = [], + i; + + for (i = 0; i < 3; i++) { + for (var j = 0; j < 3; j++) { + var cij = 0; + for (var k = 0; k < 3; k++) { + cij += a[3*i + k]*b[3*k + j]; + } + c[3*i + j] = cij; + } + } + return c; + }, + + // multiply a 3*3 matrix and a 3-vector + multmv: function(m, v) { + return [ + m[0]*v[0] + m[1]*v[1] + m[2]*v[2], + m[3]*v[0] + m[4]*v[1] + m[5]*v[2], + m[6]*v[0] + m[7]*v[1] + m[8]*v[2] + ]; + }, + + // multiply a scalar and a 3*3 matrix + multsm: function(s, m) { + var matrix = []; + + for (var i = 0, l = m.length; i < l; i++) { + matrix.push(s*m[i]); + } + + return matrix; + }, + + basisToPoints: function(x1, y1, x2, y2, x3, y3, x4, y4) { + var m = [ + x1, x2, x3, + y1, y2, y3, + 1, 1, 1 + ], + v = L.MatrixUtil.multmv(L.MatrixUtil.adj(m), [x4, y4, 1]); + + return L.MatrixUtil.multmm(m, [ + v[0], 0, 0, + 0, v[1], 0, + 0, 0, v[2] + ]); + }, + + + project: function(m, x, y) { + var v = L.MatrixUtil.multmv(m, [x, y, 1]); + return [v[0]/v[2], v[1]/v[2]]; + }, + + general2DProjection: function( + x1s, y1s, x1d, y1d, + x2s, y2s, x2d, y2d, + x3s, y3s, x3d, y3d, + x4s, y4s, x4d, y4d + ) { + var s = L.MatrixUtil.basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s), + d = L.MatrixUtil.basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d), + m = L.MatrixUtil.multmm(d, L.MatrixUtil.adj(s)); + + /* + * Normalize to the unique matrix with m[8] == 1. + * See: http://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/ + */ + return L.MatrixUtil.multsm(1/m[8], m); + } +}; +L.TrigUtil = { + + calcAngleDegrees: function(x, y) { + var pointAngle = Math.atan2(y, x); + return this.radiansToDegrees(pointAngle); + }, + + radiansToDegrees: function(angle) { + return angle * 180 / Math.PI; + }, + + degreesToRadians: function(angle) { + return angle * Math.PI / 180; + } + +}; +L.DistortableImageOverlay = L.ImageOverlay.extend({ + + 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 = 0; + // window.rotation = this.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 = 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(L.point(offset.x, -offset.y)) + ), + map.containerPointToLatLng( + center.add(L.point(-offset.x, offset.y)) + ), + map.containerPointToLatLng(center.add(offset)) + ]; + } + this._initialDimensions = { 'height': imageHeight, 'width': imageWidth, 'offset': 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.DistortableCollection = L.FeatureGroup.extend({ + 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); + + 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() { + 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.getElement() !== event.target) { + edit._deselect(); + } else { + this._toggleMultiSelect(event, edit); + } + }, this); + + L.DomEvent.stopPropagation(event); + }, + + _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.getCorner(i)) && edit._mode !== "lock") { + L.DomUtil.addClass(layer.getElement(), "selected"); + break; + } + } + }); + }, + + _getAvgCmPerPixel: function(imgs) { + var reduce = imgs.reduce(function(sum, img) { + return sum + img.cm_per_pixel; + }, 0); + return reduce / imgs.length; + }, + + _generateExportJson: function() { + var json = {}; + json.images = []; + + this.eachLayer(function(layer) { + if (this.isSelected(layer)) { + json.images.push({ + id: this.getLayerId(layer), + src: layer._image.src, + nodes: layer.getCorners(), + cm_per_pixel: L.ImageUtil.getCmPerPixel(layer) + }); + } + }, this); + + json.avg_cm_per_pixel = this._getAvgCmPerPixel(json.images); + + return json; + }, + + _runExport: function(collection) { + collection = collection || this._generateExportJson(); + $.ajax({ + url: "http://export.mapknitter.org/export", + crossDomain: true, + type: "POST", + data: { + collection: JSON.stringify(collection.images), + scale: 30 + }, + success: function _getStatusJson(data) { + console.log(data); + $.ajax("http://export.mapknitter.org" + data, { + type: "GET", + crossDomain: true + }).done(function(data) { + console.log(data); + }); + } + }); + }, + + _onKeyDown: function(e) { + if (e.key === "Escape") { + this._deselectAll(e); + } + if (e.key === "Backspace") { + this._removeFromGroup(e); + } + }, + + _dragStartMultiple: function(event) { + var overlay = event.target, + i; + + if (!this.isSelected(overlay)) { return; } + + this.eachLayer(function(layer) { + var edit = layer.editing; + edit._deselect(); + + for (i = 0; i < 4; i++) { + layer._dragStartPoints[i] = layer._map.latLngToLayerPoint( + layer.getCorner(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.getCorner(i)); + } + + 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); + }, + /** + * 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.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; + + /* 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) { + var markerOptions, + latlng = overlay.getCorner(corner); + + L.setOptions(this, options); + + this._handled = overlay; + this._corner = corner; + + markerOptions = { + draggable: true, + zIndexOffset: 10 + }; + + if (options && options.hasOwnProperty("draggable")) { + markerOptions.draggable = options.draggable; + } + + L.Marker.prototype.initialize.call(this, latlng, markerOptions); + }, + + onAdd: function(map) { + L.Marker.prototype.onAdd.call(this, map); + this._bindListeners(); + + this.updateHandle(); + }, + + onRemove: function(map) { + this._unbindListeners(); + L.Marker.prototype.onRemove.call(this, map); + }, + + _onHandleDragStart: function() { + this._handled.fire("editstart"); + }, + + _onHandleDragEnd: function() { + this._fireEdit(); + }, + + _fireEdit: function() { + this._handled.edited = true; + this._handled.fire("edit"); + }, + + _bindListeners: function() { + this.on( + { + dragstart: this._onHandleDragStart, + drag: this._onHandleDrag, + dragend: this._onHandleDragEnd + }, + this + ); + + this._handled._map.on("zoomend", this.updateHandle, this); + + this._handled.on("update", this.updateHandle, this); + }, + + _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); + }, + + /* Takes two latlngs and calculates the scaling difference. */ + _calculateScalingFactor: function(latlngA, latlngB) { + var overlay = this._handled, + map = overlay._map, + + centerPoint = map.latLngToLayerPoint(overlay.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); + }, + + /* Takes two latlngs and calculates the angle between them. */ + calculateAngleDelta: function(latlngA, latlngB) { + var overlay = this._handled, + map = overlay._map, + + centerPoint = map.latLngToLayerPoint(overlay.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; + } +}); + +L.LockHandle = L.EditHandle.extend({ + options: { + TYPE: 'lock', + icon: 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.getCorner(this._corner)); + L.DomUtil.removeClass(this._handled.getElement(), 'selected'); + } + +}); + +L.DistortHandle = L.EditHandle.extend({ + options: { + TYPE: "distort", + icon: L.icon({ + iconUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAklEQVR4AewaftIAAAChSURBVO3BIU4DURgGwNkvL2B6AkQTLBqP4QCoSm7DDXoBLBZHDbfgICAIZjEV3YTn9uVHdMZZtcnCfI13bIzxg0emg6Nm6QVbYz3jylEsXRrvwommb49X67jFkz80fR9Mb1YxTzqiWBSLYlEsikWxKBbFolgUi2JRLIpFsSgWxaJY03fHHOu40dH07bAzWCx9Ge/TiWbpHgdsjPGNB2f/yS+7xRCyiiZPJQAAAABJRU5ErkJggg==", + iconSize: [32, 32], + iconAnchor: [16, 16] + }) + }, + + _onHandleDrag: function() { + var overlay = this._handled; + + overlay._updateCorner(this._corner, this.getLatLng()); + + overlay.fire("update"); + overlay.editing._updateToolbarPos(); + }, + + updateHandle: function() { + this.setLatLng(this._handled.getCorner(this._corner)); + }, + +}); + +L.RotateScaleHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotateScale', + icon: 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, + edit = overlay.editing, + formerLatLng = overlay.getCorner(this._corner), + newLatLng = this.getLatLng(), + + angle = this.calculateAngleDelta(formerLatLng, newLatLng), + scale = this._calculateScalingFactor(formerLatLng, newLatLng); + + if (angle !== 0) { edit._rotateBy(angle); } + + /* + 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 (overlay.hasOwnProperty('edgeMinWidth')){ + var edgeMinWidth = overlay.edgeMinWidth, + w = L.latLng(overlay.getCorner(0)).distanceTo(overlay.getCorner(1)), + h = L.latLng(overlay.getCorner(1)).distanceTo(overlay.getCorner(2)); + if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { + edit._scaleBy(scale); + } + } + + overlay.fire('update'); + edit._updateToolbarPos(); + }, + + updateHandle: function() { + this.setLatLng(this._handled.getCorner(this._corner)); + }, +}); + +L.RotateHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: 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 = overlay.getCorner(this._corner), + newLatLng = this.getLatLng(), + angle = this.calculateAngleDelta(formerLatLng, newLatLng); + + if (angle !== 0) { overlay.editing._rotateBy(angle); } + + overlay.fire('update'); + overlay.editing._updateToolbarPos(); + }, + + updateHandle: function() { + this.setLatLng(this._handled.getCorner(this._corner)); + } + +}); + +L.ScaleHandle = L.EditHandle.extend({ + options: { + TYPE: 'rotate', + icon: L.icon({ + iconUrl:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0NTkiIGhlaWdodD0iNDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIiBzdHlsZT0iIj48cmVjdCBpZD0iYmFja2dyb3VuZHJlY3QiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHg9IjAiIHk9IjAiIGZpbGw9Im5vbmUiIHN0cm9rZT0ibm9uZSIgY2xhc3M9IiIgc3R5bGU9IiIvPjxnIGNsYXNzPSJjdXJyZW50TGF5ZXIiIHN0eWxlPSIiPjx0aXRsZT5MYXllciAxPC90aXRsZT48cGF0aCBkPSJNNDU5LjA0OTE1OTUzMDQ3MTM0LDg2LjkyNjIzNDUxMjU1MDAyIFYwIGgtODUuNzE0NTczMzU2MzEyMDkgdjI3LjA0MzcxNzQwMzkwNDQ1MiBIODUuNzE0NTczMzU2MzEyMDMgVjAgSDAgdjg2LjkyNjIzNDUxMjU1MDAyIGgyNS43MTQzNzIwMDY4OTM2MjYgdjI4OS43NTQxMTUwNDE4MzM0IEgwIHY4Ni45MjYyMzQ1MTI1NTAwMiBoODUuNzE0NTczMzU2MzEyMDkgdi0yNy4wNDM3MTc0MDM5MDQ0NTIgaDI4NS43MTUyNDQ1MjEwNDAzIHYyNy4wNDM3MTc0MDM5MDQ0NTIgaDg1LjcxNDU3MzM1NjMxMjA5IHYtODYuOTI2MjM0NTEyNTUwMDIgaC0yMy44MDk2MDM3MTAwODY2OSBWODYuOTI2MjM0NTEyNTUwMDIgSDQ1OS4wNDkxNTk1MzA0NzEzNCB6TTM4NC43NjMxOTU5NTUwMDA5LDEyLjU1NjAxMTY1MTgxMjc4MSBoNjEuOTA0OTY5NjQ2MjI1Mzk2IHY2Mi43ODAwNTgyNTkwNjM5MSBoLTYxLjkwNDk2OTY0NjIyNTM5NiBWMTIuNTU2MDExNjUxODEyNzgxIHpNMTIuMzgwOTkzOTI5MjQ1MDUsMTIuNTU2MDExNjUxODEyNzgxIGg2MS45MDQ5Njk2NDYyMjUzOTYgdjYyLjc4MDA1ODI1OTA2MzkxIEgxMi4zODA5OTM5MjkyNDUwNSBWMTIuNTU2MDExNjUxODEyNzgxIHpNNzQuMjg1OTYzNTc1NDcwNTMsNDUxLjA1MDU3MjQxNTEyMDY2IEgxMi4zODA5OTM5MjkyNDUwNSB2LTYyLjc4MDA1ODI1OTA2MzkxIGg2MS45MDQ5Njk2NDYyMjUzOTYgVjQ1MS4wNTA1NzI0MTUxMjA2NiB6TTQ0NS43MTU3ODE0NTI4MjI3NCw0NTEuMDUwNTcyNDE1MTIwNjYgaC02Mi44NTczNTM3OTQ2Mjg4NjQgdi02Mi43ODAwNTgyNTkwNjM5MSBoNjIuODU3MzUzNzk0NjI4ODY0IFY0NTEuMDUwNTcyNDE1MTIwNjYgek00MDcuNjIwNDE1NTE2Njg0MjYsMzc2LjY4MDM0OTU1NDM4MzQ0IGgtMzYuMTkwNTk3NjM5MzMxNzcgdjMyLjgzODc5OTcwNDc0MTEyIEg4NS43MTQ1NzMzNTYzMTIwMyB2LTMyLjgzODc5OTcwNDc0MTEyIEg0OS41MjM5NzU3MTY5ODAzMiBWODYuOTI2MjM0NTEyNTUwMDIgaDM2LjE5MDU5NzYzOTMzMTc3IFY1MC4yMjQwNDY2MDcyNTExMjUgaDI4Ny42MjAwMTI4MTc4NDcyIHYzNi43MDIxODc5MDUyOTg5IGgzNC4yODU4MjkzNDI1MjQ4MzUgVjM3Ni42ODAzNDk1NTQzODM0NCB6IiBpZD0ic3ZnXzIiIGNsYXNzPSIiIGZpbGw9IiMxYTFhZWIiIGZpbGwtb3BhY2l0eT0iMSIvPjwvZz48L3N2Zz4=', + iconSize: [32, 32], + iconAnchor: [16, 16] + }) + }, + + _onHandleDrag: function() { + var overlay = this._handled, + formerLatLng = overlay.getCorner(this._corner), + newLatLng = this.getLatLng(), + + scale = this._calculateScalingFactor(formerLatLng, newLatLng); + + overlay.editing._scaleBy(scale); + + overlay.fire('update'); + overlay.editing._updateToolbarPos(); + }, + + updateHandle: function() { + this.setLatLng(this._handled.getCorner(this._corner)); + }, +}); + +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({ + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._transparent) { + href = ''; + tooltip = 'Make Image Opaque'; + } else { + href = ''; + tooltip = 'Make Image Transparent'; + } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleTransparency(); + this.disable(); + } + }), + + ToggleOutline = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._outlined) { + href = ''; + tooltip = 'Remove Border'; + } else { + href = ''; + tooltip = 'Add Border'; + } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleOutline(); + this.disable(); + } + }), + + Delete = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: 'Delete Image' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._removeOverlay(); + this.disable(); + } + }), + + ToggleLock = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._mode === 'lock') { + href = ''; + tooltip = 'Unlock'; + } else { + href = ''; + tooltip = 'Lock'; + } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleLock(); + this.disable(); + } + }), + + ToggleRotateScale = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._mode === 'rotateScale') { + href = ''; + tooltip = 'Distort'; + } else { + href = ''; + tooltip = 'Rotate+Scale'; + } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleRotateScale(); + this.disable(); + } + }), + + Export = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: 'Export Image' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleExport(); + this.disable(); + } + }), + + ToggleOrder = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._toggledImage) { + href = ''; + tooltip = 'Stack to Front'; + } else { + href = ''; + tooltip = 'Stack to Back'; + } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._toggleOrder(); + this.disable(); + } + }), + + EnableEXIF = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: 'Geolocate Image' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var image = this._overlay.getElement(); + + EXIF.getData(image, L.EXIF(image)); + } + }), + + Restore = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: 'Restore' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._restore(); + this.disable(); + } + }); + +L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ + options: { + actions: [ + ToggleTransparency, + ToggleOutline, + ToggleLock, + ToggleRotateScale, + ToggleOrder, + EnableEXIF, + Restore, + Export, + Delete + ] + }, + + // 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)"; + } + } +}); + +L.DistortableImage = L.DistortableImage || {}; + +L.DistortableImage.Edit = L.Handler.extend({ + options: { + opacity: 0.7, + outline: "1px solid red", + keymap: { + 'Backspace': '_removeOverlay', // backspace windows / delete mac + 'CapsLock': '_toggleRotate', + 'Escape': '_deselect', + 'd': '_toggleRotateScale', + 'r': '_toggleRotateScale', + 'j': '_toggleOrder', + 'k': '_toggleOrder', + 'l': '_toggleLock', + 'o': '_toggleOutline', + 's': '_toggleScale', + 't': '_toggleTransparency', + } + }, + + initialize: function(overlay) { + this._overlay = overlay; + this._toggledImage = false; + /* Different actions. */ + var actions = ["distort", "lock", "rotate", "scale", "rotateScale"]; + /* Interaction modes. */ + this._mode = actions[actions.indexOf(this._overlay.options.mode)] || "distort"; + 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, + 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(); + + this._appendHandlesandDragable(this._mode); + + if (this._selected) { this._initToolbar(); } + + this._overlay._dragStartPoints = { + 0: L.point(0, 0), + 1: L.point(0, 0), + 2: L.point(0, 0), + 3: 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); + }, + + /* 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); + }, + + _initHandles: function() { + var overlay = this._overlay, + i; + + this._lockHandles = L.layerGroup(); + for (i = 0; i < 4; i++) { + this._lockHandles.addLayer( + new L.LockHandle(overlay, i, { draggable: false }) + ); + } + + this._distortHandles = L.layerGroup(); + for (i = 0; i < 4; i++) { + this._distortHandles.addLayer(new L.DistortHandle(overlay, i)); + } + + this._rotateHandles = L.layerGroup(); // individual rotate + for (i = 0; i < 4; i++) { + this._rotateHandles.addLayer(new L.RotateHandle(overlay, i)); + } + + this._scaleHandles = L.layerGroup(); + for (i = 0; i < 4; i++) { + this._scaleHandles.addLayer(new L.ScaleHandle(overlay, i)); + } + + this._rotateScaleHandles = L.layerGroup(); // handle includes rotate AND scale + for (i = 0; i < 4; i++) { + this._rotateScaleHandles.addLayer(new L.RotateScaleHandle(overlay, i)); + } + + this._handles = { + lock: this._lockHandles, + distort: this._distortHandles, + rotateScale: this._rotateScaleHandles, + scale: this._scaleHandles, + rotate: this._rotateHandles + }; + }, + + _appendHandlesandDragable: function (mode) { + var overlay = this._overlay, + map = overlay._map; + + map.addLayer(this._handles[mode]); + + if (mode !== 'lock') { + if (!this._selected) { + this._handles[mode].eachLayer(function (layer) { + layer.setOpacity(0); + layer.dragging.disable(); + layer.options.draggable = false; + }); + } + + this._enableDragging(); + } + }, + + + _initToolbar: function () { + this._showToolbar(); + }, + + confirmDelete: function() { + return window.confirm("Are you sure you want to delete?"); + }, + + _rotateBy: function(angle) { + var overlay = this._overlay, + map = overlay._map, + center = map.latLngToLayerPoint(overlay.getCenter()), + i, + p, + q; + + for (i = 0; i < 4; i++) { + p = map.latLngToLayerPoint(overlay._corners[i]).subtract(center); + q = L.point( + Math.cos(angle) * p.x - Math.sin(angle) * p.y, + Math.sin(angle) * p.x + Math.cos(angle) * p.y + ); + overlay._updateCorner(i, map.layerPointToLatLng(q.add(center))); + } + + // window.angle = L.TrigUtil.radiansToDegrees(angle); + + this._overlay.rotation -= L.TrigUtil.radiansToDegrees(angle); + + overlay._reset(); + }, + + _restore: function() { + var overlay = this._overlay; + var angle = overlay.rotation; + var map = overlay._map; + var center = map.latLngToLayerPoint(overlay.getCenter()); + var offset = overlay._initialDimensions.offset; + + var corners = { + 0: map.layerPointToLatLng(center.subtract(offset)), + 1: map.layerPointToLatLng(center.add(L.point(offset.x, -offset.y))), + 2: map.layerPointToLatLng(center.add(L.point(-offset.x, offset.y))), + 3: map.layerPointToLatLng(center.add(offset)) + }; + + map.removeLayer(this._handles[this._mode]); + + overlay._updateCorners(corners); + + if (angle !== 0) { this._rotateBy(L.TrigUtil.degreesToRadians(360 - angle)); } + + map.addLayer(this._handles[this._mode]); + + this._showToolbar(); + + this._overlay.rotation = angle; + }, + + _scaleBy: function(scale) { + var overlay = this._overlay, + map = overlay._map, + center = map.latLngToLayerPoint(overlay.getCenter()), + i, + p; + + for (i = 0; i < 4; i++) { + p = map + .latLngToLayerPoint(overlay.getCorner(i)) + .subtract(center) + .multiplyBy(scale) + .add(center); + overlay._updateCorner(i, map.layerPointToLatLng(p)); + } + + overlay._reset(); + }, + + _enableDragging: function() { + var overlay = this._overlay, + map = overlay._map; + + this.dragging = new L.Draggable(overlay._image); + this.dragging.enable(); + + /* Hide toolbars and markers while dragging; click will re-show it */ + this.dragging.on("dragstart", function() { + overlay.fire("dragstart"); + this._hideToolbar(); + },this); + + /* + * Adjust default behavior of L.Draggable. + * By default, L.Draggable overwrites the CSS3 distort transform + * that we want when it calls L.DomUtil.setPosition. + */ + this.dragging._updatePosition = function() { + var delta = this._newPos.subtract( + map.latLngToLayerPoint(overlay._corners[0]) + ), + currentPoint, + i; + + this.fire("predrag"); + + for (i = 0; i < 4; i++) { + currentPoint = map.latLngToLayerPoint(overlay._corners[i]); + overlay._corners[i] = map.layerPointToLatLng(currentPoint.add(delta)); + } + + overlay._reset(); + overlay.fire("update"); + overlay.fire("drag"); + + this.fire("drag"); + }; + }, + + _onKeyDown: function(event) { + var keymap = this.options.keymap, + handlerName = keymap[event.key]; + + if (this[handlerName] !== undefined && this._overlay.options.suppressToolbar !== true) { + if (this._selected) { + this[handlerName].call(this); + } + } + }, + + _toggleRotateScale: function() { + var map = this._overlay._map; + + if (this._mode === "lock") { return; } + + map.removeLayer(this._handles[this._mode]); + + /* Switch mode. */ + if (this._mode === "rotateScale") { this._mode = "distort"; } + else { this._mode = "rotateScale"; } + + map.addLayer(this._handles[this._mode]); + + this._showToolbar(); + }, + + _toggleScale: function() { + var map = this._overlay._map; + + if (this._mode === "lock") { return; } + + map.removeLayer(this._handles[this._mode]); + + if (this._mode === "scale") { this._mode = "distort"; } + else { this._mode = "scale"; } + + map.addLayer(this._handles[this._mode]); + + }, + + _toggleRotate: function() { + var map = this._overlay._map; + + if (this._mode === "lock") { return; } + + map.removeLayer(this._handles[this._mode]); + if (this._mode === "rotate") { this._mode = "distort"; } + else { this._mode = "rotate"; } + + map.addLayer(this._handles[this._mode]); + }, + + _toggleTransparency: function() { + var image = this._overlay._image, + opacity; + + this._transparent = !this._transparent; + opacity = this._transparent ? this.options.opacity : 1; + + L.DomUtil.setOpacity(image, opacity); + image.setAttribute("opacity", opacity); + + this._showToolbar(); + }, + + _toggleOutline: function() { + var image = this._overlay._image, + opacity, + outline; + + this._outlined = !this._outlined; + outline = this._outlined ? this.options.outline : "none"; + + L.DomUtil.setOpacity(image, opacity); + image.setAttribute("opacity", opacity); + + image.style.outline = outline; + + this._showToolbar(); + }, + + _sendUp: function() { + this._overlay.bringToFront(); + }, + + _sendDown: function() { + this._overlay.bringToBack(); + }, + + _toggleLock: function() { + var map = this._overlay._map; + + map.removeLayer(this._handles[this._mode]); + /* Switch mode. */ + if (this._mode === "lock") { + this._mode = "distort"; + this._enableDragging(); + } else { + this._mode = "lock"; + if (this.dragging) { this.dragging.disable(); } + delete this.dragging; + } + + map.addLayer(this._handles[this._mode]); + + this._showToolbar(); + }, + + _select: function(event) { + this._selected = true; + this._showToolbar(); + this._showMarkers(); + + if (event) { L.DomEvent.stopPropagation(event); } + }, + + _deselect: function() { + this._selected = false; + this._hideToolbar(); + if (this._mode !== "lock") { + this._hideMarkers(); + } + }, + + _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) { + var drag = layer.dragging, + opts = layer.options; + + layer.setOpacity(1); + if (drag) { drag.enable(); } + if (opts.draggable) { opts.draggable = true; } + }); + }, + + _hideMarkers: function() { + if (!this._handles) { this._initHandles(); } // workaround for race condition w/ feature group + + var mode = this._mode, + currentHandle = this._handles[mode]; + + currentHandle.eachLayer(function (layer) { + var drag = layer.dragging, + opts = layer.options; + + if (mode !== 'lock') { + 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. + corners = overlay.getCorners(), + 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(); + raised_point.lat = maxLat; + + if (overlay.options.suppressToolbar !== true) { + try { + this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); + overlay.fire('toolbar:created'); + } + catch (e) {} + } + }, + + _updateToolbarPos: function() { + var overlay = this._overlay, + //Find the topmost point on the image. + corners = overlay.getCorners(), + 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(); + raised_point.lat = maxLat; + + if (overlay.options.suppressToolbar !== true) { + this.toolbar.setLatLng(raised_point); + } + + }, + + _removeOverlay: function () { + var overlay = this._overlay, + eventParents = overlay._eventParents; + + if (this._mode === "lock") { return; } + + var choice = this.confirmDelete(); + if (!choice) { return; } + + this._hideToolbar(); + if (eventParents) { + var eP = eventParents[Object.keys(eventParents)[0]]; + eP.removeLayer(overlay); + } else { + overlay._map.removeLayer(overlay); + } + }, + + // compare this to using overlay zIndex + _toggleOrder: function () { + if (this._toggledImage) { + this._toggledImage = false; + this._overlay.bringToFront(); + } else { + this._toggledImage = true; + this._overlay.bringToBack(); + } + + this._showToolbar(); + }, + + // Based on https://github.com/publiclab/mapknitter/blob/8d94132c81b3040ae0d0b4627e685ff75275b416/app/assets/javascripts/mapknitter/Map.js#L47-L82 + _toggleExport: function() { + var map = this._overlay._map; + var overlay = this._overlay; + + // make a new image + var downloadable = new Image(); + + downloadable.id = downloadable.id || "tempId12345"; + $("body").append(downloadable); + + downloadable.onload = function onLoadDownloadableImage() { + var height = downloadable.height, + width = downloadable.width, + nw = map.latLngToLayerPoint(overlay.getCorner(0)), + ne = map.latLngToLayerPoint(overlay.getCorner(1)), + sw = map.latLngToLayerPoint(overlay.getCorner(2)), + se = map.latLngToLayerPoint(overlay.getCorner(3)); + + // I think this is to move the image to the upper left corner, + // jywarren: i think we may need these or the image goes off the edge of the canvas + // jywarren: but these seem to break the distortion math... + + // jywarren: i think it should be rejiggered so it + // finds the most negative values of x and y and then + // adds those to all coordinates + + //nw.x -= nw.x; + //ne.x -= nw.x; + //se.x -= nw.x; + //sw.x -= nw.x; + + //nw.y -= nw.y; + //ne.y -= nw.y; + //se.y -= nw.y; + //sw.y -= nw.y; + + // run once warping is complete + downloadable.onload = function() { + $(downloadable).remove(); + }; + + if (window && window.hasOwnProperty("warpWebGl")) { + warpWebGl( + downloadable.id, + [0, 0, width, 0, width, height, 0, height], + [nw.x, nw.y, ne.x, ne.y, se.x, se.y, sw.x, sw.y], + true // trigger download + ); + } + }; + + downloadable.src = overlay.options.fullResolutionSrc || overlay._image.src; + }, + + toggleIsolate: function() { + // this.isolated = !this.isolated; + // if (this.isolated) { + // $.each($L.images,function(i,img) { + // img.hidden = false; + // img.setOpacity(1); + // }); + // } else { + // $.each($L.images,function(i,img) { + // img.hidden = true; + // img.setOpacity(0); + // }); + // } + // this.hidden = false; + // this.setOpacity(1); + } +}); + +L.DistortableImageOverlay.addInitHook(function() { + this.editing = new L.DistortableImage.Edit(this); + + if (this.options.editable) { + L.DomEvent.on(this._image, "load", this.editing.enable, this.editing); + } + + this.on('remove', function () { + if (this.editing) { this.editing.disable(); } + }); +}); + L.DomUtil = L.DomUtil || {}; L.DistortableImage = L.DistortableImage || {}; @@ -1851,96 +1982,96 @@ L.DistortableImage.Keymapper = L.Control.extend({ ""; return el_wrapper; } -}); -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 = 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.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 = 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/examples/index.html b/examples/index.html index a42f617de..66b235f05 100644 --- a/examples/index.html +++ b/examples/index.html @@ -1,10 +1,10 @@ - + Leaflet.DistortableImage Example - - - + + + @@ -17,27 +17,21 @@ - - + + + + + + +
-
+
diff --git a/examples/listeners.html b/examples/listeners.html index 9e87f4605..226973220 100644 --- a/examples/listeners.html +++ b/examples/listeners.html @@ -1,15 +1,15 @@ - + Leaflet.DistortableImage Example - - - + + + - + @@ -18,6 +18,10 @@ + + + + diff --git a/examples/select.html b/examples/select.html index 1362a422f..b803442d2 100644 --- a/examples/select.html +++ b/examples/select.html @@ -1,10 +1,10 @@ - + Leaflet.DistortableImage Example - - - + + + @@ -16,8 +16,15 @@ + + + + + + + @@ -47,10 +54,10 @@ img = L.distortableImageOverlay( 'example.png', { corners: [ + L.latLng(51.52, -0.14), L.latLng(51.52,-0.10), - L.latLng(51.52,-0.14), - L.latLng(51.50,-0.10), - L.latLng(51.50,-0.14) + L.latLng(51.50, -0.14), + L.latLng(51.50,-0.10) ], mode: 'lock' }).addTo(map); @@ -59,10 +66,10 @@ img2 = L.distortableImageOverlay( 'example.png', { corners: [ + L.latLng(51.51, -0.20), L.latLng(51.51,-0.16), - L.latLng(51.51,-0.20), - L.latLng(51.49,-0.17), - L.latLng(51.49,-0.21) + L.latLng(51.49, -0.21), + L.latLng(51.49,-0.17) ], mode: 'rotateScale' }).addTo(map); @@ -70,20 +77,20 @@ img3 = L.distortableImageOverlay( 'example.png', { corners: [ + L.latLng(51.50, -0.13), L.latLng(51.50,-0.09), - L.latLng(51.50,-0.13), + L.latLng(51.48, -0.14), 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.07), L.latLng(51.51,-0.03), - L.latLng(51.51,-0.07), - L.latLng(51.49,-0.04), - L.latLng(51.49,-0.08) + L.latLng(51.49, -0.08), + L.latLng(51.49,-0.04) ], }).addTo(map); diff --git a/package-lock.json b/package-lock.json index 41a0e4ef9..aa5f06312 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,12 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/q": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", + "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -414,6 +420,12 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -672,6 +684,72 @@ } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -693,6 +771,16 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -708,12 +796,38 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=", + "dev": true + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dev": true, + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -860,6 +974,97 @@ "which": "^1.2.9" } }, + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + }, + "dependencies": { + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + } + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, + "css-selector-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.3.0.tgz", + "integrity": "sha1-XxrUPi2O77/cME/NOaUhZklD4+s=", + "dev": true + }, + "css-tree": { + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "css-url-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", + "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", + "dev": true + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "cssmin": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.4.3.tgz", + "integrity": "sha1-yRlAd+Dr2s1pHV9ZAVudgZ840BU=", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + } + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1009,6 +1214,17 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "dev": true, + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -1092,6 +1308,15 @@ "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "dev": true, + "requires": { + "env-variable": "0.0.x" + } + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1193,6 +1418,12 @@ "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, + "env-variable": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", + "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==", + "dev": true + }, "error": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", @@ -1502,6 +1733,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", + "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==", + "dev": true + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -1520,6 +1757,12 @@ "pend": "~1.2.0" } }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==", + "dev": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2619,6 +2862,29 @@ "which": "~1.3.0" } }, + "grunt-svg-sprite": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/grunt-svg-sprite/-/grunt-svg-sprite-1.5.0.tgz", + "integrity": "sha512-E7q84qIYLygO/CRlqH2t5EdsaLdblqNs2JSpY+d1XEyNIlOU0tsUuG/cro36Nd3IGSnouzIJAS4yurpPUHUrRA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "prettysize": "^1.1.0", + "svg-sprite": "^1.5.0" + } + }, + "grunt-svgmin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/grunt-svgmin/-/grunt-svgmin-6.0.0.tgz", + "integrity": "sha512-gSVIzJ9TNUDpsTpZELaAdBkQ5DRMios6SlFKc+S7XwUPFvqx8scCsiYTEiGLeYDEvE9+a2GGsSdJUd5ufIFlGQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "log-symbols": "^2.2.0", + "pretty-bytes": "^5.1.0", + "svgo": "^1.2.0" + } + }, "handlebars": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", @@ -3428,6 +3694,15 @@ "graceful-fs": "^4.1.9" } }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "dev": true, + "requires": { + "colornames": "^1.1.1" + } + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -3517,6 +3792,134 @@ "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, + "lodash._arraymap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz", + "integrity": "sha1-Go/Q9MDfS2HeoHbXF83Jfwo8PmY=", + "dev": true + }, + "lodash._basecallback": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz", + "integrity": "sha1-t7K7Q9whYEJKIczybFfkQ3cqjic=", + "dev": true, + "requires": { + "lodash._baseisequal": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.pairs": "^3.0.0" + } + }, + "lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "dev": true, + "requires": { + "lodash.keys": "^3.0.0" + } + }, + "lodash._baseget": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/lodash._baseget/-/lodash._baseget-3.7.2.tgz", + "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=", + "dev": true + }, + "lodash._baseisequal": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", + "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", + "dev": true, + "requires": { + "lodash.isarray": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._topath": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/lodash._topath/-/lodash._topath-3.8.1.tgz", + "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", + "dev": true, + "requires": { + "lodash.isarray": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.map": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-3.1.4.tgz", + "integrity": "sha1-tIOs0beGxce0ksSV97UmYim8AMI=", + "dev": true, + "requires": { + "lodash._arraymap": "^3.0.0", + "lodash._basecallback": "^3.0.0", + "lodash._baseeach": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash.pairs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz", + "integrity": "sha1-u+CNV4bu6qCaFckevw3LfSvjJqk=", + "dev": true, + "requires": { + "lodash.keys": "^3.0.0" + } + }, + "lodash.pluck": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.pluck/-/lodash.pluck-3.1.2.tgz", + "integrity": "sha1-s0fwN0wBafDusE1nLYnOyGMsIjE=", + "dev": true, + "requires": { + "lodash._baseget": "^3.0.0", + "lodash._topath": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.map": "^3.0.0" + } + }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", @@ -3556,6 +3959,33 @@ } } }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "dev": true, + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "lolex": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.0.1.tgz", @@ -3630,6 +4060,12 @@ "stack-trace": "0.0.10" } }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3854,6 +4290,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "mustache": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.1.tgz", + "integrity": "sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA==", + "dev": true + }, "nan": { "version": "2.13.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", @@ -3964,6 +4406,15 @@ "path-key": "^2.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -4080,6 +4531,18 @@ "isobject": "^3.0.1" } }, + "object.values": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -4098,6 +4561,12 @@ "wrappy": "1" } }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=", + "dev": true + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -4425,6 +4894,18 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "pretty-bytes": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.2.0.tgz", + "integrity": "sha512-ujANBhiUsl9AhREUDUEY1GPOharMGm8x8juS7qOHybcLi7XsKfrYQ88hSly1l2i0klXHTDYrlL8ihMCG55Dc3w==", + "dev": true + }, + "prettysize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prettysize/-/prettysize-1.1.0.tgz", + "integrity": "sha512-U5Noa+FYV1dGkICyLJz8IWlDUehPF4Bk9tZRO8YqPhLA9EoiHuFqtnpWY2mvMjHh5eOLo82HipeLn4RIiSsGqQ==", + "dev": true + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -4465,6 +4946,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, "qjobs": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", @@ -4623,6 +5110,12 @@ "is-finite": "^1.0.0" } }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, "request": { "version": "2.88.0", "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", @@ -4770,6 +5263,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "semver": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", @@ -4830,12 +5329,83 @@ "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, "sinon": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", @@ -5137,6 +5707,12 @@ "tweetnacl": "~0.14.0" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -5294,6 +5870,216 @@ "has-flag": "^3.0.0" } }, + "svg-sprite": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/svg-sprite/-/svg-sprite-1.5.0.tgz", + "integrity": "sha512-0mE5BLY3K8wg3+HrYfzpiKbIM44IGcg8uINED8ri22EdQbLvGecOHjRtkrNAlphbiU5kyGyqoBlIaukL45fs2Q==", + "dev": true, + "requires": { + "async": "^2.6.1", + "css-selector-parser": "^1.3.0", + "cssmin": "^0.4.3", + "cssom": "^0.3.4", + "dateformat": "^3.0.3", + "glob": "^7.1.3", + "js-yaml": "^3.12.0", + "lodash": "^4.17.11", + "lodash.pluck": "^3.1.2", + "mkdirp": "^0.5.1", + "mocha": "^5.2.0", + "mustache": "^3.0.0", + "phantomjs-prebuilt": "^2.1.16", + "prettysize": "^1.1.0", + "should": "^13.2.3", + "svgo": "^1.1.1", + "vinyl": "^2.2.0", + "winston": "^3.1.0", + "xmldom": "0.1.27", + "xpath": "^0.0.27", + "yargs": "^12.0.2" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "svgo": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.2.2.tgz", + "integrity": "sha512-rAfulcwp2D9jjdGu+0CuqlrAUin6bBWrpoqXWwKDZZZJfXcUXQSxLJOFJCQCSA0x0pP2U0TxSlJu2ROq5Bq6qA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.28", + "css-url-regex": "^1.1.0", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "dev": true + }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -5414,6 +6200,12 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==", + "dev": true + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -5545,6 +6337,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -5622,6 +6420,16 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5663,6 +6471,20 @@ "extsprintf": "^1.2.0" } }, + "vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -5714,6 +6536,90 @@ "string-width": "^1.0.2 || 2" } }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "dev": true, + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "dev": true, + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -5769,12 +6675,24 @@ "ultron": "~1.1.0" } }, + "xmldom": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz", + "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", "dev": true }, + "xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index fc687c827..cd35d6001 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "grunt-contrib-jshint": "^2.0.0", "grunt-contrib-watch": "^1.0.0", "grunt-karma": "^3.0.1", + "grunt-svg-sprite": "^1.5.0", + "grunt-svgmin": "^6.0.0", "jquery": "~> 3.4.0", "karma": "^4.1.0", "karma-coverage": "^1.1.1", diff --git a/src/DistortableCollection.js b/src/DistortableCollection.js index 6b8123438..b48a56ed3 100644 --- a/src/DistortableCollection.js +++ b/src/DistortableCollection.js @@ -106,7 +106,7 @@ L.DistortableCollection = L.FeatureGroup.extend({ id: this.getLayerId(layer), src: layer._image.src, nodes: layer.getCorners(), - cm_per_pixel: layer._getCmPerPixel() + cm_per_pixel: L.ImageUtil.getCmPerPixel(layer) }); } }, this); diff --git a/src/DistortableImageOverlay.js b/src/DistortableImageOverlay.js index ae30773e5..ae480e249 100644 --- a/src/DistortableImageOverlay.js +++ b/src/DistortableImageOverlay.js @@ -12,10 +12,11 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ this._toolArray = L.DistortableImage.EditToolbarDefaults; this.edgeMinWidth = this.options.edgeMinWidth; this._url = url; - this._rotation = this.options.rotation; + this.rotation = 0; + // window.rotation = this.rotation; L.DistortableImage._options = options; - L.Util.setOptions(this, options); + L.setOptions(this, options); }, onAdd: function(map) { @@ -107,6 +108,7 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ map.containerPointToLatLng(center.add(offset)) ]; } + this._initialDimensions = { 'height': imageHeight, 'width': imageWidth, 'offset': offset }; }, _initEvents: function() { @@ -290,15 +292,6 @@ L.DistortableImageOverlay = L.ImageOverlay.extend({ 0, h, c[2].x, c[2].y, w, h, c[3].x, c[3].y ); - }, - - _getCmPerPixel: function() { - var map = this._map; - - var dist = map.latLngToLayerPoint(this.getCorner(0)) - .distanceTo(map.latLngToLayerPoint(this.getCorner(1))); - - return (dist * 100) / this._image.width; } }); diff --git a/src/edit/DistortHandle.js b/src/edit/DistortHandle.js index f760e3257..ae2f02183 100644 --- a/src/edit/DistortHandle.js +++ b/src/edit/DistortHandle.js @@ -9,14 +9,17 @@ L.DistortHandle = L.EditHandle.extend({ }) }, + _onHandleDrag: function() { + var overlay = this._handled; + + overlay._updateCorner(this._corner, this.getLatLng()); + + overlay.fire("update"); + overlay.editing._updateToolbarPos(); + }, + updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); + this.setLatLng(this._handled.getCorner(this._corner)); }, - _onHandleDrag: function() { - this._handled._updateCorner(this._corner, this.getLatLng()); - - this._handled.fire("update"); - this._handled.editing._showToolbar(); - } }); diff --git a/src/edit/DistortableImage.Edit.js b/src/edit/DistortableImage.Edit.js index f391a1abb..61679eddb 100644 --- a/src/edit/DistortableImage.Edit.js +++ b/src/edit/DistortableImage.Edit.js @@ -173,12 +173,43 @@ L.DistortableImage.Edit = L.Handler.extend({ Math.cos(angle) * p.x - Math.sin(angle) * p.y, Math.sin(angle) * p.x + Math.cos(angle) * p.y ); - overlay._corners[i] = map.layerPointToLatLng(q.add(center)); + overlay._updateCorner(i, map.layerPointToLatLng(q.add(center))); } + // window.angle = L.TrigUtil.radiansToDegrees(angle); + + this._overlay.rotation -= L.TrigUtil.radiansToDegrees(angle); + overlay._reset(); }, + _restore: function() { + var overlay = this._overlay; + var angle = overlay.rotation; + var map = overlay._map; + var center = map.latLngToLayerPoint(overlay.getCenter()); + var offset = overlay._initialDimensions.offset; + + var corners = { + 0: map.layerPointToLatLng(center.subtract(offset)), + 1: map.layerPointToLatLng(center.add(L.point(offset.x, -offset.y))), + 2: map.layerPointToLatLng(center.add(L.point(-offset.x, offset.y))), + 3: map.layerPointToLatLng(center.add(offset)) + }; + + map.removeLayer(this._handles[this._mode]); + + overlay._updateCorners(corners); + + if (angle !== 0) { this._rotateBy(L.TrigUtil.degreesToRadians(360 - angle)); } + + map.addLayer(this._handles[this._mode]); + + this._showToolbar(); + + this._overlay.rotation = angle; + }, + _scaleBy: function(scale) { var overlay = this._overlay, map = overlay._map, @@ -188,11 +219,11 @@ L.DistortableImage.Edit = L.Handler.extend({ for (i = 0; i < 4; i++) { p = map - .latLngToLayerPoint(overlay._corners[i]) + .latLngToLayerPoint(overlay.getCorner(i)) .subtract(center) .multiplyBy(scale) .add(center); - overlay._corners[i] = map.layerPointToLatLng(p); + overlay._updateCorner(i, map.layerPointToLatLng(p)); } overlay._reset(); @@ -300,6 +331,8 @@ L.DistortableImage.Edit = L.Handler.extend({ L.DomUtil.setOpacity(image, opacity); image.setAttribute("opacity", opacity); + + this._showToolbar(); }, _toggleOutline: function() { @@ -406,11 +439,11 @@ L.DistortableImage.Edit = L.Handler.extend({ // TODO: toolbar for multiple image selection _showToolbar: function() { var overlay = this._overlay, - map = overlay._map; + map = overlay._map, + //Find the topmost point on the image. + corners = overlay.getCorners(), + maxLat = -Infinity; - //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; @@ -421,14 +454,36 @@ L.DistortableImage.Edit = L.Handler.extend({ var raised_point = overlay.getCenter(); raised_point.lat = maxLat; - if (this._overlay.options.suppressToolbar !== true) { + if (overlay.options.suppressToolbar !== true) { try { this.toolbar = new L.DistortableImage.EditToolbar(raised_point).addTo(map, overlay); overlay.fire('toolbar:created'); } catch (e) {} } - }, + }, + + _updateToolbarPos: function() { + var overlay = this._overlay, + //Find the topmost point on the image. + corners = overlay.getCorners(), + 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(); + raised_point.lat = maxLat; + + if (overlay.options.suppressToolbar !== true) { + this.toolbar.setLatLng(raised_point); + } + + }, _removeOverlay: function () { var overlay = this._overlay, @@ -475,10 +530,10 @@ L.DistortableImage.Edit = L.Handler.extend({ downloadable.onload = function onLoadDownloadableImage() { var height = downloadable.height, width = downloadable.width, - nw = map.latLngToLayerPoint(overlay._corners[0]), - ne = map.latLngToLayerPoint(overlay._corners[1]), - sw = map.latLngToLayerPoint(overlay._corners[2]), - se = map.latLngToLayerPoint(overlay._corners[3]); + nw = map.latLngToLayerPoint(overlay.getCorner(0)), + ne = map.latLngToLayerPoint(overlay.getCorner(1)), + sw = map.latLngToLayerPoint(overlay.getCorner(2)), + se = map.latLngToLayerPoint(overlay.getCorner(3)); // I think this is to move the image to the upper left corner, // jywarren: i think we may need these or the image goes off the edge of the canvas diff --git a/src/edit/DistortableImage.EditToolbar.js b/src/edit/DistortableImage.EditToolbar.js index 3f25931f2..c37612c28 100644 --- a/src/edit/DistortableImage.EditToolbar.js +++ b/src/edit/DistortableImage.EditToolbar.js @@ -1,20 +1,35 @@ 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); - } - }), - + LeafletToolbar.ToolbarAction.prototype.initialize.call(this, options); + } +}), + ToggleTransparency = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'opacity', - tooltip: 'Toggle Transparency' + initialize: function(map, overlay, options) { + var edit = overlay.editing, + href, + tooltip; + + if (edit._transparent) { + href = ''; + tooltip = 'Make Image Opaque'; + } else { + href = ''; + tooltip = 'Make Image Transparent'; } + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: tooltip + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); }, addHooks: function() { @@ -23,17 +38,26 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleTransparency(); this.disable(); } - }), - + }), + ToggleOutline = EditOverlayAction.extend({ initialize: function(map, overlay, options) { var edit = overlay.editing, - icon = edit._outlined ? 'border_clear' : 'border_outer'; + href, + tooltip; + + if (edit._outlined) { + href = ''; + tooltip = 'Remove Border'; + } else { + href = ''; + tooltip = 'Add Border'; + } options = options || {}; options.toolbarIcon = { - html: '' + icon + '', - tooltip: 'Toggle Outline' + html: '' + href + '', + tooltip: tooltip }; EditOverlayAction.prototype.initialize.call(this, map, overlay, options); @@ -45,14 +69,19 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleOutline(); this.disable(); } - }), - + }), + Delete = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'delete_forever', + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', tooltip: 'Delete Image' - } + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); }, addHooks: function() { @@ -61,25 +90,25 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._removeOverlay(); this.disable(); } - }), - + }), + ToggleLock = EditOverlayAction.extend({ initialize: function(map, overlay, options) { var edit = overlay.editing, - icon, + href, tooltip; if (edit._mode === 'lock') { - icon = 'lock_open'; + href = ''; tooltip = 'Unlock'; } else { - icon = 'lock'; + href = ''; tooltip = 'Lock'; } options = options || {}; options.toolbarIcon = { - html: '' + icon + '', + html: '' + href + '', tooltip: tooltip }; @@ -92,25 +121,25 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleLock(); this.disable(); } - }), - + }), + ToggleRotateScale = EditOverlayAction.extend({ initialize: function(map, overlay, options) { - var edit = overlay.editing, - icon, - tooltip; - - if (edit._mode === 'rotateScale') { - icon = 'transform'; - tooltip = 'Distort'; - } else { - icon = 'crop_rotate'; - tooltip = 'Rotate+Scale'; - } + var edit = overlay.editing, + href, + tooltip; + + if (edit._mode === 'rotateScale') { + href = ''; + tooltip = 'Distort'; + } else { + href = ''; + tooltip = 'Rotate+Scale'; + } options = options || {}; options.toolbarIcon = { - html: '' + icon + '', + html: '' + href + '', tooltip: tooltip }; @@ -123,14 +152,19 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleRotateScale(); this.disable(); } - }), - + }), + Export = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'get_app', + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', tooltip: 'Export Image' - } + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); }, addHooks: function() { @@ -139,25 +173,25 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleExport(); this.disable(); } - }), - + }), + ToggleOrder = EditOverlayAction.extend({ initialize: function(map, overlay, options) { var edit = overlay.editing, - icon, + href, tooltip; - if (edit._toggledImage) { - icon = 'flip_to_front'; - tooltip = 'Stack to front'; - } else { - icon = 'flip_to_back'; - tooltip = 'Stack to back'; - } + if (edit._toggledImage) { + href = ''; + tooltip = 'Stack to Front'; + } else { + href = ''; + tooltip = 'Stack to Back'; + } options = options || {}; options.toolbarIcon = { - html: '' + icon + '', + html: '' + href + '', tooltip: tooltip }; @@ -170,14 +204,19 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ editing._toggleOrder(); this.disable(); } - }), - + }), + EnableEXIF = EditOverlayAction.extend({ - options: { - toolbarIcon: { - html: 'explore', + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', tooltip: 'Geolocate Image' - } + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); }, addHooks: function() { @@ -185,6 +224,27 @@ var EditOverlayAction = LeafletToolbar.ToolbarAction.extend({ EXIF.getData(image, L.EXIF(image)); } + }), + + Restore = EditOverlayAction.extend({ + initialize: function(map, overlay, options) { + var href = ''; + + options = options || {}; + options.toolbarIcon = { + html: '' + href + '', + tooltip: 'Restore' + }; + + EditOverlayAction.prototype.initialize.call(this, map, overlay, options); + }, + + addHooks: function() { + var editing = this._overlay.editing; + + editing._restore(); + this.disable(); + } }); L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ @@ -196,6 +256,7 @@ L.DistortableImage.EditToolbar = LeafletToolbar.Popup.extend({ ToggleRotateScale, ToggleOrder, EnableEXIF, + Restore, Export, Delete ] diff --git a/src/edit/EditHandle.js b/src/edit/EditHandle.js index 5d5a83232..f043bb24c 100644 --- a/src/edit/EditHandle.js +++ b/src/edit/EditHandle.js @@ -1,7 +1,7 @@ L.EditHandle = L.Marker.extend({ initialize: function(overlay, corner, options) { var markerOptions, - latlng = overlay._corners[corner]; + latlng = overlay.getCorner(corner); L.setOptions(this, options); @@ -76,13 +76,14 @@ L.EditHandle = L.Marker.extend({ /* 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), + var overlay = this._handled, + map = overlay._map, - formerRadiusSquared = this._d2(centerPoint, formerPoint), - newRadiusSquared = this._d2(centerPoint, newPoint); + centerPoint = map.latLngToLayerPoint(overlay.getCenter()), + formerPoint = map.latLngToLayerPoint(latlngA), + newPoint = map.latLngToLayerPoint(latlngB), + formerRadiusSquared = this._d2(centerPoint, formerPoint), + newRadiusSquared = this._d2(centerPoint, newPoint); return Math.sqrt(newRadiusSquared / formerRadiusSquared); }, @@ -96,10 +97,11 @@ L.EditHandle = L.Marker.extend({ }, /* Takes two latlngs and calculates the angle between them. */ - _calculateAngle: function(latlngA, latlngB) { - var map = this._handled._map, + calculateAngleDelta: function(latlngA, latlngB) { + var overlay = this._handled, + map = overlay._map, - centerPoint = map.latLngToLayerPoint(this._handled.getCenter()), + centerPoint = map.latLngToLayerPoint(overlay.getCenter()), formerPoint = map.latLngToLayerPoint(latlngA), newPoint = map.latLngToLayerPoint(latlngB), diff --git a/src/edit/LockHandle.js b/src/edit/LockHandle.js index e166c8b54..d23dc1f5e 100644 --- a/src/edit/LockHandle.js +++ b/src/edit/LockHandle.js @@ -13,7 +13,7 @@ L.LockHandle = L.EditHandle.extend({ }, updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); + this.setLatLng(this._handled.getCorner(this._corner)); L.DomUtil.removeClass(this._handled.getElement(), 'selected'); } diff --git a/src/edit/RotateHandle.js b/src/edit/RotateHandle.js index f3dfa23ba..1b4533ec3 100644 --- a/src/edit/RotateHandle.js +++ b/src/edit/RotateHandle.js @@ -10,19 +10,18 @@ L.RotateHandle = L.EditHandle.extend({ _onHandleDrag: function() { var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], + formerLatLng = overlay.getCorner(this._corner), newLatLng = this.getLatLng(), - angle = this._calculateAngle(formerLatLng, newLatLng); + angle = this.calculateAngleDelta(formerLatLng, newLatLng); - overlay.editing._rotateBy(angle); + if (angle !== 0) { overlay.editing._rotateBy(angle); } overlay.fire('update'); - - this._handled.editing._showToolbar(); + overlay.editing._updateToolbarPos(); }, updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); + this.setLatLng(this._handled.getCorner(this._corner)); } }); diff --git a/src/edit/RotateScaleHandle.js b/src/edit/RotateScaleHandle.js index deaa78da0..142006098 100644 --- a/src/edit/RotateScaleHandle.js +++ b/src/edit/RotateScaleHandle.js @@ -10,35 +10,33 @@ L.RotateScaleHandle = L.EditHandle.extend({ _onHandleDrag: function() { var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], + edit = overlay.editing, + formerLatLng = overlay.getCorner(this._corner), newLatLng = this.getLatLng(), - angle = this._calculateAngle(formerLatLng, newLatLng), + angle = this.calculateAngleDelta(formerLatLng, newLatLng), scale = this._calculateScalingFactor(formerLatLng, newLatLng); - - overlay.editing._rotateBy(angle); + + if (angle !== 0) { edit._rotateBy(angle); } /* 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 (overlay.hasOwnProperty('edgeMinWidth')){ + var edgeMinWidth = overlay.edgeMinWidth, + w = L.latLng(overlay.getCorner(0)).distanceTo(overlay.getCorner(1)), + h = L.latLng(overlay.getCorner(1)).distanceTo(overlay.getCorner(2)); if ((w > edgeMinWidth && h > edgeMinWidth) || scale > 1) { - overlay.editing._scaleBy(scale); + edit._scaleBy(scale); } } overlay.fire('update'); - - this._handled.editing._showToolbar(); - + edit._updateToolbarPos(); }, updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); + this.setLatLng(this._handled.getCorner(this._corner)); }, - }); diff --git a/src/edit/ScaleHandle.js b/src/edit/ScaleHandle.js index 401c84adc..470c8d371 100644 --- a/src/edit/ScaleHandle.js +++ b/src/edit/ScaleHandle.js @@ -10,7 +10,7 @@ L.ScaleHandle = L.EditHandle.extend({ _onHandleDrag: function() { var overlay = this._handled, - formerLatLng = this._handled._corners[this._corner], + formerLatLng = overlay.getCorner(this._corner), newLatLng = this.getLatLng(), scale = this._calculateScalingFactor(formerLatLng, newLatLng); @@ -18,13 +18,10 @@ L.ScaleHandle = L.EditHandle.extend({ overlay.editing._scaleBy(scale); overlay.fire('update'); - - this._handled.editing._showToolbar(); + overlay.editing._updateToolbarPos(); }, updateHandle: function() { - this.setLatLng(this._handled._corners[this._corner]); + this.setLatLng(this._handled.getCorner(this._corner)); }, - - }); diff --git a/src/util/ImageUtil.js b/src/util/ImageUtil.js new file mode 100644 index 000000000..85aa4fb2c --- /dev/null +++ b/src/util/ImageUtil.js @@ -0,0 +1,13 @@ +L.ImageUtil = { + + getCmPerPixel: function(overlay) { + var map = overlay._map; + + var dist = map + .latLngToLayerPoint(overlay.getCorner(0)) + .distanceTo(map.latLngToLayerPoint(overlay.getCorner(1))); + + return (dist * 100) / overlay._image.width; + } + +}; \ No newline at end of file diff --git a/src/util/TrigUtil.js b/src/util/TrigUtil.js index 74ac10773..43108542c 100644 --- a/src/util/TrigUtil.js +++ b/src/util/TrigUtil.js @@ -1,7 +1,16 @@ L.TrigUtil = { calcAngleDegrees: function(x, y) { - return Math.atan2(y, x) * 180 / Math.PI; + var pointAngle = Math.atan2(y, x); + return this.radiansToDegrees(pointAngle); + }, + + radiansToDegrees: function(angle) { + return angle * 180 / Math.PI; + }, + + degreesToRadians: function(angle) { + return angle * Math.PI / 180; } }; \ No newline at end of file diff --git a/test/src/DistortableCollectionSpec.js b/test/src/DistortableCollectionSpec.js index b0c331d70..30737b027 100644 --- a/test/src/DistortableCollectionSpec.js +++ b/test/src/DistortableCollectionSpec.js @@ -11,8 +11,8 @@ describe("L.DistortableCollection", function () { corners: [ L.latLng(41.7934, -87.6052), L.latLng(41.7934, -87.5852), - L.latLng(41.7834, -87.5852), - L.latLng(41.7834, -87.6052) + L.latLng(41.7834, -87.6052), + L.latLng(41.7834, -87.5852) ] }).addTo(map); @@ -20,8 +20,8 @@ describe("L.DistortableCollection", function () { corners: [ L.latLng(41.7934, -87.6050), L.latLng(41.7934, -87.5850), - L.latLng(41.7834, -87.5850), - L.latLng(41.7834, -87.6050) + L.latLng(41.7834, -87.6050), + L.latLng(41.7834, -87.5850) ] }).addTo(map); diff --git a/test/src/DistortableImageOverlaySpec.js b/test/src/DistortableImageOverlaySpec.js index 62a12c7d8..fc79988d0 100644 --- a/test/src/DistortableImageOverlaySpec.js +++ b/test/src/DistortableImageOverlaySpec.js @@ -18,8 +18,8 @@ describe("L.DistortableImageOverlay", function() { corners: [ L.latLng(41.7934, -87.6052), L.latLng(41.7934, -87.5852), - L.latLng(41.7834, -87.5852), - L.latLng(41.7834, -87.6052) + L.latLng(41.7834, -87.6052), + L.latLng(41.7834, -87.5852) ] }); }); @@ -37,7 +37,7 @@ describe("L.DistortableImageOverlay", function() { }); }); - describe("#_getCenter", function() { + describe("#getCenter", function() { it("Should return the center when the outline of the image is a rectangle.", function(done) { distortable.addTo(map); diff --git a/test/src/edit/RotateHandleSpec.js b/test/src/edit/RotateHandleSpec.js index a2848b81a..78826664d 100644 --- a/test/src/edit/RotateHandleSpec.js +++ b/test/src/edit/RotateHandleSpec.js @@ -7,10 +7,10 @@ describe("L.RotateHandle", function() { map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); distortable = L.distortableImageOverlay('/examples/example.jpg', { corners: [ - new L.LatLng(41.7934, -87.6052), - new L.LatLng(41.7934, -87.5852), - new L.LatLng(41.7834, -87.5852), - new L.LatLng(41.7834, -87.6052) + L.latLng(41.7934, -87.6052), + L.latLng(41.7934, -87.5852), + L.latLng(41.7834, -87.5852), + L.latLng(41.7834, -87.6052) ] }).addTo(map); @@ -22,8 +22,8 @@ describe("L.RotateHandle", function() { describe("_calculateRotation", function() { it("Should return 0 when given the same latlng twice.", function() { - var latlng = distortable._corners[0], - angle = rotateHandle._calculateAngle(latlng, latlng); + var latlng = distortable.getCorner(0), + angle = rotateHandle.calculateAngleDelta(latlng, latlng); expect(angle).to.equal(0); }); diff --git a/test/src/edit/RotateScaleHandleSpec.js b/test/src/edit/RotateScaleHandleSpec.js index 23876a6d7..c2f5ea5b2 100644 --- a/test/src/edit/RotateScaleHandleSpec.js +++ b/test/src/edit/RotateScaleHandleSpec.js @@ -7,10 +7,10 @@ describe("L.RotateScaleHandle", function() { map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); distortable = L.distortableImageOverlay('/examples/example.png', { corners: [ - new L.LatLng(41.7934, -87.6052), - new L.LatLng(41.7934, -87.5852), - new L.LatLng(41.7834, -87.5852), - new L.LatLng(41.7834, -87.6052) + L.latLng(41.7934, -87.6052), + L.latLng(41.7934, -87.5852), + L.latLng(41.7834, -87.5852), + L.latLng(41.7834, -87.6052) ] }).addTo(map); @@ -26,8 +26,8 @@ describe("L.RotateScaleHandle", function() { describe("_calculateRotation", function() { it("Should return 0 when given the same latlng twice.", function() { - var latlng = distortable._corners[0], - angle = rotateHandle._calculateAngle(latlng, latlng); + var latlng = distortable.getCorner(0), + angle = rotateHandle.calculateAngleDelta(latlng, latlng); expect(angle).to.equal(0); }); @@ -35,7 +35,7 @@ describe("L.RotateScaleHandle", function() { describe("_calculateScalingFactor", function() { it("Should return 1 when given the same latlng twice.", function() { - var latlng = distortable._corners[0], + var latlng = distortable.getCorner(0), scale = rotateHandle._calculateScalingFactor(latlng, latlng); expect(scale).to.equal(1); diff --git a/test/src/edit/ScaleHandleSpec.js b/test/src/edit/ScaleHandleSpec.js index 72de986ff..6e370b484 100644 --- a/test/src/edit/ScaleHandleSpec.js +++ b/test/src/edit/ScaleHandleSpec.js @@ -7,10 +7,10 @@ describe("L.ScaleHandle", function() { map = L.map(L.DomUtil.create('div', '', document.body)).setView([41.7896,-87.5996], 15); distortable = L.distortableImageOverlay('/examples/example.jpg', { corners: [ - new L.LatLng(41.7934, -87.6052), - new L.LatLng(41.7934, -87.5852), - new L.LatLng(41.7834, -87.5852), - new L.LatLng(41.7834, -87.6052) + L.latLng(41.7934, -87.6052), + L.latLng(41.7934, -87.5852), + L.latLng(41.7834, -87.5852), + L.latLng(41.7834, -87.6052) ] }).addTo(map); @@ -22,7 +22,7 @@ describe("L.ScaleHandle", function() { describe("_calculateScalingFactor", function() { it("Should return 1 when given the same latlng twice.", function() { - var latlng = distortable._corners[0], + var latlng = distortable.getCorner(0), scale = scaleHandle._calculateScalingFactor(latlng, latlng); expect(scale).to.equal(1);