From 703ae02aa8cbd0b87be5b01e77754b83ad732267 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Mon, 30 Jan 2017 12:35:16 +0200 Subject: [PATCH] ES6 modules & Rollup (#4989) * WIP ES6 modules & rollup * WIP ES6 modules & rollup 2 * WIP ES6 modules & rollup 3 * WIP ES6 modules Browser * WIP ES6 module fixes * WIP ES6 modules: simpler browser exports * WIP ES6: refactor CRS/Projection modules, CRS obj -> CRS.Base * get rid of unnecessary index.js * WIP ES6 modules, dom events and stuff * Make linter happy, rollup to dist/ * revert to CRS namespace/class for now * WIP rollup: export more stuff * export controls * rollup: export Layer * rollup: export DomEvent * rollup: export more layer types * rollup: export Popup/Tooltip * WIP: ES6-ify marker, icon, domutil, draggable. * ES6-ify gridlayer, tilelayer. * ES6-ify: Tweak imports-exports, code is now runnable!! * ES6-ify: Fix scope in some DomUtils * ES6-ify: Path, fix Popup * ES6-ify: Lint & cleanup * ES6-ify map handlers, more linting * ES6-ify: Icon.Default namespacing * ES6-ify: Renderers, CircleMarker * ES6-ify: Circle, Polyline, LineUtil * ES6-ify: Polygon, Rectangle, LineUtil, PolyUtil, linting * ES6-ify: SVG.VML * ES6-ify: DomEvent.Pointer, DomEvent.DoubleTap * ES6-ify: Linting, make Karma play nice with Rollup * ES6-ify: More work on fixing bits breaking some unit tests. * ES6-ify: rollup the version number, fiddled with build scripts * ES6-ify: Fiddle with test scripts * ES6-ify: cleanup (refs to global L, imports from (DOM)Util), prevent cyclic loop on Map imports * ES6-ify: More cleanup of (DOM)Util/Browser/DomEvent imports * ES6ify: Use rollup's "legacy" option for ES3 (IE8) builds * ES6-ify: Clean up build scripts, fix CONTRIBUTING.md instructions * Typo * ES6-ify: minor fixes and lefovers after rebasing on top of 1.0.2 * ES6-ify: upgrade to rollup 0.38 for proper IE8 builds, fix L.SVG.VML * Make linter happy. * ES6: Fixing typos and sxrew-ups after big rebase * Fix symlink for debugging scripts * ES6: Cleanup old build scripts * ES6-ify: Update build system to include git rev in L.version * ES6-ify: re-enable unit tests replacing L.Path with L.Polyline * Export Path * ES6ify: cleanup old banner file * ES6-ify: whitespace in var declarations * ES6-ify: Export toTransformation as L.transformation * ES6-ify: cleanup L.transform exports * ES6-ify: "import Util" in Transformation and SVG.VML --- CONTRIBUTING.md | 30 +- Jakefile.js | 67 ++- bower.json | 2 +- build/build.html | 227 ------- build/build.js | 237 -------- build/deps.js | 240 -------- build/publish.sh | 15 +- build/rollup-config.js | 40 ++ build/rollup-watch-config.js | 43 ++ debug/leaflet-include.js | 59 +- debug/map/rollup.html | 105 ++++ debug/tests/touch-zoom-bounce.html | 5 +- docs/index.html | 2 +- package.json | 26 +- spec/index.html | 2 +- spec/karma.conf.js | 32 +- spec/suites/SpecHelper.js | 3 + spec/suites/layer/vector/PathSpec.js | 7 +- spec/suites/layer/vector/PolygonSpec.js | 2 +- .../layer/vector/PolylineGeometrySpec.js | 26 + spec/suites/layer/vector/PolylineSpec.js | 24 - spec/suites/map/MapSpec.js | 32 +- src/Leaflet.js | 189 +++++- src/control/Control.Attribution.js | 27 +- src/control/Control.Layers.js | 73 +-- src/control/Control.Scale.js | 16 +- src/control/Control.Zoom.js | 39 +- src/control/Control.js | 26 +- src/copyright.js | 4 - src/core/Browser.js | 245 ++++---- src/core/Class.js | 25 +- src/core/Events.js | 28 +- src/core/Handler.js | 4 +- src/core/Util.js | 460 +++++++-------- src/dom/DomEvent.DoubleTap.js | 153 +++-- src/dom/DomEvent.Pointer.js | 240 ++++---- src/dom/DomEvent.js | 552 +++++++++-------- src/dom/DomUtil.js | 558 +++++++++--------- src/dom/Draggable.js | 99 ++-- src/dom/PosAnimation.js | 15 +- src/geo/LatLng.js | 40 +- src/geo/LatLngBounds.js | 52 +- src/geo/crs/CRS.EPSG3395.js | 14 +- src/geo/crs/CRS.EPSG3857.js | 15 +- src/geo/crs/CRS.EPSG4326.js | 11 +- src/geo/crs/CRS.Earth.js | 5 +- src/geo/crs/CRS.Simple.js | 11 +- src/geo/crs/CRS.js | 29 +- src/geo/projection/Projection.LonLat.js | 14 +- src/geo/projection/Projection.Mercator.js | 12 +- .../Projection.SphericalMercator.js | 12 +- src/geo/projection/Projection.js | 22 + src/geo/projection/Projection.leafdoc | 16 - src/geometry/Bounds.js | 36 +- src/geometry/LineUtil.js | 448 +++++++------- src/geometry/Point.js | 42 +- src/geometry/PolyUtil.js | 21 +- src/geometry/Transformation.js | 23 +- src/layer/DivOverlay.js | 30 +- src/layer/FeatureGroup.js | 15 +- src/layer/GeoJSON.js | 348 +++++------ src/layer/ImageOverlay.js | 40 +- src/layer/Layer.js | 23 +- src/layer/LayerGroup.js | 12 +- src/layer/Popup.js | 97 +-- src/layer/Tooltip.js | 78 +-- src/layer/marker/DivIcon.js | 13 +- src/layer/marker/Icon.Default.js | 19 +- src/layer/marker/Icon.js | 21 +- src/layer/marker/Marker.Drag.js | 16 +- src/layer/marker/Marker.js | 45 +- src/layer/tile/GridLayer.js | 116 ++-- src/layer/tile/TileLayer.WMS.js | 29 +- src/layer/tile/TileLayer.js | 49 +- src/layer/vector/Canvas.js | 100 +--- src/layer/vector/Circle.js | 30 +- src/layer/vector/CircleMarker.js | 29 +- src/layer/vector/Path.js | 10 +- src/layer/vector/Polygon.js | 57 +- src/layer/vector/Polyline.js | 67 ++- src/layer/vector/Rectangle.js | 15 +- src/layer/vector/Renderer.getRenderer.js | 41 ++ src/layer/vector/Renderer.js | 67 +-- src/layer/vector/SVG.Util.js | 39 ++ src/layer/vector/SVG.VML.js | 83 ++- src/layer/vector/SVG.js | 85 +-- src/map/Map.js | 222 +++---- src/map/handler/Map.BoxZoom.js | 50 +- src/map/handler/Map.DoubleClickZoom.js | 9 +- src/map/handler/Map.Drag.js | 31 +- src/map/handler/Map.Keyboard.js | 24 +- src/map/handler/Map.ScrollWheelZoom.js | 21 +- src/map/handler/Map.Tap.js | 37 +- src/map/handler/Map.TouchZoom.js | 46 +- 94 files changed, 3382 insertions(+), 3434 deletions(-) delete mode 100644 build/build.html delete mode 100644 build/build.js delete mode 100644 build/deps.js create mode 100644 build/rollup-config.js create mode 100644 build/rollup-watch-config.js mode change 100644 => 120000 debug/leaflet-include.js create mode 100644 debug/map/rollup.html delete mode 100644 src/copyright.js create mode 100644 src/geo/projection/Projection.js delete mode 100644 src/geo/projection/Projection.leafdoc create mode 100644 src/layer/vector/Renderer.getRenderer.js create mode 100644 src/layer/vector/SVG.Util.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d022693..a0a20f5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,9 +72,11 @@ To set up the Leaflet build system, install Node then run the following commands npm install -g jake npm install ``` - -You can build minified Leaflet by running `jake` (it will be built from source in the `dist` folder). -For a custom build with selected components, open `build/build.html` in the browser and follow the instructions from there. +or, if you prefer [`yarn`](https://yarnpkg.com/) over `npm`: +``` +yarn install -g jake +yarn install +``` ### Making Changes to Leaflet Source @@ -96,6 +98,25 @@ Also, please make sure that you have [line endings configured properly](https:// Happy coding! +### Using RollupJS + +The source javascript code for Leaflet is a few dozen files, in the `src/` directory. +But normally, Leaflet is loaded in a web browser as just one javascript file. + +In order to create this file, run `npm run-script rollup` or `yarn run rollup`. + +You'll find `dist/leaflet-src.js` and `dist/leaflet.js`. The difference is that +`dist/leaflet-src.js` has sourcemaps and it's not uglified, so it's better for +development. `dist/leaflet.js` is uglified and thus is smaller, so it's better +for deployment. + +When developing (or bugfixing) core Leaflet functionalities, it's common to use +the webpages in the `debug/` directory, and run the unit tests (`spec/index.html`) +in a graphical browser. This requires regenerating the bundled files quickly. + +In order to do so, run `npm run-script watch` or `yarn run rollup`. This will keep +on rebuilding the bundles whenever any source file changes. + ## Running the Tests To run the tests from the command line, @@ -158,7 +179,8 @@ code for every method, option or property there is a special code comment docume that feature. In order to edit the API documentation, just edit these comments in the source code. -In order to generate the documentation, just run +In order to generate the documentation, make sure that the development dependencies +are installed (run either `npm install` or `yarn install`), then just run ``` jake docs diff --git a/Jakefile.js b/Jakefile.js index 35f4a02c..c71a7413 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -12,9 +12,9 @@ To run the tests, run "jake test". To build the documentation, run "jake docs". For a custom build, open build/build.html in the browser and follow the instructions. */ -var build = require('./build/build.js'), - buildDocs = require('./build/docs'), - git = require('git-rev'); +var buildDocs = require('./build/docs'), + git = require('git-rev-sync'), + path = require('path'); function hint(msg, args) { return function () { @@ -29,16 +29,14 @@ function hint(msg, args) { // Returns the version string in package.json, plus a semver build metadata if // this is not an official release -function calculateVersion(officialRelease, callback) { +function calculateVersion(officialRelease) { var version = require('./package.json').version; if (officialRelease) { - callback(version); + return version; } else { - git.short(function(str) { - callback (version + '+' + str); - }); + return version + '+' + git.short(); } } @@ -48,16 +46,53 @@ task('lint', {async: true}, hint('Checking for JS errors...', 'src')); desc('Check Leaflet specs source for errors with ESLint'); task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/suites')); -desc('Combine and compress Leaflet source files'); -task('build', {async: true}, function (compsBase32, buildName, officialRelease) { - calculateVersion(officialRelease, function(v){ - build.build(complete, v, compsBase32, buildName); - }); -}); - desc('Run PhantomJS tests'); task('test', ['lint', 'lintspec'], {async: true}, function () { - build.test(complete); + + var karma = require('karma'), + testConfig = {configFile : path.join(__dirname, './spec/karma.conf.js')}; + + testConfig.browsers = ['PhantomJSCustom']; + + function isArgv(optName) { + return process.argv.indexOf(optName) !== -1; + } + + if (isArgv('--chrome')) { + testConfig.browsers.push('Chrome'); + } + if (isArgv('--safari')) { + testConfig.browsers.push('Safari'); + } + if (isArgv('--ff')) { + testConfig.browsers.push('Firefox'); + } + if (isArgv('--ie')) { + testConfig.browsers.push('IE'); + } + + if (isArgv('--cov')) { + testConfig.preprocessors = { + 'src/**/*.js': 'coverage' + }; + testConfig.coverageReporter = { + type : 'html', + dir : 'coverage/' + }; + testConfig.reporters = ['coverage']; + } + + console.log('Running tests...'); + + var server = new karma.Server(testConfig, function(exitCode) { + if (!exitCode) { + console.log('\tTests ran successfully.\n'); + complete(); + } else { + process.exit(exitCode); + } + }); + server.start(); }); desc('Build documentation'); diff --git a/bower.json b/bower.json index a87c9f49..6b424959 100644 --- a/bower.json +++ b/bower.json @@ -6,7 +6,7 @@ "dist/leaflet.css", "dist/leaflet-src.js" ], - "ignore": [ + "ignore": [ ".*", "CHANGELOG.json", "FAQ.md", diff --git a/build/build.html b/build/build.html deleted file mode 100644 index f876582c..00000000 --- a/build/build.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - Leaflet Build Helper - - - - - - -
-

Leaflet Build Helper

- -

- Select All | - Deselect All -

- - - -

Building using Node and UglifyJS

-
    -
  1. Download and install Node
  2. -
  3. Run this in the command line:
    -
    npm install -g jake
    -npm install
  4. -
  5. Run this command inside the Leaflet directory:
    -
-
- - - - diff --git a/build/build.js b/build/build.js deleted file mode 100644 index c41f194c..00000000 --- a/build/build.js +++ /dev/null @@ -1,237 +0,0 @@ -var fs = require('fs'), - UglifyJS = require('uglify-js'), - zlib = require('zlib'), - SourceNode = require( 'source-map' ).SourceNode; - - deps = require('./deps.js').deps; - -function getFiles(compsBase32) { - var memo = {}, - comps; - - if (compsBase32) { - comps = parseInt(compsBase32, 32).toString(2).split(''); - console.log('Managing dependencies...'); - } - - function addFiles(srcs) { - for (var j = 0, len = srcs.length; j < len; j++) { - memo[srcs[j]] = true; - } - } - - for (var i in deps) { - if (comps) { - if (parseInt(comps.pop(), 2) === 1) { - console.log(' * ' + i); - addFiles(deps[i].src); - } else { - console.log(' ' + i); - } - } else { - addFiles(deps[i].src); - } - } - - console.log(''); - - var files = []; - - for (var src in memo) { - files.push('src/' + src); - } - - return files; -} - -exports.getFiles = getFiles; - -function getSizeDelta(newContent, oldContent, fixCRLF) { - if (!oldContent) { - return ' (new)'; - } - if (newContent === oldContent) { - return ' (unchanged)'; - } - if (fixCRLF) { - newContent = newContent.replace(/\r\n?/g, '\n'); - oldContent = oldContent.replace(/\r\n?/g, '\n'); - } - var delta = newContent.length - oldContent.length; - - return delta === 0 ? '' : ' (' + (delta > 0 ? '+' : '') + delta + ' bytes)'; -} - -function loadSilently(path) { - try { - return fs.readFileSync(path, 'utf8'); - } catch (e) { - return null; - } -} - -// Concatenate the files while building up a sourcemap for the concatenation, -// and replace the line defining L.version with the string prepared in the jakefile -function bundleFiles(files, copy, version) { - var node = new SourceNode(null, null, null, ''); - - node.add(new SourceNode(null, null, null, copy + '(function (window, document, undefined) {')); - - for (var i = 0, len = files.length; i < len; i++) { - var contents = fs.readFileSync(files[i], 'utf8'); - - if (files[i] === 'src/Leaflet.js') { - contents = contents.replace( - new RegExp('version: \'.*\''), - 'version: ' + JSON.stringify(version) - ); - } - - var lines = contents.split('\n'); - var lineCount = lines.length; - var fileNode = new SourceNode(null, null, null, ''); - - fileNode.setSourceContent(files[i], contents); - - for (var j=0; j ` + limit + `) { + var msg = "This rollupjs bundle is potentially old. Make sure you're running 'npm run-script watch' or 'yarn run watch'."; + alert(msg); + // throw new Error(msg); +} + +/* + * Leaflet ` + version + `, a JS library for interactive maps. http://leafletjs.com + * (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ + +`; + +export default { + format: 'umd', + moduleName: 'L', + banner: warningCode, + entry: 'src/Leaflet.js', + dest: 'debug/leaflet-rollup-src.js', + plugins: [ + rollupGitVersion() + ], + sourceMap: true, + legacy: true // Needed to create files loadable by IE8 +}; diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js deleted file mode 100644 index 7f8b67f2..00000000 --- a/debug/leaflet-include.js +++ /dev/null @@ -1,58 +0,0 @@ -(function() { - function getFiles() { - var memo = {}, - files = [], - i, src; - - function addFiles(srcs) { - for (var j = 0, len = srcs.length; j < len; j++) { - memo[srcs[j]] = true; - } - } - - for (i in deps) { - addFiles(deps[i].src); - } - - for (src in memo) { - files.push(src); - } - - return files; - } - var scripts = getFiles(); - - function getSrcUrl() { - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - var src = scripts[i].src; - if (src) { - var res = src.match(/^(.*)leaflet-include\.js$/); - if (res) { - return res[1] + '../src/'; - } - } - } - } - - var path = getSrcUrl(); - for (var i = 0; i < scripts.length; i++) { - document.writeln(""); - } -})(); - -function getRandomLatLng(map) { - var bounds = map.getBounds(), - southWest = bounds.getSouthWest(), - northEast = bounds.getNorthEast(), - lngSpan = northEast.lng - southWest.lng, - latSpan = northEast.lat - southWest.lat; - - return new L.LatLng( - southWest.lat + latSpan * Math.random(), - southWest.lng + lngSpan * Math.random()); -} - -function logEvent(e) { - console.log(e.type); -} diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js new file mode 120000 index 00000000..8681426b --- /dev/null +++ b/debug/leaflet-include.js @@ -0,0 +1 @@ +leaflet-rollup-src.js \ No newline at end of file diff --git a/debug/map/rollup.html b/debug/map/rollup.html new file mode 100644 index 00000000..f53ec501 --- /dev/null +++ b/debug/map/rollup.html @@ -0,0 +1,105 @@ + + + + Leaflet debug page + + + + + + + + + + + + + +
+ + + + + + + + diff --git a/debug/tests/touch-zoom-bounce.html b/debug/tests/touch-zoom-bounce.html index 7ff397b8..b3c52f6c 100644 --- a/debug/tests/touch-zoom-bounce.html +++ b/debug/tests/touch-zoom-bounce.html @@ -73,7 +73,8 @@ // }); - var hand = new Hand({timing: 'minimal'}); +// var hand = new Hand({timing: 'minimal'}); + var hand = new Hand({timing: 'frame'}); var f1 = hand.growFinger('touch'); var f2 = hand.growFinger('touch'); @@ -152,4 +153,4 @@ - \ No newline at end of file + diff --git a/docs/index.html b/docs/index.html index 841f3497..f1b7b737 100644 --- a/docs/index.html +++ b/docs/index.html @@ -5,7 +5,7 @@ layout: v2
Jan 23, 2017 — Leaflet 1.0.3, a bugfix release, is out.

Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps. -Weighing just about 33 KB of JS, +Weighing just about 38 KB of JS, it has all the mapping features most developers ever need.

Leaflet is designed with simplicity, performance and usability in mind. diff --git a/package.json b/package.json index c2ec59aa..50cc840c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "devDependencies": { "eslint": "^3.5.0 <3.6.0", "eslint-config-mourner": "^2.0.1", - "git-rev": "^0.2.1", + "git-rev-sync": "^1.8.0", "happen": "~0.3.1", "jake": "~8.0.12", "karma": "^1.3.0", @@ -14,20 +14,32 @@ "karma-firefox-launcher": "~1.0.0", "karma-mocha": "^1.2.0", "karma-phantomjs-launcher": "^1.0.2", + "karma-rollup-preprocessor": "^2.0.2", "karma-safari-launcher": "~1.0.0", "leafdoc": "^1.4.1", "mocha": "^3.1.0", "phantomjs-prebuilt": "^2.1.12", "prosthetic-hand": "^1.3.1", + "rollup": "^0.41.4", + "rollup-plugin-git-version": "0.2.1", + "rollup-plugin-json": "^2.1.0", + "rollup-watch": "^2.5.0", "source-map": "^0.5.6", "uglify-js": "~2.7.3" }, "main": "dist/leaflet-src.js", "style": "dist/leaflet.css", "scripts": { + "test-jake": "jake test", "test": "jake test", - "build": "jake build", - "release": "./build/publish.sh" + "build-jake": "jake build", + "build": "npm run rollup && npm run uglify", + "release": "./build/publish.sh", + "lint": "eslint src", + "lintfix": "eslint src --fix", + "rollup": "rollup -c build/rollup-config.js", + "watch": "rollup -w -c build/rollup-watch-config.js", + "uglify": "uglifyjs dist/leaflet-src.js -c -m -o dist/leaflet.js" }, "eslintConfig": { "root": true, @@ -40,7 +52,15 @@ "node": false }, "extends": "mourner", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, "rules": { + "linebreak-style": [ + 0, + "unix" + ], "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" diff --git a/spec/index.html b/spec/index.html index f7e84870..01443dda 100644 --- a/spec/index.html +++ b/spec/index.html @@ -19,7 +19,7 @@ // Trick Leaflet into believing we have a touchscreen (for Chrome) if (window.Touch) { window.ontouchstart = function(){} }; - + diff --git a/spec/karma.conf.js b/spec/karma.conf.js index ac2e0c6e..ad817795 100644 --- a/spec/karma.conf.js +++ b/spec/karma.conf.js @@ -1,12 +1,17 @@ +// var es3 = require('rollup-plugin-es3'); +var json = require('rollup-plugin-json'); + // Karma configuration module.exports = function (config) { - var libSources = require(__dirname + '/../build/build.js').getFiles(); +// var libSources = require(__dirname + '/../build/build.js').getFiles(); var files = [ "spec/sinon.js", - "spec/expect.js" - ].concat(libSources, [ + "spec/expect.js", + + "src/Leaflet.js", + "spec/after.js", "node_modules/happen/happen.js", "node_modules/prosthetic-hand/dist/prosthetic-hand.js", @@ -14,13 +19,14 @@ module.exports = function (config) { "spec/suites/**/*.js", "dist/*.css", {pattern: "dist/images/*.png", included: false, serve: true} - ]); + ]; config.set({ // base path, that will be used to resolve files and exclude basePath: '../', plugins: [ + 'karma-rollup-preprocessor', 'karma-mocha', 'karma-coverage', 'karma-phantomjs-launcher', @@ -38,6 +44,24 @@ module.exports = function (config) { }, exclude: [], + // Rollup the ES6 Leaflet sources into just one file, before tests + preprocessors: { + 'src/Leaflet.js': ['rollup'] + }, + rollupPreprocessor: { + rollup: { + plugins: [ +// es3(), + json() + ] + }, + bundle: { + format: 'umd', + moduleName: 'L' +// sourceMap: 'inline' + } + }, + // test results reporter to use // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' reporters: ['dots'], diff --git a/spec/suites/SpecHelper.js b/spec/suites/SpecHelper.js index 44b608b7..900757c0 100644 --- a/spec/suites/SpecHelper.js +++ b/spec/suites/SpecHelper.js @@ -55,6 +55,9 @@ happen.at = function (what, x, y, props) { // We'll want to skip a couple of things when in PhantomJS, due to lack of CSS animations it.skipInPhantom = L.Browser.any3d ? it : it.skip; +// Viceversa: some tests we want only to run in browsers without CSS animations. +it.skipInNonPhantom = L.Browser.any3d ? it.skip : it; + // A couple of tests need the browser to be touch-capable it.skipIfNotTouch = window.TouchEvent ? it : it.skip; diff --git a/spec/suites/layer/vector/PathSpec.js b/spec/suites/layer/vector/PathSpec.js index fca44980..527b78fa 100644 --- a/spec/suites/layer/vector/PathSpec.js +++ b/spec/suites/layer/vector/PathSpec.js @@ -1,14 +1,17 @@ describe('Path', function () { + + // The following two tests are skipped, as the ES6-ifycation of Leaflet + // means that L.Path is no longer visible. describe('#bringToBack', function () { it('is a no-op for layers not on a map', function () { - var path = new L.Path(); + var path = new L.Polyline([[1, 2], [3, 4], [5, 6]]); expect(path.bringToBack()).to.equal(path); }); }); describe('#bringToFront', function () { it('is a no-op for layers not on a map', function () { - var path = new L.Path(); + var path = new L.Polyline([[1, 2], [3, 4], [5, 6]]); expect(path.bringToFront()).to.equal(path); }); }); diff --git a/spec/suites/layer/vector/PolygonSpec.js b/spec/suites/layer/vector/PolygonSpec.js index 7383a8f4..65a1c064 100644 --- a/spec/suites/layer/vector/PolygonSpec.js +++ b/spec/suites/layer/vector/PolygonSpec.js @@ -12,7 +12,7 @@ describe('Polygon', function () { var polygon = new L.Polygon(latLngs); - expect(L.Polyline._flat(polygon._latlngs)).to.be(false); + expect(L.LineUtil._flat(polygon._latlngs)).to.be(false); expect(polygon.getLatLngs()).to.eql(polygon._latlngs); }); diff --git a/spec/suites/layer/vector/PolylineGeometrySpec.js b/spec/suites/layer/vector/PolylineGeometrySpec.js index 86a9e463..082df99e 100644 --- a/spec/suites/layer/vector/PolylineGeometrySpec.js +++ b/spec/suites/layer/vector/PolylineGeometrySpec.js @@ -33,3 +33,29 @@ describe('PolylineGeometry', function () { }); }); }); + +describe('LineUtil', function () { + describe('#_flat', function () { + var layer = L.polyline([]); + + it('should return true for an array of LatLngs', function () { + expect(L.LineUtil._flat([L.latLng([0, 0])])).to.be(true); + }); + + it('should return true for an array of LatLngs arrays', function () { + expect(L.LineUtil._flat([[0, 0]])).to.be(true); + }); + + it('should return true for an empty array', function () { + expect(L.LineUtil._flat([])).to.be(true); + }); + + it('should return false for a nested array of LatLngs', function () { + expect(L.LineUtil._flat([[L.latLng([0, 0])]])).to.be(false); + }); + + it('should return false for a nested empty array', function () { + expect(L.LineUtil._flat([[]])).to.be(false); + }); + }); +}); diff --git a/spec/suites/layer/vector/PolylineSpec.js b/spec/suites/layer/vector/PolylineSpec.js index 2c3c7bd2..a4b7ba38 100644 --- a/spec/suites/layer/vector/PolylineSpec.js +++ b/spec/suites/layer/vector/PolylineSpec.js @@ -144,30 +144,6 @@ describe('Polyline', function () { }); - describe('#_flat', function () { - var layer = L.polyline([]); - - it('should return true for an array of LatLngs', function () { - expect(L.Polyline._flat([L.latLng([0, 0])])).to.be(true); - }); - - it('should return true for an array of LatLngs arrays', function () { - expect(L.Polyline._flat([[0, 0]])).to.be(true); - }); - - it('should return true for an empty array', function () { - expect(L.Polyline._flat([])).to.be(true); - }); - - it('should return false for a nested array of LatLngs', function () { - expect(L.Polyline._flat([[L.latLng([0, 0])]])).to.be(false); - }); - - it('should return false for a nested empty array', function () { - expect(L.Polyline._flat([[]])).to.be(false); - }); - - }); describe("#_defaultShape", function () { diff --git a/spec/suites/map/MapSpec.js b/spec/suites/map/MapSpec.js index 78645448..953cced9 100644 --- a/spec/suites/map/MapSpec.js +++ b/spec/suites/map/MapSpec.js @@ -188,11 +188,11 @@ describe("Map", function () { expect(map.getBoundsZoom(bounds, false, padding)).to.be.equal(19); }); - it("returns multiples of zoomSnap when zoomSnap > 0 on any3d browsers", function () { + it.skipInPhantom("returns multiples of zoomSnap when zoomSnap > 0 on any3d browsers", function () { var container = map.getContainer(); container.style.height = height; document.body.appendChild(container); - L.Browser.any3d = true; +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.options.zoomSnap = 0.5; expect(map.getBoundsZoom(bounds, false, padding)).to.be.equal(19.5); map.options.zoomSnap = 0.2; @@ -770,8 +770,8 @@ describe("Map", function () { map.zoomOut(null, {animate: false}); }); - it('zoomIn ignores the zoomDelta option on non-any3d browsers', function (done) { - L.Browser.any3d = false; + it.skipInNonPhantom('zoomIn ignores the zoomDelta option on non-any3d browsers', function (done) { +// L.Browser.any3d = false; // L.Browser is frozen since ES6ication map.options.zoomSnap = 0.25; map.options.zoomDelta = 0.25; map.once('zoomend', function () { @@ -782,8 +782,8 @@ describe("Map", function () { map.zoomIn(null, {animate: false}); }); - it('zoomIn respects the zoomDelta option on any3d browsers', function (done) { - L.Browser.any3d = true; + it.skipInPhantom('zoomIn respects the zoomDelta option on any3d browsers', function (done) { +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.options.zoomSnap = 0.25; map.options.zoomDelta = 0.25; map.setView(center, 10); @@ -795,8 +795,8 @@ describe("Map", function () { map.zoomIn(null, {animate: false}); }); - it('zoomOut respects the zoomDelta option on any3d browsers', function (done) { - L.Browser.any3d = true; + it.skipInPhantom('zoomOut respects the zoomDelta option on any3d browsers', function (done) { +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.options.zoomSnap = 0.25; map.options.zoomDelta = 0.25; map.setView(center, 10); @@ -808,7 +808,7 @@ describe("Map", function () { map.zoomOut(null, {animate: false}); }); - it('zoomIn snaps to zoomSnap on any3d browsers', function (done) { + it.skipInPhantom('zoomIn snaps to zoomSnap on any3d browsers', function (done) { map.options.zoomSnap = 0.25; map.setView(center, 10); map.once('zoomend', function () { @@ -816,11 +816,11 @@ describe("Map", function () { expect(map.getCenter()).to.eql(center); done(); }); - L.Browser.any3d = true; +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.zoomIn(0.22, {animate: false}); }); - it('zoomOut snaps to zoomSnap on any3d browsers', function (done) { + it.skipInPhantom('zoomOut snaps to zoomSnap on any3d browsers', function (done) { map.options.zoomSnap = 0.25; map.setView(center, 10); map.once('zoomend', function () { @@ -828,7 +828,7 @@ describe("Map", function () { expect(map.getCenter()).to.eql(center); done(); }); - L.Browser.any3d = true; +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.zoomOut(0.22, {animate: false}); }); }); @@ -859,9 +859,9 @@ describe("Map", function () { map.fitBounds(bounds, {animate: false}); }); - it('Snaps zoom to zoomSnap on any3d browsers', function (done) { + it.skipInPhantom('Snaps zoom to zoomSnap on any3d browsers', function (done) { map.options.zoomSnap = 0.25; - L.Browser.any3d = true; +// L.Browser.any3d = true; // L.Browser is frozen since ES6ication map.once('zoomend', function () { expect(map.getZoom()).to.eql(2.75); expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true); @@ -870,9 +870,9 @@ describe("Map", function () { map.fitBounds(bounds, {animate: false}); }); - it('Ignores zoomSnap on non-any3d browsers', function (done) { + it.skipInNonPhantom('Ignores zoomSnap on non-any3d browsers', function (done) { map.options.zoomSnap = 0.25; - L.Browser.any3d = false; +// L.Browser.any3d = false; // L.Browser is frozen since ES6ication map.once('zoomend', function () { expect(map.getZoom()).to.eql(2); expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true); diff --git a/src/Leaflet.js b/src/Leaflet.js index 21ab842c..010dd608 100644 --- a/src/Leaflet.js +++ b/src/Leaflet.js @@ -1,29 +1,170 @@ -var L = { - version: '1.0.3' -}; +import {version} from '../package.json'; +export {version}; -function expose() { - var oldL = window.L; +// control - L.noConflict = function () { - window.L = oldL; - return this; - }; +import {Control, control} from './control/Control'; +import {Layers, layers} from './control/Control.Layers'; +import {Zoom, zoom} from './control/Control.Zoom'; +import {Scale, scale} from './control/Control.Scale'; +import {Attribution, attribution} from './control/Control.Attribution.js'; - window.L = L; -} - -// define Leaflet for Node module pattern loaders, including Browserify -if (typeof module === 'object' && typeof module.exports === 'object') { - module.exports = L; - -// define Leaflet as an AMD module -} else if (typeof define === 'function' && define.amd) { - define(L); -} - -// define Leaflet as a global L variable, saving the original L to restore later if needed -if (typeof window !== 'undefined') { - expose(); +Control.Layers = Layers; +Control.Zoom = Zoom; +Control.Scale = Scale; +Control.Attribution = Attribution; + +control.layers = layers; +control.zoom = zoom; +control.scale = scale; +control.attribution = attribution; + +export {Control, control}; + +// core + +import * as Browser from './core/Browser'; +export {Browser}; + +export {Class} from './core/Class'; + +import {Evented} from './core/Events'; +export {Evented}; +// export var Mixin = {Events: Evented.prototype}; + +export {Handler} from './core/Handler'; + +import * as Util from './core/Util'; +export {Util}; +export {extend, bind, stamp, setOptions} from './core/Util'; + +// dom + +export {PosAnimation} from './dom/PosAnimation'; + +import * as DomEvent from './dom/DomEvent'; +export {DomEvent}; + +import * as DomUtil from './dom/DomUtil'; +export {DomUtil}; + +export {Draggable} from './dom/Draggable'; + +// geometry + +export {Point, toPoint as point} from './geometry/Point'; +export {Bounds, toBounds as bounds} from './geometry/Bounds'; +export {Transformation, toTransformation as transformation} from './geometry/Transformation'; + +// geo + +export {LatLng, toLatLng as latLng} from './geo/LatLng'; +export {LatLngBounds, toLatLngBounds as latLngBounds} from './geo/LatLngBounds'; + +// geo/projection + +import * as Projection from './geo/projection/Projection'; +export {Projection}; + +// geo/crs + +import {CRS} from './geo/crs/CRS'; +import {Earth} from './geo/crs/CRS.Earth'; +import {EPSG3395} from './geo/crs/CRS.EPSG3395'; +import {EPSG3857, EPSG900913} from './geo/crs/CRS.EPSG3857'; +import {EPSG4326} from './geo/crs/CRS.EPSG4326'; +import {Simple} from './geo/crs/CRS.Simple'; + +CRS.Earth = Earth; +CRS.EPSG3395 = EPSG3395; +CRS.EPSG3857 = EPSG3857; +CRS.EPSG900913 = EPSG900913; +CRS.EPSG4326 = EPSG4326; +CRS.Simple = Simple; + +export {CRS}; + +// layer + +export {Layer} from './layer/Layer'; +export {LayerGroup, layerGroup} from './layer/LayerGroup'; +export {FeatureGroup, featureGroup} from './layer/FeatureGroup'; +import {GeoJSON, geoJSON, geoJson, geometryToLayer, coordsToLatLng, coordsToLatLngs, latLngToCoords, latLngsToCoords, getFeature, asFeature} from './layer/GeoJSON'; +GeoJSON.geometryToLayer = geometryToLayer; +GeoJSON.coordsToLatLng = coordsToLatLng; +GeoJSON.coordsToLatLngs = coordsToLatLngs; +GeoJSON.latLngToCoords = latLngToCoords; +GeoJSON.latLngsToCoords = latLngsToCoords; +GeoJSON.getFeature = getFeature; +GeoJSON.asFeature = asFeature; +export {GeoJSON, geoJSON, geoJson}; + +export {ImageOverlay, imageOverlay} from './layer/ImageOverlay'; + +export {DivOverlay} from './layer/DivOverlay'; +export {Popup, popup} from './layer/Popup'; +export {Tooltip, tooltip} from './layer/Tooltip'; + +import {Icon} from './layer/marker/Icon'; +export {icon} from './layer/marker/Icon'; +import {IconDefault} from './layer/marker/Icon.Default'; +Icon.Default = IconDefault; +export {Icon}; + +export {DivIcon, divIcon} from './layer/marker/DivIcon'; +export {Marker, marker} from './layer/marker/Marker'; + +// layer, tile +export {GridLayer, gridLayer} from './layer/tile/GridLayer'; +import {TileLayer, tileLayer} from './layer/tile/TileLayer'; +import {TileLayerWMS, tileLayerWMS} from './layer/tile/TileLayer.WMS'; +TileLayer.WMS = TileLayerWMS; +tileLayer.wms = tileLayerWMS; +export {TileLayer, tileLayer}; + +// layer, vector +export {Renderer} from './layer/vector/Renderer'; +export {Canvas, canvas} from './layer/vector/Canvas'; +export {SVG, svg} from './layer/vector/SVG'; +import './layer/vector/Renderer.getRenderer'; // This is a bit of a hack, but needed because circular dependencies + +export {Path} from './layer/vector/Path'; +export {CircleMarker, circleMarker} from './layer/vector/CircleMarker'; +export {Circle, circle} from './layer/vector/Circle'; +export {Polyline, polyline} from './layer/vector/Polyline'; +export {Polygon, polygon} from './layer/vector/Polygon'; +export {Rectangle, rectangle} from './layer/vector/Rectangle'; + +import * as LineUtil from './geometry/LineUtil'; +export {LineUtil}; +import * as PolyUtil from './geometry/PolyUtil'; +export {PolyUtil}; + +// map + +import {Map} from './map/Map'; +import {BoxZoom} from './map/handler/Map.BoxZoom'; +Map.BoxZoom = BoxZoom; +import {DoubleClickZoom} from './map/handler/Map.DoubleClickZoom'; +Map.DoubleClickZoom = DoubleClickZoom; +import {Drag} from './map/handler/Map.Drag'; +Map.Drag = Drag; +import {Keyboard} from './map/handler/Map.Keyboard'; +Map.Keyboard = Keyboard; +import {ScrollWheelZoom} from './map/handler/Map.ScrollWheelZoom'; +Map.ScrollWheelZoom = ScrollWheelZoom; +import {Tap} from './map/handler/Map.Tap'; +Map.Tap = Tap; +import {TouchZoom} from './map/handler/Map.TouchZoom'; +Map.TouchZoom = TouchZoom; + +export {Map, createMap as map} from './map/Map'; + +// misc + +var oldL = window.L; +export function noConflict() { + window.L = oldL; + return this; } diff --git a/src/control/Control.Attribution.js b/src/control/Control.Attribution.js index 2ed04cd4..49cd478e 100644 --- a/src/control/Control.Attribution.js +++ b/src/control/Control.Attribution.js @@ -1,3 +1,10 @@ + +import {Control} from './Control'; +import {Map} from '../map/Map'; +import * as Util from '../core/Util'; +import * as DomEvent from '../dom/DomEvent'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class Control.Attribution * @aka L.Control.Attribution @@ -6,7 +13,7 @@ * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control. */ -L.Control.Attribution = L.Control.extend({ +export var Attribution = Control.extend({ // @section // @aka Control.Attribution options options: { @@ -18,17 +25,15 @@ L.Control.Attribution = L.Control.extend({ }, initialize: function (options) { - L.setOptions(this, options); + Util.setOptions(this, options); this._attributions = {}; }, onAdd: function (map) { map.attributionControl = this; - this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); - if (L.DomEvent) { - L.DomEvent.disableClickPropagation(this._container); - } + this._container = DomUtil.create('div', 'leaflet-control-attribution'); + DomEvent.disableClickPropagation(this._container); // TODO ugly, refactor for (var i in map._layers) { @@ -106,19 +111,19 @@ L.Control.Attribution = L.Control.extend({ // @section Control options // @option attributionControl: Boolean = true // Whether a [attribution control](#control-attribution) is added to the map by default. -L.Map.mergeOptions({ +Map.mergeOptions({ attributionControl: true }); -L.Map.addInitHook(function () { +Map.addInitHook(function () { if (this.options.attributionControl) { - new L.Control.Attribution().addTo(this); + new Attribution().addTo(this); } }); // @namespace Control.Attribution // @factory L.control.attribution(options: Control.Attribution options) // Creates an attribution control. -L.control.attribution = function (options) { - return new L.Control.Attribution(options); +export var attribution = function (options) { + return new Attribution(options); }; diff --git a/src/control/Control.Layers.js b/src/control/Control.Layers.js index de5ad016..f306886c 100644 --- a/src/control/Control.Layers.js +++ b/src/control/Control.Layers.js @@ -1,3 +1,10 @@ + +import {Control} from './Control'; +import * as Util from '../core/Util'; +import * as Browser from '../core/Browser'; +import * as DomEvent from '../dom/DomEvent'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class Control.Layers * @aka L.Control.Layers @@ -37,8 +44,7 @@ * ``` */ - -L.Control.Layers = L.Control.extend({ +export var Layers = Control.extend({ // @section // @aka Control.Layers options options: { @@ -72,7 +78,7 @@ L.Control.Layers = L.Control.extend({ }, initialize: function (baseLayers, overlays, options) { - L.setOptions(this, options); + Util.setOptions(this, options); this._layers = []; this._lastZIndex = 0; @@ -124,7 +130,7 @@ L.Control.Layers = L.Control.extend({ removeLayer: function (layer) { layer.off('add remove', this._onLayerChange, this); - var obj = this._getLayer(L.stamp(layer)); + var obj = this._getLayer(Util.stamp(layer)); if (obj) { this._layers.splice(this._layers.indexOf(obj), 1); } @@ -134,14 +140,14 @@ L.Control.Layers = L.Control.extend({ // @method expand(): this // Expand the control container if collapsed. expand: function () { - L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); + DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); this._form.style.height = null; var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); if (acceptableHeight < this._form.clientHeight) { - L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar'); + DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar'); this._form.style.height = acceptableHeight + 'px'; } else { - L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); + DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); } this._checkDisabledLayers(); return this; @@ -150,51 +156,50 @@ L.Control.Layers = L.Control.extend({ // @method collapse(): this // Collapse the control container if expanded. collapse: function () { - L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); + DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); return this; }, _initLayout: function () { var className = 'leaflet-control-layers', - container = this._container = L.DomUtil.create('div', className), + container = this._container = DomUtil.create('div', className), collapsed = this.options.collapsed; // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released container.setAttribute('aria-haspopup', true); - L.DomEvent.disableClickPropagation(container); - if (!L.Browser.touch) { - L.DomEvent.disableScrollPropagation(container); + DomEvent.disableClickPropagation(container); + if (!Browser.touch) { + DomEvent.disableScrollPropagation(container); } - var form = this._form = L.DomUtil.create('form', className + '-list'); + var form = this._form = DomUtil.create('form', className + '-list'); if (collapsed) { this._map.on('click', this.collapse, this); - if (!L.Browser.android) { - L.DomEvent.on(container, { + if (!Browser.android) { + DomEvent.on(container, { mouseenter: this.expand, mouseleave: this.collapse }, this); } } - var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); + var link = this._layersLink = DomUtil.create('a', className + '-toggle', container); link.href = '#'; link.title = 'Layers'; - if (L.Browser.touch) { - L.DomEvent - .on(link, 'click', L.DomEvent.stop) - .on(link, 'click', this.expand, this); + if (Browser.touch) { + DomEvent.on(link, 'click', DomEvent.stop); + DomEvent.on(link, 'click', this.expand, this); } else { - L.DomEvent.on(link, 'focus', this.expand, this); + DomEvent.on(link, 'focus', this.expand, this); } // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033 - L.DomEvent.on(form, 'click', function () { - setTimeout(L.bind(this._onInputClick, this), 0); + DomEvent.on(form, 'click', function () { + setTimeout(Util.bind(this._onInputClick, this), 0); }, this); // TODO keyboard accessibility @@ -203,9 +208,9 @@ L.Control.Layers = L.Control.extend({ this.expand(); } - this._baseLayersList = L.DomUtil.create('div', className + '-base', form); - this._separator = L.DomUtil.create('div', className + '-separator', form); - this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); + this._baseLayersList = DomUtil.create('div', className + '-base', form); + this._separator = DomUtil.create('div', className + '-separator', form); + this._overlaysList = DomUtil.create('div', className + '-overlays', form); container.appendChild(form); }, @@ -213,7 +218,7 @@ L.Control.Layers = L.Control.extend({ _getLayer: function (id) { for (var i = 0; i < this._layers.length; i++) { - if (this._layers[i] && L.stamp(this._layers[i].layer) === id) { + if (this._layers[i] && Util.stamp(this._layers[i].layer) === id) { return this._layers[i]; } } @@ -243,8 +248,8 @@ L.Control.Layers = L.Control.extend({ _update: function () { if (!this._container) { return this; } - L.DomUtil.empty(this._baseLayersList); - L.DomUtil.empty(this._overlaysList); + DomUtil.empty(this._baseLayersList); + DomUtil.empty(this._overlaysList); var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; @@ -272,7 +277,7 @@ L.Control.Layers = L.Control.extend({ this._update(); } - var obj = this._getLayer(L.stamp(e.target)); + var obj = this._getLayer(Util.stamp(e.target)); // @namespace Map // @section Layer events @@ -318,9 +323,9 @@ L.Control.Layers = L.Control.extend({ input = this._createRadioElement('leaflet-base-layers', checked); } - input.layerId = L.stamp(obj.layer); + input.layerId = Util.stamp(obj.layer); - L.DomEvent.on(input, 'click', this._onInputClick, this); + DomEvent.on(input, 'click', this._onInputClick, this); var name = document.createElement('span'); name.innerHTML = ' ' + obj.name; @@ -404,6 +409,6 @@ L.Control.Layers = L.Control.extend({ // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options) // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation. -L.control.layers = function (baseLayers, overlays, options) { - return new L.Control.Layers(baseLayers, overlays, options); +export var layers = function (baseLayers, overlays, options) { + return new Layers(baseLayers, overlays, options); }; diff --git a/src/control/Control.Scale.js b/src/control/Control.Scale.js index cbb12c80..99aa927e 100644 --- a/src/control/Control.Scale.js +++ b/src/control/Control.Scale.js @@ -1,3 +1,7 @@ + +import {Control} from './Control'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class Control.Scale * @aka L.Control.Scale @@ -12,7 +16,7 @@ * ``` */ -L.Control.Scale = L.Control.extend({ +export var Scale = Control.extend({ // @section // @aka Control.Scale options options: { @@ -36,7 +40,7 @@ L.Control.Scale = L.Control.extend({ onAdd: function (map) { var className = 'leaflet-control-scale', - container = L.DomUtil.create('div', className), + container = DomUtil.create('div', className), options = this.options; this._addScales(options, className + '-line', container); @@ -53,10 +57,10 @@ L.Control.Scale = L.Control.extend({ _addScales: function (options, className, container) { if (options.metric) { - this._mScale = L.DomUtil.create('div', className, container); + this._mScale = DomUtil.create('div', className, container); } if (options.imperial) { - this._iScale = L.DomUtil.create('div', className, container); + this._iScale = DomUtil.create('div', className, container); } }, @@ -123,6 +127,6 @@ L.Control.Scale = L.Control.extend({ // @factory L.control.scale(options?: Control.Scale options) // Creates an scale control with the given options. -L.control.scale = function (options) { - return new L.Control.Scale(options); +export var scale = function (options) { + return new Scale(options); }; diff --git a/src/control/Control.Zoom.js b/src/control/Control.Zoom.js index 0345d887..e0f42be5 100644 --- a/src/control/Control.Zoom.js +++ b/src/control/Control.Zoom.js @@ -1,3 +1,9 @@ + +import {Control} from './Control'; +import {Map} from '../map/Map'; +import * as DomUtil from '../dom/DomUtil'; +import * as DomEvent from '../dom/DomEvent'; + /* * @class Control.Zoom * @aka L.Control.Zoom @@ -6,7 +12,7 @@ * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`. */ -L.Control.Zoom = L.Control.extend({ +export var Zoom = Control.extend({ // @section // @aka Control.Zoom options options: { @@ -31,7 +37,7 @@ L.Control.Zoom = L.Control.extend({ onAdd: function (map) { var zoomName = 'leaflet-control-zoom', - container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), + container = DomUtil.create('div', zoomName + ' leaflet-bar'), options = this.options; this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, @@ -74,7 +80,7 @@ L.Control.Zoom = L.Control.extend({ }, _createButton: function (html, title, className, container, fn) { - var link = L.DomUtil.create('a', className, container); + var link = DomUtil.create('a', className, container); link.innerHTML = html; link.href = '#'; link.title = title; @@ -85,11 +91,10 @@ L.Control.Zoom = L.Control.extend({ link.setAttribute('role', 'button'); link.setAttribute('aria-label', title); - L.DomEvent - .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) - .on(link, 'click', L.DomEvent.stop) - .on(link, 'click', fn, this) - .on(link, 'click', this._refocusOnMap, this); + DomEvent.on(link, 'mousedown dblclick', DomEvent.stopPropagation); + DomEvent.on(link, 'click', DomEvent.stop); + DomEvent.on(link, 'click', fn, this); + DomEvent.on(link, 'click', this._refocusOnMap, this); return link; }, @@ -98,14 +103,14 @@ L.Control.Zoom = L.Control.extend({ var map = this._map, className = 'leaflet-disabled'; - L.DomUtil.removeClass(this._zoomInButton, className); - L.DomUtil.removeClass(this._zoomOutButton, className); + DomUtil.removeClass(this._zoomInButton, className); + DomUtil.removeClass(this._zoomOutButton, className); if (this._disabled || map._zoom === map.getMinZoom()) { - L.DomUtil.addClass(this._zoomOutButton, className); + DomUtil.addClass(this._zoomOutButton, className); } if (this._disabled || map._zoom === map.getMaxZoom()) { - L.DomUtil.addClass(this._zoomInButton, className); + DomUtil.addClass(this._zoomInButton, className); } } }); @@ -114,13 +119,13 @@ L.Control.Zoom = L.Control.extend({ // @section Control options // @option zoomControl: Boolean = true // Whether a [zoom control](#control-zoom) is added to the map by default. -L.Map.mergeOptions({ +Map.mergeOptions({ zoomControl: true }); -L.Map.addInitHook(function () { +Map.addInitHook(function () { if (this.options.zoomControl) { - this.zoomControl = new L.Control.Zoom(); + this.zoomControl = new Zoom(); this.addControl(this.zoomControl); } }); @@ -128,6 +133,6 @@ L.Map.addInitHook(function () { // @namespace Control.Zoom // @factory L.control.zoom(options: Control.Zoom options) // Creates a zoom control -L.control.zoom = function (options) { - return new L.Control.Zoom(options); +export var zoom = function (options) { + return new Zoom(options); }; diff --git a/src/control/Control.js b/src/control/Control.js index a7b74a45..bf57d1d8 100644 --- a/src/control/Control.js +++ b/src/control/Control.js @@ -1,3 +1,9 @@ + +import {Class} from '../core/Class'; +import {Map} from '../map/Map'; +import * as Util from '../core/Util'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class Control * @aka L.Control @@ -7,7 +13,7 @@ * All other controls extend from this class. */ -L.Control = L.Class.extend({ +export var Control = Class.extend({ // @section // @aka Control options options: { @@ -18,7 +24,7 @@ L.Control = L.Class.extend({ }, initialize: function (options) { - L.setOptions(this, options); + Util.setOptions(this, options); }, /* @section @@ -65,7 +71,7 @@ L.Control = L.Class.extend({ pos = this.getPosition(), corner = map._controlCorners[pos]; - L.DomUtil.addClass(container, 'leaflet-control'); + DomUtil.addClass(container, 'leaflet-control'); if (pos.indexOf('bottom') !== -1) { corner.insertBefore(container, corner.firstChild); @@ -83,7 +89,7 @@ L.Control = L.Class.extend({ return this; } - L.DomUtil.remove(this._container); + DomUtil.remove(this._container); if (this.onRemove) { this.onRemove(this._map); @@ -102,8 +108,8 @@ L.Control = L.Class.extend({ } }); -L.control = function (options) { - return new L.Control(options); +export var control = function (options) { + return new Control(options); }; /* @section Extension methods @@ -121,7 +127,7 @@ L.control = function (options) { /* @namespace Map * @section Methods for Layers and Controls */ -L.Map.include({ +Map.include({ // @method addControl(control: Control): this // Adds the given control to the map addControl: function (control) { @@ -140,12 +146,12 @@ L.Map.include({ var corners = this._controlCorners = {}, l = 'leaflet-', container = this._controlContainer = - L.DomUtil.create('div', l + 'control-container', this._container); + DomUtil.create('div', l + 'control-container', this._container); function createCorner(vSide, hSide) { var className = l + vSide + ' ' + l + hSide; - corners[vSide + hSide] = L.DomUtil.create('div', className, container); + corners[vSide + hSide] = DomUtil.create('div', className, container); } createCorner('top', 'left'); @@ -155,6 +161,6 @@ L.Map.include({ }, _clearControlPos: function () { - L.DomUtil.remove(this._controlContainer); + DomUtil.remove(this._controlContainer); } }); diff --git a/src/copyright.js b/src/copyright.js deleted file mode 100644 index 6730041e..00000000 --- a/src/copyright.js +++ /dev/null @@ -1,4 +0,0 @@ -/* - Leaflet {VERSION}, a JS library for interactive maps. http://leafletjs.com - (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade -*/ diff --git a/src/core/Browser.js b/src/core/Browser.js index 9fcbe2cd..33316f34 100644 --- a/src/core/Browser.js +++ b/src/core/Browser.js @@ -1,3 +1,5 @@ +import {svgCreate} from '../layer/vector/SVG.Util'; + /* * @namespace Browser * @aka L.Browser @@ -13,139 +15,128 @@ * ``` */ -(function () { +var style = document.documentElement.style; - var ua = navigator.userAgent.toLowerCase(), - doc = document.documentElement, +// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). +export var ie = 'ActiveXObject' in window; - ie = 'ActiveXObject' in window, +// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. +export var ielt9 = ie && !document.addEventListener; - webkit = ua.indexOf('webkit') !== -1, - phantomjs = ua.indexOf('phantom') !== -1, - android23 = ua.search('android [23]') !== -1, - chrome = ua.indexOf('chrome') !== -1, - gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, +// @property edge: Boolean; `true` for the Edge web browser. +export var edge = 'msLaunchUri' in navigator && !('documentMode' in document); - win = navigator.platform.indexOf('Win') === 0, +// @property webkit: Boolean; +// `true` for webkit-based browsers like Chrome and Safari (including mobile versions). +export var webkit = userAgentContains('webkit'); - mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, - msPointer = !window.PointerEvent && window.MSPointerEvent, - pointer = window.PointerEvent || msPointer, +// @property android: Boolean +// `true` for any browser running on an Android platform. +export var android = userAgentContains('android'); - ie3d = ie && ('transition' in doc.style), - webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, - gecko3d = 'MozPerspective' in doc.style, - opera12 = 'OTransition' in doc.style; +// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3. +export var android23 = userAgentContains('android 2') || userAgentContains('android 3'); + +// @property opera: Boolean; `true` for the Opera browser +export var opera = !!window.opera; + +// @property chrome: Boolean; `true` for the Chrome browser. +export var chrome = userAgentContains('chrome'); + +// @property gecko: Boolean; `true` for gecko-based browsers like Firefox. +export var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; + +// @property safari: Boolean; `true` for the Safari browser. +export var safari = !chrome && userAgentContains('safari'); + +export var phantom = userAgentContains('phantom'); + +// @property opera12: Boolean +// `true` for the Opera browser supporting CSS transforms (version 12 or later). +export var opera12 = 'OTransition' in style; + +// @property win: Boolean; `true` when the browser is running in a Windows platform +export var win = navigator.platform.indexOf('Win') === 0; + +// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. +export var ie3d = ie && ('transition' in style); + +// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. +export var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; + +// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. +export var gecko3d = 'MozPerspective' in style; + +// @property any3d: Boolean +// `true` for all browsers supporting CSS transforms. +export var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; + +// @property mobile: Boolean; `true` for all browsers running in a mobile device. +export var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); + +// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. +export var mobileWebkit = mobile && webkit; + +// @property mobileWebkit3d: Boolean +// `true` for all webkit-based browsers in a mobile device supporting CSS transforms. +export var mobileWebkit3d = mobile && webkit3d; + +// @property msPointer: Boolean +// `true` for browsers implementing the Microsoft touch events model (notably IE10). +export var msPointer = !window.PointerEvent && window.MSPointerEvent; + +// @property pointer: Boolean +// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). +export var pointer = !!(window.PointerEvent || msPointer); + +// @property touch: Boolean +// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). +// This does not necessarily mean that the browser is running in a computer with +// a touchscreen, it only means that the browser is capable of understanding +// touch events. +export var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || + (window.DocumentTouch && document instanceof window.DocumentTouch)); + +// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. +export var mobileOpera = mobile && opera; + +// @property mobileGecko: Boolean +// `true` for gecko-based browsers running in a mobile device. +export var mobileGecko = mobile && gecko; + +// @property retina: Boolean +// `true` for browsers on a high-resolution "retina" screen. +export var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; - var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window || - (window.DocumentTouch && document instanceof window.DocumentTouch)); - - L.Browser = { - - // @property ie: Boolean - // `true` for all Internet Explorer versions (not Edge). - ie: ie, - - // @property ielt9: Boolean - // `true` for Internet Explorer versions less than 9. - ielt9: ie && !document.addEventListener, - - // @property edge: Boolean - // `true` for the Edge web browser. - edge: 'msLaunchUri' in navigator && !('documentMode' in document), - - // @property webkit: Boolean - // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). - webkit: webkit, - - // @property gecko: Boolean - // `true` for gecko-based browsers like Firefox. - gecko: gecko, - - // @property android: Boolean - // `true` for any browser running on an Android platform. - android: ua.indexOf('android') !== -1, - - // @property android23: Boolean - // `true` for browsers running on Android 2 or Android 3. - android23: android23, - - // @property chrome: Boolean - // `true` for the Chrome browser. - chrome: chrome, - - // @property safari: Boolean - // `true` for the Safari browser. - safari: !chrome && ua.indexOf('safari') !== -1, - - - // @property win: Boolean - // `true` when the browser is running in a Windows platform - win: win, - - - // @property ie3d: Boolean - // `true` for all Internet Explorer versions supporting CSS transforms. - ie3d: ie3d, - - // @property webkit3d: Boolean - // `true` for webkit-based browsers supporting CSS transforms. - webkit3d: webkit3d, - - // @property gecko3d: Boolean - // `true` for gecko-based browsers supporting CSS transforms. - gecko3d: gecko3d, - - // @property opera12: Boolean - // `true` for the Opera browser supporting CSS transforms (version 12 or later). - opera12: opera12, - - // @property any3d: Boolean - // `true` for all browsers supporting CSS transforms. - any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, - - - // @property mobile: Boolean - // `true` for all browsers running in a mobile device. - mobile: mobile, - - // @property mobileWebkit: Boolean - // `true` for all webkit-based browsers in a mobile device. - mobileWebkit: mobile && webkit, - - // @property mobileWebkit3d: Boolean - // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. - mobileWebkit3d: mobile && webkit3d, - - // @property mobileOpera: Boolean - // `true` for the Opera browser in a mobile device. - mobileOpera: mobile && window.opera, - - // @property mobileGecko: Boolean - // `true` for gecko-based browsers running in a mobile device. - mobileGecko: mobile && gecko, - - - // @property touch: Boolean - // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). - // This does not necessarily mean that the browser is running in a computer with - // a touchscreen, it only means that the browser is capable of understanding - // touch events. - touch: !!touch, - - // @property msPointer: Boolean - // `true` for browsers implementing the Microsoft touch events model (notably IE10). - msPointer: !!msPointer, - - // @property pointer: Boolean - // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). - pointer: !!pointer, - - - // @property retina: Boolean - // `true` for browsers on a high-resolution "retina" screen. - retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 - }; - +// @property canvas: Boolean +// `true` when the browser supports [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). +export var canvas = (function () { + return !!document.createElement('canvas').getContext; }()); + +// @property svg: Boolean +// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). +export var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect); + +// @property vml: Boolean +// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). +export var vml = !svg && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } +}()); + + +function userAgentContains(str) { + return navigator.userAgent.toLowerCase().indexOf(str) >= 0; +} diff --git a/src/core/Class.js b/src/core/Class.js index af678764..ecf0f035 100644 --- a/src/core/Class.js +++ b/src/core/Class.js @@ -1,3 +1,4 @@ +import * as Util from './Util'; // @class Class // @aka L.Class @@ -7,9 +8,9 @@ // Thanks to John Resig and Dean Edwards for inspiration! -L.Class = function () {}; +export function Class() {} -L.Class.extend = function (props) { +Class.extend = function (props) { // @function extend(props: Object): Function // [Extends the current class](#class-inheritance) given the properties to be included. @@ -27,7 +28,7 @@ L.Class.extend = function (props) { var parentProto = NewClass.__super__ = this.prototype; - var proto = L.Util.create(parentProto); + var proto = Util.create(parentProto); proto.constructor = NewClass; NewClass.prototype = proto; @@ -41,23 +42,23 @@ L.Class.extend = function (props) { // mix static properties into the class if (props.statics) { - L.extend(NewClass, props.statics); + Util.extend(NewClass, props.statics); delete props.statics; } // mix includes into the prototype if (props.includes) { - L.Util.extend.apply(null, [proto].concat(props.includes)); + Util.extend.apply(null, [proto].concat(props.includes)); delete props.includes; } // merge options if (proto.options) { - props.options = L.Util.extend(L.Util.create(proto.options), props.options); + props.options = Util.extend(Util.create(proto.options), props.options); } // mix given properties into the prototype - L.extend(proto, props); + Util.extend(proto, props); proto._initHooks = []; @@ -83,21 +84,21 @@ L.Class.extend = function (props) { // @function include(properties: Object): this // [Includes a mixin](#class-includes) into the current class. -L.Class.include = function (props) { - L.extend(this.prototype, props); +Class.include = function (props) { + Util.extend(this.prototype, props); return this; }; // @function mergeOptions(options: Object): this // [Merges `options`](#class-options) into the defaults of the class. -L.Class.mergeOptions = function (options) { - L.extend(this.prototype.options, options); +Class.mergeOptions = function (options) { + Util.extend(this.prototype.options, options); return this; }; // @function addInitHook(fn: Function): this // Adds a [constructor hook](#class-constructor-hooks) to the class. -L.Class.addInitHook = function (fn) { // (Function) || (String, args...) +Class.addInitHook = function (fn) { // (Function) || (String, args...) var args = Array.prototype.slice.call(arguments, 1); var init = typeof fn === 'function' ? fn : function () { diff --git a/src/core/Events.js b/src/core/Events.js index ae019383..17d15edb 100644 --- a/src/core/Events.js +++ b/src/core/Events.js @@ -1,3 +1,6 @@ +import {Class} from './Class'; +import * as Util from './Util'; + /* * @class Evented * @aka L.Evented @@ -23,8 +26,7 @@ * ``` */ - -L.Evented = L.Class.extend({ +export var Evented = Class.extend({ /* @method on(type: String, fn: Function, context?: Object): this * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). @@ -45,7 +47,7 @@ L.Evented = L.Class.extend({ } else { // types can be a string of space-separated words - types = L.Util.splitWords(types); + types = Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._on(types[i], fn, context); @@ -78,7 +80,7 @@ L.Evented = L.Class.extend({ } } else { - types = L.Util.splitWords(types); + types = Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._off(types[i], fn, context); @@ -132,7 +134,7 @@ L.Evented = L.Class.extend({ if (!fn) { // Set all removed listeners to noop so they are not called if remove happens in fire for (i = 0, len = listeners.length; i < len; i++) { - listeners[i].fn = L.Util.falseFn; + listeners[i].fn = Util.falseFn; } // clear all listeners for a type if function isn't specified delete this._events[type]; @@ -152,7 +154,7 @@ L.Evented = L.Class.extend({ if (l.fn === fn) { // set the removed listener to noop so that's not called if remove happens in fire - l.fn = L.Util.falseFn; + l.fn = Util.falseFn; if (this._firingCount) { /* copy array in case events are being fired */ @@ -173,7 +175,7 @@ L.Evented = L.Class.extend({ fire: function (type, data, propagate) { if (!this.listens(type, propagate)) { return this; } - var event = L.Util.extend({}, data, {type: type, target: this}); + var event = Util.extend({}, data, {type: type, target: this}); if (this._events) { var listeners = this._events[type]; @@ -223,7 +225,7 @@ L.Evented = L.Class.extend({ return this; } - var handler = L.bind(function () { + var handler = Util.bind(function () { this .off(types, fn, context) .off(types, handler, context); @@ -239,7 +241,7 @@ L.Evented = L.Class.extend({ // Adds an event parent - an `Evented` that will receive propagated events addEventParent: function (obj) { this._eventParents = this._eventParents || {}; - this._eventParents[L.stamp(obj)] = obj; + this._eventParents[Util.stamp(obj)] = obj; return this; }, @@ -247,19 +249,19 @@ L.Evented = L.Class.extend({ // Removes an event parent, so it will stop receiving propagated events removeEventParent: function (obj) { if (this._eventParents) { - delete this._eventParents[L.stamp(obj)]; + delete this._eventParents[Util.stamp(obj)]; } return this; }, _propagateEvent: function (e) { for (var id in this._eventParents) { - this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true); + this._eventParents[id].fire(e.type, Util.extend({layer: e.target}, e), true); } } }); -var proto = L.Evented.prototype; +var proto = Evented.prototype; // aliases; we should ditch those eventually @@ -285,5 +287,3 @@ proto.fireEvent = proto.fire; // @method hasEventListeners(…): Boolean // Alias to [`listens(…)`](#evented-listens) proto.hasEventListeners = proto.listens; - -L.Mixin = {Events: proto}; diff --git a/src/core/Handler.js b/src/core/Handler.js index cb7a8cf7..c02f6d09 100644 --- a/src/core/Handler.js +++ b/src/core/Handler.js @@ -1,3 +1,5 @@ +import {Class} from './Class'; + /* L.Handler is a base class for handler classes that are used internally to inject interaction features like dragging to classes like Map and Marker. @@ -7,7 +9,7 @@ // @aka L.Handler // Abstract class for map interaction handlers -L.Handler = L.Class.extend({ +export var Handler = Class.extend({ initialize: function (map) { this._map = map; }, diff --git a/src/core/Util.js b/src/core/Util.js index b38f8ff1..8d31fbdc 100644 --- a/src/core/Util.js +++ b/src/core/Util.js @@ -4,247 +4,233 @@ * Various utility functions, used by Leaflet internally. */ -L.Util = { +// @function extend(dest: Object, src?: Object): Object +// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. +export function extend(dest) { + var i, j, len, src; - // @function extend(dest: Object, src?: Object): Object - // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. - extend: function (dest) { - var i, j, len, src; - - for (j = 1, len = arguments.length; j < len; j++) { - src = arguments[j]; - for (i in src) { - dest[i] = src[i]; - } + for (j = 1, len = arguments.length; j < len; j++) { + src = arguments[j]; + for (i in src) { + dest[i] = src[i]; } - return dest; - }, - - // @function create(proto: Object, properties?: Object): Object - // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) - create: Object.create || (function () { - function F() {} - return function (proto) { - F.prototype = proto; - return new F(); - }; - })(), - - // @function bind(fn: Function, …): Function - // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). - // Has a `L.bind()` shortcut. - bind: function (fn, obj) { - var slice = Array.prototype.slice; - - if (fn.bind) { - return fn.bind.apply(fn, slice.call(arguments, 1)); - } - - var args = slice.call(arguments, 2); - - return function () { - return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); - }; - }, - - // @function stamp(obj: Object): Number - // Returns the unique ID of an object, assiging it one if it doesn't have it. - stamp: function (obj) { - /*eslint-disable */ - obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId; - return obj._leaflet_id; - /*eslint-enable */ - }, - - // @property lastId: Number - // Last unique ID used by [`stamp()`](#util-stamp) - lastId: 0, - - // @function throttle(fn: Function, time: Number, context: Object): Function - // Returns a function which executes function `fn` with the given scope `context` - // (so that the `this` keyword refers to `context` inside `fn`'s code). The function - // `fn` will be called no more than one time per given amount of `time`. The arguments - // received by the bound function will be any arguments passed when binding the - // function, followed by any arguments passed when invoking the bound function. - // Has an `L.bind` shortcut. - throttle: function (fn, time, context) { - var lock, args, wrapperFn, later; - - later = function () { - // reset lock and call if queued - lock = false; - if (args) { - wrapperFn.apply(context, args); - args = false; - } - }; - - wrapperFn = function () { - if (lock) { - // called too soon, queue to call later - args = arguments; - - } else { - // call and lock until later - fn.apply(context, arguments); - setTimeout(later, time); - lock = true; - } - }; - - return wrapperFn; - }, - - // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number - // Returns the number `num` modulo `range` in such a way so it lies within - // `range[0]` and `range[1]`. The returned value will be always smaller than - // `range[1]` unless `includeMax` is set to `true`. - wrapNum: function (x, range, includeMax) { - var max = range[1], - min = range[0], - d = max - min; - return x === max && includeMax ? x : ((x - min) % d + d) % d + min; - }, - - // @function falseFn(): Function - // Returns a function which always returns `false`. - falseFn: function () { return false; }, - - // @function formatNum(num: Number, digits?: Number): Number - // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default. - formatNum: function (num, digits) { - var pow = Math.pow(10, digits || 5); - return Math.round(num * pow) / pow; - }, - - // @function trim(str: String): String - // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) - trim: function (str) { - return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); - }, - - // @function splitWords(str: String): String[] - // Trims and splits the string on whitespace and returns the array of parts. - splitWords: function (str) { - return L.Util.trim(str).split(/\s+/); - }, - - // @function setOptions(obj: Object, options: Object): Object - // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. - setOptions: function (obj, options) { - if (!obj.hasOwnProperty('options')) { - obj.options = obj.options ? L.Util.create(obj.options) : {}; - } - for (var i in options) { - obj.options[i] = options[i]; - } - return obj.options; - }, - - // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String - // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` - // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will - // be appended at the end. If `uppercase` is `true`, the parameter names will - // be uppercased (e.g. `'?A=foo&B=bar'`) - getParamString: function (obj, existingUrl, uppercase) { - var params = []; - for (var i in obj) { - params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); - } - return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); - }, - - // @function template(str: String, data: Object): String - // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` - // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string - // `('Hello foo, bar')`. You can also specify functions instead of strings for - // data values — they will be evaluated passing `data` as an argument. - template: function (str, data) { - return str.replace(L.Util.templateRe, function (str, key) { - var value = data[key]; - - if (value === undefined) { - throw new Error('No value provided for variable ' + str); - - } else if (typeof value === 'function') { - value = value(data); - } - return value; - }); - }, - - templateRe: /\{ *([\w_\-]+) *\}/g, - - // @function isArray(obj): Boolean - // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) - isArray: Array.isArray || function (obj) { - return (Object.prototype.toString.call(obj) === '[object Array]'); - }, - - // @function indexOf(array: Array, el: Object): Number - // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) - indexOf: function (array, el) { - for (var i = 0; i < array.length; i++) { - if (array[i] === el) { return i; } - } - return -1; - }, - - // @property emptyImageUrl: String - // Data URI string containing a base64-encoded empty GIF image. - // Used as a hack to free memory from unused images on WebKit-powered - // mobile devices (by setting image `src` to this string). - emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' -}; - -(function () { - // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - - function getPrefixed(name) { - return window['webkit' + name] || window['moz' + name] || window['ms' + name]; } + return dest; +} - var lastTime = 0; - - // fallback for IE 7-8 - function timeoutDefer(fn) { - var time = +new Date(), - timeToCall = Math.max(0, 16 - (time - lastTime)); - - lastTime = time + timeToCall; - return window.setTimeout(fn, timeToCall); - } - - var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer, - cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || - getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; - - - // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number - // Schedules `fn` to be executed when the browser repaints. `fn` is bound to - // `context` if given. When `immediate` is set, `fn` is called immediately if - // the browser doesn't have native support for - // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), - // otherwise it's delayed. Returns a request ID that can be used to cancel the request. - L.Util.requestAnimFrame = function (fn, context, immediate) { - if (immediate && requestFn === timeoutDefer) { - fn.call(context); - } else { - return requestFn.call(window, L.bind(fn, context)); - } - }; - - // @function cancelAnimFrame(id: Number): undefined - // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). - L.Util.cancelAnimFrame = function (id) { - if (id) { - cancelFn.call(window, id); - } +// @function create(proto: Object, properties?: Object): Object +// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) +export var create = Object.create || (function () { + function F() {} + return function (proto) { + F.prototype = proto; + return new F(); }; })(); -// shortcuts for most used utility functions -L.extend = L.Util.extend; -L.bind = L.Util.bind; -L.stamp = L.Util.stamp; -L.setOptions = L.Util.setOptions; +// @function bind(fn: Function, …): Function +// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). +// Has a `L.bind()` shortcut. +export function bind(fn, obj) { + var slice = Array.prototype.slice; + + if (fn.bind) { + return fn.bind.apply(fn, slice.call(arguments, 1)); + } + + var args = slice.call(arguments, 2); + + return function () { + return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); + }; +} + +var lastId = 0; + +// @function stamp(obj: Object): Number +// Returns the unique ID of an object, assiging it one if it doesn't have it. +export function stamp(obj) { + /*eslint-disable */ + obj._leaflet_id = obj._leaflet_id || ++lastId; + return obj._leaflet_id; + /*eslint-enable */ +} + +// @function throttle(fn: Function, time: Number, context: Object): Function +// Returns a function which executes function `fn` with the given scope `context` +// (so that the `this` keyword refers to `context` inside `fn`'s code). The function +// `fn` will be called no more than one time per given amount of `time`. The arguments +// received by the bound function will be any arguments passed when binding the +// function, followed by any arguments passed when invoking the bound function. +// Has an `L.throttle` shortcut. +export function throttle(fn, time, context) { + var lock, args, wrapperFn, later; + + later = function () { + // reset lock and call if queued + lock = false; + if (args) { + wrapperFn.apply(context, args); + args = false; + } + }; + + wrapperFn = function () { + if (lock) { + // called too soon, queue to call later + args = arguments; + + } else { + // call and lock until later + fn.apply(context, arguments); + setTimeout(later, time); + lock = true; + } + }; + + return wrapperFn; +} + +// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number +// Returns the number `num` modulo `range` in such a way so it lies within +// `range[0]` and `range[1]`. The returned value will be always smaller than +// `range[1]` unless `includeMax` is set to `true`. +export function wrapNum(x, range, includeMax) { + var max = range[1], + min = range[0], + d = max - min; + return x === max && includeMax ? x : ((x - min) % d + d) % d + min; +} + +// @function falseFn(): Function +// Returns a function which always returns `false`. +export function falseFn() { return false; } + +// @function formatNum(num: Number, digits?: Number): Number +// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default. +export function formatNum(num, digits) { + var pow = Math.pow(10, digits || 5); + return Math.round(num * pow) / pow; +} + +// @function trim(str: String): String +// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) +export function trim(str) { + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); +} + +// @function splitWords(str: String): String[] +// Trims and splits the string on whitespace and returns the array of parts. +export function splitWords(str) { + return trim(str).split(/\s+/); +} + +// @function setOptions(obj: Object, options: Object): Object +// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. +export function setOptions(obj, options) { + if (!obj.hasOwnProperty('options')) { + obj.options = obj.options ? create(obj.options) : {}; + } + for (var i in options) { + obj.options[i] = options[i]; + } + return obj.options; +} + +// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String +// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` +// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will +// be appended at the end. If `uppercase` is `true`, the parameter names will +// be uppercased (e.g. `'?A=foo&B=bar'`) +export function getParamString(obj, existingUrl, uppercase) { + var params = []; + for (var i in obj) { + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); + } + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); +} + +var templateRe = /\{ *([\w_\-]+) *\}/g; + +// @function template(str: String, data: Object): String +// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` +// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string +// `('Hello foo, bar')`. You can also specify functions instead of strings for +// data values — they will be evaluated passing `data` as an argument. +export function template(str, data) { + return str.replace(templateRe, function (str, key) { + var value = data[key]; + + if (value === undefined) { + throw new Error('No value provided for variable ' + str); + + } else if (typeof value === 'function') { + value = value(data); + } + return value; + }); +} + +// @function isArray(obj): Boolean +// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) +export var isArray = Array.isArray || function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); +}; + +// @function indexOf(array: Array, el: Object): Number +// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) +export function indexOf(array, el) { + for (var i = 0; i < array.length; i++) { + if (array[i] === el) { return i; } + } + return -1; +} + +// @property emptyImageUrl: String +// Data URI string containing a base64-encoded empty GIF image. +// Used as a hack to free memory from unused images on WebKit-powered +// mobile devices (by setting image `src` to this string). +export var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; + +// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/ + +function getPrefixed(name) { + return window['webkit' + name] || window['moz' + name] || window['ms' + name]; +} + +var lastTime = 0; + +// fallback for IE 7-8 +function timeoutDefer(fn) { + var time = +new Date(), + timeToCall = Math.max(0, 16 - (time - lastTime)); + + lastTime = time + timeToCall; + return window.setTimeout(fn, timeToCall); +} + +export var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; +export var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || + getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; + +// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number +// Schedules `fn` to be executed when the browser repaints. `fn` is bound to +// `context` if given. When `immediate` is set, `fn` is called immediately if +// the browser doesn't have native support for +// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), +// otherwise it's delayed. Returns a request ID that can be used to cancel the request. +export function requestAnimFrame(fn, context, immediate) { + if (immediate && requestFn === timeoutDefer) { + fn.call(context); + } else { + return requestFn.call(window, bind(fn, context)); + } +} + +// @function cancelAnimFrame(id: Number): undefined +// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). +export function cancelAnimFrame(id) { + if (id) { + cancelFn.call(window, id); + } +} diff --git a/src/dom/DomEvent.DoubleTap.js b/src/dom/DomEvent.DoubleTap.js index 26d3325a..733096ee 100644 --- a/src/dom/DomEvent.DoubleTap.js +++ b/src/dom/DomEvent.DoubleTap.js @@ -1,91 +1,86 @@ +import * as Browser from '../core/Browser'; +import {_pointersCount} from './DomEvent.Pointer'; + /* * Extends the event handling code with double tap support for mobile browsers. */ -L.extend(L.DomEvent, { +var _touchstart = Browser.msPointer ? 'MSPointerDown' : Browser.pointer ? 'pointerdown' : 'touchstart', + _touchend = Browser.msPointer ? 'MSPointerUp' : Browser.pointer ? 'pointerup' : 'touchend', + _pre = '_leaflet_'; - _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', - _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', +// inspired by Zepto touch code by Thomas Fuchs +export function addDoubleTapListener(obj, handler, id) { + var last, touch, + doubleTap = false, + delay = 250; - // inspired by Zepto touch code by Thomas Fuchs - addDoubleTapListener: function (obj, handler, id) { - var last, touch, - doubleTap = false, - delay = 250; + function onTouchStart(e) { + var count; - function onTouchStart(e) { - var count; - - if (L.Browser.pointer) { - if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; } - count = L.DomEvent._pointersCount; - } else { - count = e.touches.length; - } - - if (count > 1) { return; } - - var now = Date.now(), - delta = now - (last || now); - - touch = e.touches ? e.touches[0] : e; - doubleTap = (delta > 0 && delta <= delay); - last = now; + if (Browser.pointer) { + if ((!Browser.edge) || e.pointerType === 'mouse') { return; } + count = _pointersCount; + } else { + count = e.touches.length; } - function onTouchEnd(e) { - if (doubleTap && !touch.cancelBubble) { - if (L.Browser.pointer) { - if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; } + if (count > 1) { return; } - // work around .type being readonly with MSPointer* events - var newTouch = {}, - prop, i; + var now = Date.now(), + delta = now - (last || now); - for (i in touch) { - prop = touch[i]; - newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; - } - touch = newTouch; - } - touch.type = 'dblclick'; - handler(touch); - last = null; - } - } - - var pre = '_leaflet_', - touchstart = this._touchstart, - touchend = this._touchend; - - obj[pre + touchstart + id] = onTouchStart; - obj[pre + touchend + id] = onTouchEnd; - obj[pre + 'dblclick' + id] = handler; - - obj.addEventListener(touchstart, onTouchStart, false); - obj.addEventListener(touchend, onTouchEnd, false); - - // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), - // the browser doesn't fire touchend/pointerup events but does fire - // native dblclicks. See #4127. - // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. - obj.addEventListener('dblclick', handler, false); - - return this; - }, - - removeDoubleTapListener: function (obj, id) { - var pre = '_leaflet_', - touchstart = obj[pre + this._touchstart + id], - touchend = obj[pre + this._touchend + id], - dblclick = obj[pre + 'dblclick' + id]; - - obj.removeEventListener(this._touchstart, touchstart, false); - obj.removeEventListener(this._touchend, touchend, false); - if (!L.Browser.edge) { - obj.removeEventListener('dblclick', dblclick, false); - } - - return this; + touch = e.touches ? e.touches[0] : e; + doubleTap = (delta > 0 && delta <= delay); + last = now; } -}); + + function onTouchEnd(e) { + if (doubleTap && !touch.cancelBubble) { + if (Browser.pointer) { + if ((!Browser.edge) || e.pointerType === 'mouse') { return; } + // work around .type being readonly with MSPointer* events + var newTouch = {}, + prop, i; + + for (i in touch) { + prop = touch[i]; + newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; + } + touch = newTouch; + } + touch.type = 'dblclick'; + handler(touch); + last = null; + } + } + + obj[_pre + _touchstart + id] = onTouchStart; + obj[_pre + _touchend + id] = onTouchEnd; + obj[_pre + 'dblclick' + id] = handler; + + obj.addEventListener(_touchstart, onTouchStart, false); + obj.addEventListener(_touchend, onTouchEnd, false); + + // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse), + // the browser doesn't fire touchend/pointerup events but does fire + // native dblclicks. See #4127. + // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180. + obj.addEventListener('dblclick', handler, false); + + return this; +} + +export function removeDoubleTapListener(obj, id) { + var touchstart = obj[_pre + _touchstart + id], + touchend = obj[_pre + _touchend + id], + dblclick = obj[_pre + 'dblclick' + id]; + + obj.removeEventListener(_touchstart, touchstart, false); + obj.removeEventListener(_touchend, touchend, false); + if (!Browser.edge) { + obj.removeEventListener('dblclick', dblclick, false); + } + + return this; +} diff --git a/src/dom/DomEvent.Pointer.js b/src/dom/DomEvent.Pointer.js index 6932c121..3a130ebd 100644 --- a/src/dom/DomEvent.Pointer.js +++ b/src/dom/DomEvent.Pointer.js @@ -1,131 +1,135 @@ +import * as DomEvent from './DomEvent'; +import * as Util from '../core/Util'; +import * as Browser from '../core/Browser'; + /* * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. */ -L.extend(L.DomEvent, { - POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', - POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', - POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', - POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', - TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'], +var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown', + POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove', + POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup', + POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', + TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'], - _pointers: {}, - _pointersCount: 0, + _pointers = {}, + _pointerDocListener = false; - // Provides a touch events wrapper for (ms)pointer events. - // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 +// DomEvent.DoubleTap needs to know about this +export var _pointersCount = 0; - addPointerListener: function (obj, type, handler, id) { +// Provides a touch events wrapper for (ms)pointer events. +// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 - if (type === 'touchstart') { - this._addPointerStart(obj, handler, id); +export function addPointerListener(obj, type, handler, id) { - } else if (type === 'touchmove') { - this._addPointerMove(obj, handler, id); + if (type === 'touchstart') { + _addPointerStart(obj, handler, id); - } else if (type === 'touchend') { - this._addPointerEnd(obj, handler, id); - } + } else if (type === 'touchmove') { + _addPointerMove(obj, handler, id); - return this; - }, - - removePointerListener: function (obj, type, id) { - var handler = obj['_leaflet_' + type + id]; - - if (type === 'touchstart') { - obj.removeEventListener(this.POINTER_DOWN, handler, false); - - } else if (type === 'touchmove') { - obj.removeEventListener(this.POINTER_MOVE, handler, false); - - } else if (type === 'touchend') { - obj.removeEventListener(this.POINTER_UP, handler, false); - obj.removeEventListener(this.POINTER_CANCEL, handler, false); - } - - return this; - }, - - _addPointerStart: function (obj, handler, id) { - var onDown = L.bind(function (e) { - if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { - // In IE11, some touch events needs to fire for form controls, or - // the controls will stop working. We keep a whitelist of tag names that - // need these events. For other target tags, we prevent default on the event. - if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { - L.DomEvent.preventDefault(e); - } else { - return; - } - } - - this._handlePointer(e, handler); - }, this); - - obj['_leaflet_touchstart' + id] = onDown; - obj.addEventListener(this.POINTER_DOWN, onDown, false); - - // need to keep track of what pointers and how many are active to provide e.touches emulation - if (!this._pointerDocListener) { - var pointerUp = L.bind(this._globalPointerUp, this); - - // we listen documentElement as any drags that end by moving the touch off the screen get fired there - document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true); - document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true); - document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true); - document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true); - - this._pointerDocListener = true; - } - }, - - _globalPointerDown: function (e) { - this._pointers[e.pointerId] = e; - this._pointersCount++; - }, - - _globalPointerMove: function (e) { - if (this._pointers[e.pointerId]) { - this._pointers[e.pointerId] = e; - } - }, - - _globalPointerUp: function (e) { - delete this._pointers[e.pointerId]; - this._pointersCount--; - }, - - _handlePointer: function (e, handler) { - e.touches = []; - for (var i in this._pointers) { - e.touches.push(this._pointers[i]); - } - e.changedTouches = [e]; - - handler(e); - }, - - _addPointerMove: function (obj, handler, id) { - var onMove = L.bind(function (e) { - // don't fire touch moves when mouse isn't down - if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } - - this._handlePointer(e, handler); - }, this); - - obj['_leaflet_touchmove' + id] = onMove; - obj.addEventListener(this.POINTER_MOVE, onMove, false); - }, - - _addPointerEnd: function (obj, handler, id) { - var onUp = L.bind(function (e) { - this._handlePointer(e, handler); - }, this); - - obj['_leaflet_touchend' + id] = onUp; - obj.addEventListener(this.POINTER_UP, onUp, false); - obj.addEventListener(this.POINTER_CANCEL, onUp, false); + } else if (type === 'touchend') { + _addPointerEnd(obj, handler, id); } -}); + + return this; +} + +export function removePointerListener(obj, type, id) { + var handler = obj['_leaflet_' + type + id]; + + if (type === 'touchstart') { + obj.removeEventListener(POINTER_DOWN, handler, false); + + } else if (type === 'touchmove') { + obj.removeEventListener(POINTER_MOVE, handler, false); + + } else if (type === 'touchend') { + obj.removeEventListener(POINTER_UP, handler, false); + obj.removeEventListener(POINTER_CANCEL, handler, false); + } + + return this; +} + +function _addPointerStart(obj, handler, id) { + var onDown = Util.bind(function (e) { + if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { + // In IE11, some touch events needs to fire for form controls, or + // the controls will stop working. We keep a whitelist of tag names that + // need these events. For other target tags, we prevent default on the event. + if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) { + DomEvent.preventDefault(e); + } else { + return; + } + } + + _handlePointer(e, handler); + }); + + obj['_leaflet_touchstart' + id] = onDown; + obj.addEventListener(POINTER_DOWN, onDown, false); + + // need to keep track of what pointers and how many are active to provide e.touches emulation + if (!_pointerDocListener) { + // we listen documentElement as any drags that end by moving the touch off the screen get fired there + document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true); + document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true); + document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true); + document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true); + + _pointerDocListener = true; + } +} + +function _globalPointerDown(e) { + _pointers[e.pointerId] = e; + _pointersCount++; +} + +function _globalPointerMove(e) { + if (_pointers[e.pointerId]) { + _pointers[e.pointerId] = e; + } +} + +function _globalPointerUp(e) { + delete _pointers[e.pointerId]; + _pointersCount--; +} + +function _handlePointer(e, handler) { + e.touches = []; + for (var i in _pointers) { + e.touches.push(_pointers[i]); + } + e.changedTouches = [e]; + + handler(e); +} + +function _addPointerMove(obj, handler, id) { + var onMove = function (e) { + // don't fire touch moves when mouse isn't down + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } + + _handlePointer(e, handler); + }; + + obj['_leaflet_touchmove' + id] = onMove; + obj.addEventListener(POINTER_MOVE, onMove, false); +} + +function _addPointerEnd(obj, handler, id) { + var onUp = function (e) { + _handlePointer(e, handler); + }; + + obj['_leaflet_touchend' + id] = onUp; + obj.addEventListener(POINTER_UP, onUp, false); + obj.addEventListener(POINTER_CANCEL, onUp, false); +} + diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 6ebaf987..22a02047 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -1,3 +1,9 @@ +import {Point} from '../geometry/Point'; +import * as Util from '../core/Util'; +import * as Browser from '../core/Browser'; +import {addPointerListener, removePointerListener} from './DomEvent.Pointer'; +import {addDoubleTapListener, removeDoubleTapListener} from './DomEvent.DoubleTap'; + /* * @namespace DomEvent * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. @@ -5,307 +11,295 @@ // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. +// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Adds a listener function (`fn`) to a particular DOM event type of the +// element `el`. You can optionally specify the context of the listener +// (object the `this` keyword will point to). You can also pass several +// space-separated types (e.g. `'click dblclick'`). +// @alternative +// @function on(el: HTMLElement, eventMap: Object, context?: Object): this +// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +export function on(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + addOne(obj, type, types[type], fn); + } + } else { + types = Util.splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + addOne(obj, types[i], fn, context); + } + } + + return this; +} + +// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this +// Removes a previously added listener function. If no function is specified, +// it will remove all the listeners of that particular DOM event from the element. +// Note that if you passed a custom context to on, you must pass the same +// context to `off` in order to remove the listener. + +// @alternative +// @function off(el: HTMLElement, eventMap: Object, context?: Object): this +// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` +export function off(obj, types, fn, context) { + + if (typeof types === 'object') { + for (var type in types) { + removeOne(obj, type, types[type], fn); + } + } else { + types = Util.splitWords(types); + + for (var i = 0, len = types.length; i < len; i++) { + removeOne(obj, types[i], fn, context); + } + } + + return this; +} var eventsKey = '_leaflet_events'; -L.DomEvent = { +function addOne(obj, type, fn, context) { + var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : ''); - // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this - // Adds a listener function (`fn`) to a particular DOM event type of the - // element `el`. You can optionally specify the context of the listener - // (object the `this` keyword will point to). You can also pass several - // space-separated types (e.g. `'click dblclick'`). + if (obj[eventsKey] && obj[eventsKey][id]) { return this; } - // @alternative - // @function on(el: HTMLElement, eventMap: Object, context?: Object): this - // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` - on: function (obj, types, fn, context) { + var handler = function (e) { + return fn.call(context || obj, e || window.event); + }; - if (typeof types === 'object') { - for (var type in types) { - this._on(obj, type, types[type], fn); - } - } else { - types = L.Util.splitWords(types); + var originalHandler = handler; - for (var i = 0, len = types.length; i < len; i++) { - this._on(obj, types[i], fn, context); - } - } + if (Browser.pointer && type.indexOf('touch') === 0) { + // Needs DomEvent.Pointer.js + addPointerListener(obj, type, handler, id); - return this; - }, + } else if (Browser.touch && (type === 'dblclick') && addDoubleTapListener && + !(Browser.pointer && Browser.chrome)) { + // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener + // See #5180 + addDoubleTapListener(obj, handler, id); - // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this - // Removes a previously added listener function. If no function is specified, - // it will remove all the listeners of that particular DOM event from the element. - // Note that if you passed a custom context to on, you must pass the same - // context to `off` in order to remove the listener. + } else if ('addEventListener' in obj) { - // @alternative - // @function off(el: HTMLElement, eventMap: Object, context?: Object): this - // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` - off: function (obj, types, fn, context) { + if (type === 'mousewheel') { + obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); - if (typeof types === 'object') { - for (var type in types) { - this._off(obj, type, types[type], fn); - } - } else { - types = L.Util.splitWords(types); - - for (var i = 0, len = types.length; i < len; i++) { - this._off(obj, types[i], fn, context); - } - } - - return this; - }, - - _on: function (obj, type, fn, context) { - var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''); - - if (obj[eventsKey] && obj[eventsKey][id]) { return this; } - - var handler = function (e) { - return fn.call(context || obj, e || window.event); - }; - - var originalHandler = handler; - - if (L.Browser.pointer && type.indexOf('touch') === 0) { - this.addPointerListener(obj, type, handler, id); - - } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener && - !(L.Browser.pointer && L.Browser.chrome)) { - // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener - // See #5180 - this.addDoubleTapListener(obj, handler, id); - - } else if ('addEventListener' in obj) { - - if (type === 'mousewheel') { - obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); - - } else if ((type === 'mouseenter') || (type === 'mouseleave')) { - handler = function (e) { - e = e || window.event; - if (L.DomEvent._isExternalTarget(obj, e)) { - originalHandler(e); - } - }; - obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); - - } else { - if (type === 'click' && L.Browser.android) { - handler = function (e) { - return L.DomEvent._filterClick(e, originalHandler); - }; + } else if ((type === 'mouseenter') || (type === 'mouseleave')) { + handler = function (e) { + e = e || window.event; + if (isExternalTarget(obj, e)) { + originalHandler(e); } - obj.addEventListener(type, handler, false); - } + }; + obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); - } else if ('attachEvent' in obj) { - obj.attachEvent('on' + type, handler); - } - - obj[eventsKey] = obj[eventsKey] || {}; - obj[eventsKey][id] = handler; - - return this; - }, - - _off: function (obj, type, fn, context) { - - var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''), - handler = obj[eventsKey] && obj[eventsKey][id]; - - if (!handler) { return this; } - - if (L.Browser.pointer && type.indexOf('touch') === 0) { - this.removePointerListener(obj, type, id); - - } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { - this.removeDoubleTapListener(obj, id); - - } else if ('removeEventListener' in obj) { - - if (type === 'mousewheel') { - obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); - - } else { - obj.removeEventListener( - type === 'mouseenter' ? 'mouseover' : - type === 'mouseleave' ? 'mouseout' : type, handler, false); - } - - } else if ('detachEvent' in obj) { - obj.detachEvent('on' + type, handler); - } - - obj[eventsKey][id] = null; - - return this; - }, - - // @function stopPropagation(ev: DOMEvent): this - // Stop the given event from propagation to parent elements. Used inside the listener functions: - // ```js - // L.DomEvent.on(div, 'click', function (ev) { - // L.DomEvent.stopPropagation(ev); - // }); - // ``` - stopPropagation: function (e) { - - if (e.stopPropagation) { - e.stopPropagation(); - } else if (e.originalEvent) { // In case of Leaflet event. - e.originalEvent._stopped = true; } else { - e.cancelBubble = true; - } - L.DomEvent._skipped(e); - - return this; - }, - - // @function disableScrollPropagation(el: HTMLElement): this - // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). - disableScrollPropagation: function (el) { - return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation); - }, - - // @function disableClickPropagation(el: HTMLElement): this - // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, - // `'mousedown'` and `'touchstart'` events (plus browser variants). - disableClickPropagation: function (el) { - var stop = L.DomEvent.stopPropagation; - - L.DomEvent.on(el, L.Draggable.START.join(' '), stop); - - return L.DomEvent.on(el, { - click: L.DomEvent._fakeStop, - dblclick: stop - }); - }, - - // @function preventDefault(ev: DOMEvent): this - // Prevents the default action of the DOM Event `ev` from happening (such as - // following a link in the href of the a element, or doing a POST request - // with page reload when a `

` is submitted). - // Use it inside listener functions. - preventDefault: function (e) { - - if (e.preventDefault) { - e.preventDefault(); - } else { - e.returnValue = false; - } - return this; - }, - - // @function stop(ev): this - // Does `stopPropagation` and `preventDefault` at the same time. - stop: function (e) { - return L.DomEvent - .preventDefault(e) - .stopPropagation(e); - }, - - // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point - // Gets normalized mouse position from a DOM event relative to the - // `container` or to the whole page if not specified. - getMousePosition: function (e, container) { - if (!container) { - return new L.Point(e.clientX, e.clientY); - } - - var rect = container.getBoundingClientRect(); - - return new L.Point( - e.clientX - rect.left - container.clientLeft, - e.clientY - rect.top - container.clientTop); - }, - - // Chrome on Win scrolls double the pixels as in other platforms (see #4538), - // and Firefox scrolls device pixels, not CSS pixels - _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 : - L.Browser.gecko ? window.devicePixelRatio : - 1, - - // @function getWheelDelta(ev: DOMEvent): Number - // Gets normalized wheel delta from a mousewheel DOM event, in vertical - // pixels scrolled (negative if scrolling down). - // Events from pointing devices without precise scrolling are mapped to - // a best guess of 60 pixels. - getWheelDelta: function (e) { - return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta - (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels - (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines - (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages - (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events - e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels - (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines - e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages - 0; - }, - - _skipEvents: {}, - - _fakeStop: function (e) { - // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) - L.DomEvent._skipEvents[e.type] = true; - }, - - _skipped: function (e) { - var skipped = this._skipEvents[e.type]; - // reset when checking, as it's only used in map container and propagates outside of the map - this._skipEvents[e.type] = false; - return skipped; - }, - - // check if element really left/entered the event target (for mouseenter/mouseleave) - _isExternalTarget: function (el, e) { - - var related = e.relatedTarget; - - if (!related) { return true; } - - try { - while (related && (related !== el)) { - related = related.parentNode; + if (type === 'click' && Browser.android) { + handler = function (e) { + filterClick(e, originalHandler); + }; } - } catch (err) { - return false; + obj.addEventListener(type, handler, false); } - return (related !== el); - }, - // this is a horrible workaround for a bug in Android where a single touch triggers two click events - _filterClick: function (e, handler) { - var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), - elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); - - // are they closer together than 500ms yet more than 100ms? - // Android typically triggers them ~300ms apart while multiple listeners - // on the same event should be triggered far faster; - // or check if click is simulated on the element, and if it is, reject any non-simulated events - - if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { - L.DomEvent.stop(e); - return; - } - L.DomEvent._lastClick = timeStamp; - - handler(e); + } else if ('attachEvent' in obj) { + obj.attachEvent('on' + type, handler); } -}; + + obj[eventsKey] = obj[eventsKey] || {}; + obj[eventsKey][id] = handler; +} + +function removeOne(obj, type, fn, context) { + + var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : ''), + handler = obj[eventsKey] && obj[eventsKey][id]; + + if (!handler) { return this; } + + if (Browser.pointer && type.indexOf('touch') === 0) { + removePointerListener(obj, type, id); + + } else if (Browser.touch && (type === 'dblclick') && removeDoubleTapListener) { + removeDoubleTapListener(obj, id); + + } else if ('removeEventListener' in obj) { + + if (type === 'mousewheel') { + obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); + + } else { + obj.removeEventListener( + type === 'mouseenter' ? 'mouseover' : + type === 'mouseleave' ? 'mouseout' : type, handler, false); + } + + } else if ('detachEvent' in obj) { + obj.detachEvent('on' + type, handler); + } + + obj[eventsKey][id] = null; +} + +// @function stopPropagation(ev: DOMEvent): this +// Stop the given event from propagation to parent elements. Used inside the listener functions: +// ```js +// L.DomEvent.on(div, 'click', function (ev) { +// L.DomEvent.stopPropagation(ev); +// }); +// ``` +export function stopPropagation(e) { + + if (e.stopPropagation) { + e.stopPropagation(); + } else if (e.originalEvent) { // In case of Leaflet event. + e.originalEvent._stopped = true; + } else { + e.cancelBubble = true; + } + skipped(e); + + return this; +} + +// @function disableScrollPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). +export function disableScrollPropagation(el) { + return addOne(el, 'mousewheel', stopPropagation); +} + +// @function disableClickPropagation(el: HTMLElement): this +// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, +// `'mousedown'` and `'touchstart'` events (plus browser variants). +export function disableClickPropagation(el) { + on(el, 'mousedown touchstart dblclick', stopPropagation); + addOne(el, 'click', fakeStop); + return this; +} + +// @function preventDefault(ev: DOMEvent): this +// Prevents the default action of the DOM Event `ev` from happening (such as +// following a link in the href of the a element, or doing a POST request +// with page reload when a `` is submitted). +// Use it inside listener functions. +export function preventDefault(e) { + if (e.preventDefault) { + e.preventDefault(); + } else { + e.returnValue = false; + } + return this; +} + +// @function stop(ev): this +// Does `stopPropagation` and `preventDefault` at the same time. +export function stop(e) { + preventDefault(e); + stopPropagation(e); + return this; +} + +// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point +// Gets normalized mouse position from a DOM event relative to the +// `container` or to the whole page if not specified. +export function getMousePosition(e, container) { + if (!container) { + return new Point(e.clientX, e.clientY); + } + + var rect = container.getBoundingClientRect(); + + return new Point( + e.clientX - rect.left - container.clientLeft, + e.clientY - rect.top - container.clientTop); +} + +// Chrome on Win scrolls double the pixels as in other platforms (see #4538), +// and Firefox scrolls device pixels, not CSS pixels +var wheelPxFactor = + (Browser.win && Browser.chrome) ? 2 : + Browser.gecko ? window.devicePixelRatio : 1; + +// @function getWheelDelta(ev: DOMEvent): Number +// Gets normalized wheel delta from a mousewheel DOM event, in vertical +// pixels scrolled (negative if scrolling down). +// Events from pointing devices without precise scrolling are mapped to +// a best guess of 60 pixels. +export function getWheelDelta(e) { + return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta + (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels + (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines + (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages + (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events + e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels + (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines + e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages + 0; +} + +var skipEvents = {}; + +export function fakeStop(e) { + // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e) + skipEvents[e.type] = true; +} + +export function skipped(e) { + var events = skipEvents[e.type]; + // reset when checking, as it's only used in map container and propagates outside of the map + skipEvents[e.type] = false; + return events; +} + +// check if element really left/entered the event target (for mouseenter/mouseleave) +export function isExternalTarget(el, e) { + + var related = e.relatedTarget; + + if (!related) { return true; } + + try { + while (related && (related !== el)) { + related = related.parentNode; + } + } catch (err) { + return false; + } + return (related !== el); +} + +var lastClick; + +// this is a horrible workaround for a bug in Android where a single touch triggers two click events +function filterClick(e, handler) { + var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)), + elapsed = lastClick && (timeStamp - lastClick); + + // are they closer together than 500ms yet more than 100ms? + // Android typically triggers them ~300ms apart while multiple listeners + // on the same event should be triggered far faster; + // or check if click is simulated on the element, and if it is, reject any non-simulated events + + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { + stop(e); + return; + } + lastClick = timeStamp; + + handler(e); +} // @function addListener(…): this // Alias to [`L.DomEvent.on`](#domevent-on) -L.DomEvent.addListener = L.DomEvent.on; +export {on as addListener}; // @function removeListener(…): this // Alias to [`L.DomEvent.off`](#domevent-off) -L.DomEvent.removeListener = L.DomEvent.off; +export {off as removeListener}; diff --git a/src/dom/DomUtil.js b/src/dom/DomUtil.js index 0724e465..d3b781c8 100644 --- a/src/dom/DomUtil.js +++ b/src/dom/DomUtil.js @@ -1,3 +1,8 @@ +import * as DomEvent from './DomEvent'; +import * as Util from '../core/Util'; +import {Point} from '../geometry/Point'; +import * as Browser from '../core/Browser'; + /* * @namespace DomUtil * @@ -9,312 +14,301 @@ * in HTML and SVG classes in SVG. */ -L.DomUtil = { - // @function get(id: String|HTMLElement): HTMLElement - // Returns an element given its DOM id, or returns the element itself - // if it was passed directly. - get: function (id) { - return typeof id === 'string' ? document.getElementById(id) : id; - }, +// @property TRANSFORM: String +// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). +export var TRANSFORM = testProp( + ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); - // @function getStyle(el: HTMLElement, styleAttrib: String): String - // Returns the value for a certain style attribute on an element, - // including computed values or values set through CSS. - getStyle: function (el, style) { +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser - var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); +// @property TRANSITION: String +// Vendor-prefixed transform style name. +export var TRANSITION = testProp( + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); - if ((!value || value === 'auto') && document.defaultView) { - var css = document.defaultView.getComputedStyle(el, null); - value = css ? css[style] : null; - } +export var TRANSITION_END = + TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend'; - return value === 'auto' ? null : value; - }, - // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement - // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. - create: function (tagName, className, container) { +// @function get(id: String|HTMLElement): HTMLElement +// Returns an element given its DOM id, or returns the element itself +// if it was passed directly. +export function get(id) { + return typeof id === 'string' ? document.getElementById(id) : id; +} - var el = document.createElement(tagName); - el.className = className || ''; +// @function getStyle(el: HTMLElement, styleAttrib: String): String +// Returns the value for a certain style attribute on an element, +// including computed values or values set through CSS. +export function getStyle(el, style) { + var value = el.style[style] || (el.currentStyle && el.currentStyle[style]); - if (container) { - container.appendChild(el); - } - - return el; - }, - - // @function remove(el: HTMLElement) - // Removes `el` from its parent element - remove: function (el) { - var parent = el.parentNode; - if (parent) { - parent.removeChild(el); - } - }, - - // @function empty(el: HTMLElement) - // Removes all of `el`'s children elements from `el` - empty: function (el) { - while (el.firstChild) { - el.removeChild(el.firstChild); - } - }, - - // @function toFront(el: HTMLElement) - // Makes `el` the last children of its parent, so it renders in front of the other children. - toFront: function (el) { - el.parentNode.appendChild(el); - }, - - // @function toBack(el: HTMLElement) - // Makes `el` the first children of its parent, so it renders back from the other children. - toBack: function (el) { - var parent = el.parentNode; - parent.insertBefore(el, parent.firstChild); - }, - - // @function hasClass(el: HTMLElement, name: String): Boolean - // Returns `true` if the element's class attribute contains `name`. - hasClass: function (el, name) { - if (el.classList !== undefined) { - return el.classList.contains(name); - } - var className = L.DomUtil.getClass(el); - return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); - }, - - // @function addClass(el: HTMLElement, name: String) - // Adds `name` to the element's class attribute. - addClass: function (el, name) { - if (el.classList !== undefined) { - var classes = L.Util.splitWords(name); - for (var i = 0, len = classes.length; i < len; i++) { - el.classList.add(classes[i]); - } - } else if (!L.DomUtil.hasClass(el, name)) { - var className = L.DomUtil.getClass(el); - L.DomUtil.setClass(el, (className ? className + ' ' : '') + name); - } - }, - - // @function removeClass(el: HTMLElement, name: String) - // Removes `name` from the element's class attribute. - removeClass: function (el, name) { - if (el.classList !== undefined) { - el.classList.remove(name); - } else { - L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' '))); - } - }, - - // @function setClass(el: HTMLElement, name: String) - // Sets the element's class. - setClass: function (el, name) { - if (el.className.baseVal === undefined) { - el.className = name; - } else { - // in case of SVG element - el.className.baseVal = name; - } - }, - - // @function getClass(el: HTMLElement): String - // Returns the element's class. - getClass: function (el) { - return el.className.baseVal === undefined ? el.className : el.className.baseVal; - }, - - // @function setOpacity(el: HTMLElement, opacity: Number) - // Set the opacity of an element (including old IE support). - // `opacity` must be a number from `0` to `1`. - setOpacity: function (el, value) { - - if ('opacity' in el.style) { - el.style.opacity = value; - - } else if ('filter' in el.style) { - L.DomUtil._setOpacityIE(el, value); - } - }, - - _setOpacityIE: function (el, value) { - var filter = false, - filterName = 'DXImageTransform.Microsoft.Alpha'; - - // filters collection throws an error if we try to retrieve a filter that doesn't exist - try { - filter = el.filters.item(filterName); - } catch (e) { - // don't set opacity to 1 if we haven't already set an opacity, - // it isn't needed and breaks transparent pngs. - if (value === 1) { return; } - } - - value = Math.round(value * 100); - - if (filter) { - filter.Enabled = (value !== 100); - filter.Opacity = value; - } else { - el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; - } - }, - - // @function testProp(props: String[]): String|false - // Goes through the array of style names and returns the first name - // that is a valid style name for an element. If no such name is found, - // it returns false. Useful for vendor-prefixed styles like `transform`. - testProp: function (props) { - - var style = document.documentElement.style; - - for (var i = 0; i < props.length; i++) { - if (props[i] in style) { - return props[i]; - } - } - return false; - }, - - // @function setTransform(el: HTMLElement, offset: Point, scale?: Number) - // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels - // and optionally scaled by `scale`. Does not have an effect if the - // browser doesn't support 3D CSS transforms. - setTransform: function (el, offset, scale) { - var pos = offset || new L.Point(0, 0); - - el.style[L.DomUtil.TRANSFORM] = - (L.Browser.ie3d ? - 'translate(' + pos.x + 'px,' + pos.y + 'px)' : - 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + - (scale ? ' scale(' + scale + ')' : ''); - }, - - // @function setPosition(el: HTMLElement, position: Point) - // Sets the position of `el` to coordinates specified by `position`, - // using CSS translate or top/left positioning depending on the browser - // (used by Leaflet internally to position its layers). - setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) - - /*eslint-disable */ - el._leaflet_pos = point; - /*eslint-enable */ - - if (L.Browser.any3d) { - L.DomUtil.setTransform(el, point); - } else { - el.style.left = point.x + 'px'; - el.style.top = point.y + 'px'; - } - }, - - // @function getPosition(el: HTMLElement): Point - // Returns the coordinates of an element previously positioned with setPosition. - getPosition: function (el) { - // this method is only used for elements previously positioned using setPosition, - // so it's safe to cache the position for performance - - return el._leaflet_pos || new L.Point(0, 0); + if ((!value || value === 'auto') && document.defaultView) { + var css = document.defaultView.getComputedStyle(el, null); + value = css ? css[style] : null; } -}; + return value === 'auto' ? null : value; +} +// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement +// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. +export function create(tagName, className, container) { + var el = document.createElement(tagName); + el.className = className || ''; -(function () { - // prefix style property names + if (container) { + container.appendChild(el); + } + return el; +} - // @property TRANSFORM: String - // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). - L.DomUtil.TRANSFORM = L.DomUtil.testProp( - ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); +// @function remove(el: HTMLElement) +// Removes `el` from its parent element +export function remove(el) { + var parent = el.parentNode; + if (parent) { + parent.removeChild(el); + } +} +// @function empty(el: HTMLElement) +// Removes all of `el`'s children elements from `el` +export function empty(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } +} - // webkitTransition comes first because some browser versions that drop vendor prefix don't do - // the same for the transitionend event, in particular the Android 4.1 stock browser +// @function toFront(el: HTMLElement) +// Makes `el` the last children of its parent, so it renders in front of the other children. +export function toFront(el) { + el.parentNode.appendChild(el); +} - // @property TRANSITION: String - // Vendor-prefixed transform style name. - var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( - ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); +// @function toBack(el: HTMLElement) +// Makes `el` the first children of its parent, so it renders back from the other children. +export function toBack(el) { + var parent = el.parentNode; + parent.insertBefore(el, parent.firstChild); +} - L.DomUtil.TRANSITION_END = - transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; +// @function hasClass(el: HTMLElement, name: String): Boolean +// Returns `true` if the element's class attribute contains `name`. +export function hasClass(el, name) { + if (el.classList !== undefined) { + return el.classList.contains(name); + } + var className = getClass(el); + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); +} - // @function disableTextSelection() - // Prevents the user from generating `selectstart` DOM events, usually generated - // when the user drags the mouse through a page with text. Used internally - // by Leaflet to override the behaviour of any click-and-drag interaction on - // the map. Affects drag interactions on the whole document. - - // @function enableTextSelection() - // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). - if ('onselectstart' in document) { - L.DomUtil.disableTextSelection = function () { - L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); - }; - L.DomUtil.enableTextSelection = function () { - L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); - }; +// @function addClass(el: HTMLElement, name: String) +// Adds `name` to the element's class attribute. +export function addClass(el, name) { + if (el.classList !== undefined) { + var classes = Util.splitWords(name); + for (var i = 0, len = classes.length; i < len; i++) { + el.classList.add(classes[i]); + } + } else if (!hasClass(el, name)) { + var className = getClass(el); + setClass(el, (className ? className + ' ' : '') + name); + } +} +// @function removeClass(el: HTMLElement, name: String) +// Removes `name` from the element's class attribute. +export function removeClass(el, name) { + if (el.classList !== undefined) { + el.classList.remove(name); } else { - var userSelectProperty = L.DomUtil.testProp( - ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + } +} - L.DomUtil.disableTextSelection = function () { - if (userSelectProperty) { - var style = document.documentElement.style; - this._userSelect = style[userSelectProperty]; - style[userSelectProperty] = 'none'; - } - }; - L.DomUtil.enableTextSelection = function () { - if (userSelectProperty) { - document.documentElement.style[userSelectProperty] = this._userSelect; - delete this._userSelect; - } - }; +// @function setClass(el: HTMLElement, name: String) +// Sets the element's class. +export function setClass(el, name) { + if (el.className.baseVal === undefined) { + el.className = name; + } else { + // in case of SVG element + el.className.baseVal = name; + } +} + +// @function getClass(el: HTMLElement): String +// Returns the element's class. +export function getClass(el) { + return el.className.baseVal === undefined ? el.className : el.className.baseVal; +} + +// @function setOpacity(el: HTMLElement, opacity: Number) +// Set the opacity of an element (including old IE support). +// `opacity` must be a number from `0` to `1`. +export function setOpacity(el, value) { + if ('opacity' in el.style) { + el.style.opacity = value; + } else if ('filter' in el.style) { + _setOpacityIE(el, value); + } +} + +function _setOpacityIE(el, value) { + var filter = false, + filterName = 'DXImageTransform.Microsoft.Alpha'; + + // filters collection throws an error if we try to retrieve a filter that doesn't exist + try { + filter = el.filters.item(filterName); + } catch (e) { + // don't set opacity to 1 if we haven't already set an opacity, + // it isn't needed and breaks transparent pngs. + if (value === 1) { return; } } - // @function disableImageDrag() - // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but - // for `dragstart` DOM events, usually generated when the user drags an image. - L.DomUtil.disableImageDrag = function () { - L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); - }; + value = Math.round(value * 100); - // @function enableImageDrag() - // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). - L.DomUtil.enableImageDrag = function () { - L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); - }; + if (filter) { + filter.Enabled = (value !== 100); + filter.Opacity = value; + } else { + el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')'; + } +} - // @function preventOutline(el: HTMLElement) - // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) - // of the element `el` invisible. Used internally by Leaflet to prevent - // focusable elements from displaying an outline when the user performs a - // drag interaction on them. - L.DomUtil.preventOutline = function (element) { - while (element.tabIndex === -1) { - element = element.parentNode; +// @function testProp(props: String[]): String|false +// Goes through the array of style names and returns the first name +// that is a valid style name for an element. If no such name is found, +// it returns false. Useful for vendor-prefixed styles like `transform`. +export function testProp(props) { + var style = document.documentElement.style; + + for (var i = 0; i < props.length; i++) { + if (props[i] in style) { + return props[i]; } - if (!element || !element.style) { return; } - L.DomUtil.restoreOutline(); - this._outlineElement = element; - this._outlineStyle = element.style.outline; - element.style.outline = 'none'; - L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this); - }; + } + return false; +} - // @function restoreOutline() - // Cancels the effects of a previous [`L.DomUtil.preventOutline`](). - L.DomUtil.restoreOutline = function () { - if (!this._outlineElement) { return; } - this._outlineElement.style.outline = this._outlineStyle; - delete this._outlineElement; - delete this._outlineStyle; - L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); +// @function setTransform(el: HTMLElement, offset: Point, scale?: Number) +// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels +// and optionally scaled by `scale`. Does not have an effect if the +// browser doesn't support 3D CSS transforms. +export function setTransform(el, offset, scale) { + var pos = offset || new Point(0, 0); + + el.style[TRANSFORM] = + (Browser.ie3d ? + 'translate(' + pos.x + 'px,' + pos.y + 'px)' : + 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') + + (scale ? ' scale(' + scale + ')' : ''); +} + +// @function setPosition(el: HTMLElement, position: Point) +// Sets the position of `el` to coordinates specified by `position`, +// using CSS translate or top/left positioning depending on the browser +// (used by Leaflet internally to position its layers). +export function setPosition(el, point) { + + /*eslint-disable */ + el._leaflet_pos = point; + /*eslint-enable */ + + if (Browser.any3d) { + setTransform(el, point); + } else { + el.style.left = point.x + 'px'; + el.style.top = point.y + 'px'; + } +} + +// @function getPosition(el: HTMLElement): Point +// Returns the coordinates of an element previously positioned with setPosition. +export function getPosition(el) { + // this method is only used for elements previously positioned using setPosition, + // so it's safe to cache the position for performance + + return el._leaflet_pos || new Point(0, 0); +} + +// @function disableTextSelection() +// Prevents the user from generating `selectstart` DOM events, usually generated +// when the user drags the mouse through a page with text. Used internally +// by Leaflet to override the behaviour of any click-and-drag interaction on +// the map. Affects drag interactions on the whole document. + +// @function enableTextSelection() +// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). +export var disableTextSelection; +export var enableTextSelection; +var _userSelect; +if ('onselectstart' in document) { + disableTextSelection = function () { + DomEvent.on(window, 'selectstart', DomEvent.preventDefault); }; -})(); + enableTextSelection = function () { + DomEvent.off(window, 'selectstart', DomEvent.preventDefault); + }; +} else { + var userSelectProperty = testProp( + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + + disableTextSelection = function () { + if (userSelectProperty) { + var style = document.documentElement.style; + _userSelect = style[userSelectProperty]; + style[userSelectProperty] = 'none'; + } + }; + enableTextSelection = function () { + if (userSelectProperty) { + document.documentElement.style[userSelectProperty] = _userSelect; + _userSelect = undefined; + } + }; +} + +// @function disableImageDrag() +// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but +// for `dragstart` DOM events, usually generated when the user drags an image. +export function disableImageDrag() { + DomEvent.on(window, 'dragstart', DomEvent.preventDefault); +} + +// @function enableImageDrag() +// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). +export function enableImageDrag() { + DomEvent.off(window, 'dragstart', DomEvent.preventDefault); +} + +var _outlineElement, _outlineStyle; +// @function preventOutline(el: HTMLElement) +// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) +// of the element `el` invisible. Used internally by Leaflet to prevent +// focusable elements from displaying an outline when the user performs a +// drag interaction on them. +export function preventOutline(element) { + while (element.tabIndex === -1) { + element = element.parentNode; + } + if (!element || !element.style) { return; } + restoreOutline(); + _outlineElement = element; + _outlineStyle = element.style.outline; + element.style.outline = 'none'; + DomEvent.on(window, 'keydown', restoreOutline); +} + +// @function restoreOutline() +// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). +export function restoreOutline() { + if (!_outlineElement) { return; } + _outlineElement.style.outline = _outlineStyle; + _outlineElement = undefined; + _outlineStyle = undefined; + DomEvent.off(window, 'keydown', restoreOutline); +} diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 3cb8742a..87f16580 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -1,3 +1,10 @@ +import {Evented} from '../core/Events'; +import * as Browser from '../core/Browser'; +import * as DomEvent from './DomEvent'; +import * as DomUtil from './DomUtil'; +import * as Util from '../core/Util'; +import {Point} from '../geometry/Point'; + /* * @class Draggable * @aka L.Draggable @@ -14,7 +21,23 @@ * ``` */ -L.Draggable = L.Evented.extend({ +var _dragging = false; +var START = Browser.touch ? 'touchstart mousedown' : 'mousedown'; +var END = { + mousedown: 'mouseup', + touchstart: 'touchend', + pointerdown: 'touchend', + MSPointerDown: 'touchend' +}; +var MOVE = { + mousedown: 'mousemove', + touchstart: 'touchmove', + pointerdown: 'touchmove', + MSPointerDown: 'touchmove' +}; + + +export var Draggable = Evented.extend({ options: { // @option clickTolerance: Number = 3 @@ -23,22 +46,6 @@ L.Draggable = L.Evented.extend({ clickTolerance: 3 }, - statics: { - START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], - END: { - mousedown: 'mouseup', - touchstart: 'touchend', - pointerdown: 'touchend', - MSPointerDown: 'touchend' - }, - MOVE: { - mousedown: 'mousemove', - touchstart: 'touchmove', - pointerdown: 'touchmove', - MSPointerDown: 'touchmove' - } - }, - // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean) // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). initialize: function (element, dragStartTarget, preventOutline) { @@ -52,7 +59,7 @@ L.Draggable = L.Evented.extend({ enable: function () { if (this._enabled) { return; } - L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); + DomEvent.on(this._dragStartTarget, START, this._onDown, this); this._enabled = true; }, @@ -68,7 +75,7 @@ L.Draggable = L.Evented.extend({ this.finishDrag(); } - L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); + DomEvent.off(this._dragStartTarget, START, this._onDown, this); this._enabled = false; this._moved = false; @@ -84,17 +91,17 @@ L.Draggable = L.Evented.extend({ this._moved = false; - if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } + if (DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } - if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } - L.Draggable._dragging = this; // Prevent dragging multiple objects at once. + if (_dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + _dragging = this; // Prevent dragging multiple objects at once. if (this._preventOutline) { - L.DomUtil.preventOutline(this._element); + DomUtil.preventOutline(this._element); } - L.DomUtil.disableImageDrag(); - L.DomUtil.disableTextSelection(); + DomUtil.disableImageDrag(); + DomUtil.disableTextSelection(); if (this._moving) { return; } @@ -104,11 +111,10 @@ L.Draggable = L.Evented.extend({ var first = e.touches ? e.touches[0] : e; - this._startPoint = new L.Point(first.clientX, first.clientY); + this._startPoint = new Point(first.clientX, first.clientY); - L.DomEvent - .on(document, L.Draggable.MOVE[e.type], this._onMove, this) - .on(document, L.Draggable.END[e.type], this._onUp, this); + DomEvent.on(document, MOVE[e.type], this._onMove, this); + DomEvent.on(document, END[e.type], this._onUp, this); }, _onMove: function (e) { @@ -125,13 +131,13 @@ L.Draggable = L.Evented.extend({ } var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), - newPoint = new L.Point(first.clientX, first.clientY), + newPoint = new Point(first.clientX, first.clientY), offset = newPoint.subtract(this._startPoint); if (!offset.x && !offset.y) { return; } if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } - L.DomEvent.preventDefault(e); + DomEvent.preventDefault(e); if (!this._moved) { // @event dragstart: Event @@ -139,9 +145,9 @@ L.Draggable = L.Evented.extend({ this.fire('dragstart'); this._moved = true; - this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); + this._startPos = DomUtil.getPosition(this._element).subtract(offset); - L.DomUtil.addClass(document.body, 'leaflet-dragging'); + DomUtil.addClass(document.body, 'leaflet-dragging'); this._lastTarget = e.target || e.srcElement; // IE and Edge do not give the element, so fetch it @@ -149,15 +155,15 @@ L.Draggable = L.Evented.extend({ if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { this._lastTarget = this._lastTarget.correspondingUseElement; } - L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); + DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); } this._newPos = this._startPos.add(offset); this._moving = true; - L.Util.cancelAnimFrame(this._animRequest); + Util.cancelAnimFrame(this._animRequest); this._lastEvent = e; - this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true); + this._animRequest = Util.requestAnimFrame(this._updatePosition, this, true); }, _updatePosition: function () { @@ -167,7 +173,7 @@ L.Draggable = L.Evented.extend({ // Fired continuously during dragging *before* each corresponding // update of the element's position. this.fire('predrag', e); - L.DomUtil.setPosition(this._element, this._newPos); + DomUtil.setPosition(this._element, this._newPos); // @event drag: Event // Fired continuously during dragging. @@ -185,25 +191,24 @@ L.Draggable = L.Evented.extend({ }, finishDrag: function () { - L.DomUtil.removeClass(document.body, 'leaflet-dragging'); + DomUtil.removeClass(document.body, 'leaflet-dragging'); if (this._lastTarget) { - L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); + DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); this._lastTarget = null; } - for (var i in L.Draggable.MOVE) { - L.DomEvent - .off(document, L.Draggable.MOVE[i], this._onMove, this) - .off(document, L.Draggable.END[i], this._onUp, this); + for (var i in MOVE) { + DomEvent.off(document, MOVE[i], this._onMove, this); + DomEvent.off(document, END[i], this._onUp, this); } - L.DomUtil.enableImageDrag(); - L.DomUtil.enableTextSelection(); + DomUtil.enableImageDrag(); + DomUtil.enableTextSelection(); if (this._moved && this._moving) { // ensure drag is not fired after dragend - L.Util.cancelAnimFrame(this._animRequest); + Util.cancelAnimFrame(this._animRequest); // @event dragend: DragEndEvent // Fired when the drag ends. @@ -213,7 +218,7 @@ L.Draggable = L.Evented.extend({ } this._moving = false; - L.Draggable._dragging = false; + _dragging = false; } }); diff --git a/src/dom/PosAnimation.js b/src/dom/PosAnimation.js index b14f061f..05290c11 100644 --- a/src/dom/PosAnimation.js +++ b/src/dom/PosAnimation.js @@ -1,3 +1,8 @@ +import * as Util from '../core/Util'; +import {Evented} from '../core/Events'; +import * as DomUtil from '../dom/DomUtil'; + + /* * @class PosAnimation * @aka L.PosAnimation @@ -15,7 +20,7 @@ * */ -L.PosAnimation = L.Evented.extend({ +export var PosAnimation = Evented.extend({ // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) // Run an animation of a given element to a new position, optionally setting @@ -30,7 +35,7 @@ L.PosAnimation = L.Evented.extend({ this._duration = duration || 0.25; this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); - this._startPos = L.DomUtil.getPosition(el); + this._startPos = DomUtil.getPosition(el); this._offset = newPos.subtract(this._startPos); this._startTime = +new Date(); @@ -52,7 +57,7 @@ L.PosAnimation = L.Evented.extend({ _animate: function () { // animation loop - this._animId = L.Util.requestAnimFrame(this._animate, this); + this._animId = Util.requestAnimFrame(this._animate, this); this._step(); }, @@ -73,7 +78,7 @@ L.PosAnimation = L.Evented.extend({ if (round) { pos._round(); } - L.DomUtil.setPosition(this._el, pos); + DomUtil.setPosition(this._el, pos); // @event step: Event // Fired continuously during the animation. @@ -81,7 +86,7 @@ L.PosAnimation = L.Evented.extend({ }, _complete: function () { - L.Util.cancelAnimFrame(this._animId); + Util.cancelAnimFrame(this._animId); this._inProgress = false; // @event end: Event diff --git a/src/geo/LatLng.js b/src/geo/LatLng.js index b2000bf1..a44dd889 100644 --- a/src/geo/LatLng.js +++ b/src/geo/LatLng.js @@ -1,3 +1,7 @@ +import * as Util from '../core/Util'; +import {Earth} from './crs/CRS.Earth'; +import {toLatLngBounds} from './LatLngBounds'; + /* @class LatLng * @aka L.LatLng * @@ -19,7 +23,7 @@ * ``` */ -L.LatLng = function (lat, lng, alt) { +export function LatLng(lat, lng, alt) { if (isNaN(lat) || isNaN(lng)) { throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); } @@ -37,15 +41,15 @@ L.LatLng = function (lat, lng, alt) { if (alt !== undefined) { this.alt = +alt; } -}; +} -L.LatLng.prototype = { +LatLng.prototype = { // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number. equals: function (obj, maxMargin) { if (!obj) { return false; } - obj = L.latLng(obj); + obj = toLatLng(obj); var margin = Math.max( Math.abs(this.lat - obj.lat), @@ -58,20 +62,20 @@ L.LatLng.prototype = { // Returns a string representation of the point (for debugging purposes). toString: function (precision) { return 'LatLng(' + - L.Util.formatNum(this.lat, precision) + ', ' + - L.Util.formatNum(this.lng, precision) + ')'; + Util.formatNum(this.lat, precision) + ', ' + + Util.formatNum(this.lng, precision) + ')'; }, // @method distanceTo(otherLatLng: LatLng): Number // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula). distanceTo: function (other) { - return L.CRS.Earth.distance(this, L.latLng(other)); + return Earth.distance(this, toLatLng(other)); }, // @method wrap(): LatLng // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. wrap: function () { - return L.CRS.Earth.wrapLatLng(this); + return Earth.wrapLatLng(this); }, // @method toBounds(sizeInMeters: Number): LatLngBounds @@ -80,13 +84,13 @@ L.LatLng.prototype = { var latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); - return L.latLngBounds( + return toLatLngBounds( [this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]); }, clone: function () { - return new L.LatLng(this.lat, this.lng, this.alt); + return new LatLng(this.lat, this.lng, this.alt); } }; @@ -103,16 +107,16 @@ L.LatLng.prototype = { // @factory L.latLng(coords: Object): LatLng // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. -L.latLng = function (a, b, c) { - if (a instanceof L.LatLng) { +export function toLatLng(a, b, c) { + if (a instanceof LatLng) { return a; } - if (L.Util.isArray(a) && typeof a[0] !== 'object') { + if (Util.isArray(a) && typeof a[0] !== 'object') { if (a.length === 3) { - return new L.LatLng(a[0], a[1], a[2]); + return new LatLng(a[0], a[1], a[2]); } if (a.length === 2) { - return new L.LatLng(a[0], a[1]); + return new LatLng(a[0], a[1]); } return null; } @@ -120,10 +124,10 @@ L.latLng = function (a, b, c) { return a; } if (typeof a === 'object' && 'lat' in a) { - return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); + return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); } if (b === undefined) { return null; } - return new L.LatLng(a, b, c); -}; + return new LatLng(a, b, c); +} diff --git a/src/geo/LatLngBounds.js b/src/geo/LatLngBounds.js index 537b80e8..bfe44d33 100644 --- a/src/geo/LatLngBounds.js +++ b/src/geo/LatLngBounds.js @@ -1,3 +1,5 @@ +import {LatLng, toLatLng} from './LatLng'; + /* * @class LatLngBounds * @aka L.LatLngBounds @@ -24,7 +26,7 @@ * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. */ -L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) +export function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) if (!corner1) { return; } var latlngs = corner2 ? [corner1, corner2] : corner1; @@ -32,9 +34,9 @@ L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) for (var i = 0, len = latlngs.length; i < len; i++) { this.extend(latlngs[i]); } -}; +} -L.LatLngBounds.prototype = { +LatLngBounds.prototype = { // @method extend(latlng: LatLng): this // Extend the bounds to contain the given point @@ -47,23 +49,23 @@ L.LatLngBounds.prototype = { ne = this._northEast, sw2, ne2; - if (obj instanceof L.LatLng) { + if (obj instanceof LatLng) { sw2 = obj; ne2 = obj; - } else if (obj instanceof L.LatLngBounds) { + } else if (obj instanceof LatLngBounds) { sw2 = obj._southWest; ne2 = obj._northEast; if (!sw2 || !ne2) { return this; } } else { - return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; + return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; } if (!sw && !ne) { - this._southWest = new L.LatLng(sw2.lat, sw2.lng); - this._northEast = new L.LatLng(ne2.lat, ne2.lng); + this._southWest = new LatLng(sw2.lat, sw2.lng); + this._northEast = new LatLng(ne2.lat, ne2.lng); } else { sw.lat = Math.min(sw2.lat, sw.lat); sw.lng = Math.min(sw2.lng, sw.lng); @@ -82,15 +84,15 @@ L.LatLngBounds.prototype = { heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; - return new L.LatLngBounds( - new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), - new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); + return new LatLngBounds( + new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), + new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); }, // @method getCenter(): LatLng // Returns the center point of the bounds. getCenter: function () { - return new L.LatLng( + return new LatLng( (this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2); }, @@ -110,13 +112,13 @@ L.LatLngBounds.prototype = { // @method getNorthWest(): LatLng // Returns the north-west point of the bounds. getNorthWest: function () { - return new L.LatLng(this.getNorth(), this.getWest()); + return new LatLng(this.getNorth(), this.getWest()); }, // @method getSouthEast(): LatLng // Returns the south-east point of the bounds. getSouthEast: function () { - return new L.LatLng(this.getSouth(), this.getEast()); + return new LatLng(this.getSouth(), this.getEast()); }, // @method getWest(): Number @@ -150,17 +152,17 @@ L.LatLngBounds.prototype = { // @method contains (latlng: LatLng): Boolean // Returns `true` if the rectangle contains the given point. contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean - if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) { - obj = L.latLng(obj); + if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { + obj = toLatLng(obj); } else { - obj = L.latLngBounds(obj); + obj = toLatLngBounds(obj); } var sw = this._southWest, ne = this._northEast, sw2, ne2; - if (obj instanceof L.LatLngBounds) { + if (obj instanceof LatLngBounds) { sw2 = obj.getSouthWest(); ne2 = obj.getNorthEast(); } else { @@ -174,7 +176,7 @@ L.LatLngBounds.prototype = { // @method intersects(otherBounds: LatLngBounds): Boolean // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. intersects: function (bounds) { - bounds = L.latLngBounds(bounds); + bounds = toLatLngBounds(bounds); var sw = this._southWest, ne = this._northEast, @@ -190,7 +192,7 @@ L.LatLngBounds.prototype = { // @method overlaps(otherBounds: Bounds): Boolean // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. overlaps: function (bounds) { - bounds = L.latLngBounds(bounds); + bounds = toLatLngBounds(bounds); var sw = this._southWest, ne = this._northEast, @@ -214,7 +216,7 @@ L.LatLngBounds.prototype = { equals: function (bounds) { if (!bounds) { return false; } - bounds = L.latLngBounds(bounds); + bounds = toLatLngBounds(bounds); return this._southWest.equals(bounds.getSouthWest()) && this._northEast.equals(bounds.getNorthEast()); @@ -235,9 +237,9 @@ L.LatLngBounds.prototype = { // @alternative // @factory L.latLngBounds(latlngs: LatLng[]) // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). -L.latLngBounds = function (a, b) { - if (a instanceof L.LatLngBounds) { +export function toLatLngBounds(a, b) { + if (a instanceof LatLngBounds) { return a; } - return new L.LatLngBounds(a, b); -}; + return new LatLngBounds(a, b); +} diff --git a/src/geo/crs/CRS.EPSG3395.js b/src/geo/crs/CRS.EPSG3395.js index 0bc590f3..05a2977c 100644 --- a/src/geo/crs/CRS.EPSG3395.js +++ b/src/geo/crs/CRS.EPSG3395.js @@ -1,16 +1,20 @@ +import {Earth} from './CRS.Earth'; +import {Mercator} from '../projection/Projection.Mercator'; +import {toTransformation} from '../../geometry/Transformation'; +import * as Util from '../../core/Util'; + /* * @namespace CRS * @crs L.CRS.EPSG3395 * * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. */ - -L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, { +export var EPSG3395 = Util.extend({}, Earth, { code: 'EPSG:3395', - projection: L.Projection.Mercator, + projection: Mercator, transformation: (function () { - var scale = 0.5 / (Math.PI * L.Projection.Mercator.R); - return L.transformation(scale, 0.5, -scale, 0.5); + var scale = 0.5 / (Math.PI * Mercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); }()) }); diff --git a/src/geo/crs/CRS.EPSG3857.js b/src/geo/crs/CRS.EPSG3857.js index 8b53b74f..d2b4a757 100644 --- a/src/geo/crs/CRS.EPSG3857.js +++ b/src/geo/crs/CRS.EPSG3857.js @@ -1,3 +1,8 @@ +import {Earth} from './CRS.Earth'; +import {SphericalMercator} from '../projection/Projection.SphericalMercator'; +import {toTransformation} from '../../geometry/Transformation'; +import * as Util from '../../core/Util'; + /* * @namespace CRS * @crs L.CRS.EPSG3857 @@ -7,16 +12,16 @@ * Map's `crs` option. */ -L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, { +export var EPSG3857 = Util.extend({}, Earth, { code: 'EPSG:3857', - projection: L.Projection.SphericalMercator, + projection: SphericalMercator, transformation: (function () { - var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R); - return L.transformation(scale, 0.5, -scale, 0.5); + var scale = 0.5 / (Math.PI * SphericalMercator.R); + return toTransformation(scale, 0.5, -scale, 0.5); }()) }); -L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { +export var EPSG900913 = Util.extend({}, EPSG3857, { code: 'EPSG:900913' }); diff --git a/src/geo/crs/CRS.EPSG4326.js b/src/geo/crs/CRS.EPSG4326.js index 1020615d..55978dbd 100644 --- a/src/geo/crs/CRS.EPSG4326.js +++ b/src/geo/crs/CRS.EPSG4326.js @@ -1,3 +1,8 @@ +import {Earth} from './CRS.Earth'; +import {LonLat} from '../projection/Projection.LonLat'; +import {toTransformation} from '../../geometry/Transformation'; +import * as Util from '../../core/Util'; + /* * @namespace CRS * @crs L.CRS.EPSG4326 @@ -11,8 +16,8 @@ * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. */ -L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, { +export var EPSG4326 = Util.extend({}, Earth, { code: 'EPSG:4326', - projection: L.Projection.LonLat, - transformation: L.transformation(1 / 180, 1, -1 / 180, 0.5) + projection: LonLat, + transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) }); diff --git a/src/geo/crs/CRS.Earth.js b/src/geo/crs/CRS.Earth.js index 85120364..0ded3129 100644 --- a/src/geo/crs/CRS.Earth.js +++ b/src/geo/crs/CRS.Earth.js @@ -1,3 +1,6 @@ +import {CRS} from './CRS'; +import * as Util from '../../core/Util'; + /* * @namespace CRS * @crs L.CRS.Earth @@ -8,7 +11,7 @@ * meters. */ -L.CRS.Earth = L.extend({}, L.CRS, { +export var Earth = Util.extend({}, CRS, { wrapLng: [-180, 180], // Mean Earth Radius, as recommended for use by diff --git a/src/geo/crs/CRS.Simple.js b/src/geo/crs/CRS.Simple.js index 8760bda3..634baa29 100644 --- a/src/geo/crs/CRS.Simple.js +++ b/src/geo/crs/CRS.Simple.js @@ -1,3 +1,8 @@ +import {CRS} from './CRS'; +import {LonLat} from '../projection/Projection.LonLat'; +import {toTransformation} from '../../geometry/Transformation'; +import * as Util from '../../core/Util'; + /* * @namespace CRS * @crs L.CRS.Simple @@ -8,9 +13,9 @@ * simple euclidean distance. */ -L.CRS.Simple = L.extend({}, L.CRS, { - projection: L.Projection.LonLat, - transformation: L.transformation(1, 0, -1, 0), +export var Simple = Util.extend({}, CRS, { + projection: LonLat, + transformation: toTransformation(1, 0, -1, 0), scale: function (zoom) { return Math.pow(2, zoom); diff --git a/src/geo/crs/CRS.js b/src/geo/crs/CRS.js index 02a9ca12..7dfcdd0e 100644 --- a/src/geo/crs/CRS.js +++ b/src/geo/crs/CRS.js @@ -1,7 +1,13 @@ + +import {Bounds} from '../../geometry/Bounds'; +import {LatLng} from '../LatLng'; +import {LatLngBounds} from '../LatLngBounds'; +import * as Util from '../../core/Util'; + /* - * @class CRS - * @aka L.CRS - * Abstract class that defines coordinate reference systems for projecting + * @namespace CRS + * @crs L.CRS.Base + * Object that defines coordinate reference systems for projecting * geographical points into pixel (screen) coordinates and back (and to * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system). @@ -11,7 +17,7 @@ * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. */ -L.CRS = { +export var CRS = { // @method latLngToPoint(latlng: LatLng, zoom: Number): Point // Projects geographical coordinates into pixel coordinates for a given zoom. latLngToPoint: function (latlng, zoom) { @@ -70,7 +76,7 @@ L.CRS = { min = this.transformation.transform(b.min, s), max = this.transformation.transform(b.max, s); - return L.bounds(min, max); + return new Bounds(min, max); }, // @method distance(latlng1: LatLng, latlng2: LatLng): Number @@ -97,13 +103,12 @@ L.CRS = { // @method wrapLatLng(latlng: LatLng): LatLng // Returns a `LatLng` where lat and lng has been wrapped according to the // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. - // Only accepts actual `L.LatLng` instances, not arrays. wrapLatLng: function (latlng) { - var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, - lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, + var lng = this.wrapLng ? Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, + lat = this.wrapLat ? Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, alt = latlng.alt; - return L.latLng(lat, lng, alt); + return new LatLng(lat, lng, alt); }, // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds @@ -122,9 +127,9 @@ L.CRS = { var sw = bounds.getSouthWest(), ne = bounds.getNorthEast(), - newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}), - newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift}); + newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), + newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); - return new L.LatLngBounds(newSw, newNe); + return new LatLngBounds(newSw, newNe); } }; diff --git a/src/geo/projection/Projection.LonLat.js b/src/geo/projection/Projection.LonLat.js index 3471af42..a080742f 100644 --- a/src/geo/projection/Projection.LonLat.js +++ b/src/geo/projection/Projection.LonLat.js @@ -1,3 +1,7 @@ +import {LatLng} from '../LatLng'; +import {Bounds} from '../../geometry/Bounds'; +import {Point} from '../../geometry/Point'; + /* * @namespace Projection * @section @@ -11,16 +15,14 @@ * `EPSG:3395` and `Simple` CRS. */ -L.Projection = {}; - -L.Projection.LonLat = { +export var LonLat = { project: function (latlng) { - return new L.Point(latlng.lng, latlng.lat); + return new Point(latlng.lng, latlng.lat); }, unproject: function (point) { - return new L.LatLng(point.y, point.x); + return new LatLng(point.y, point.x); }, - bounds: L.bounds([-180, -90], [180, 90]) + bounds: new Bounds([-180, -90], [180, 90]) }; diff --git a/src/geo/projection/Projection.Mercator.js b/src/geo/projection/Projection.Mercator.js index 4c18dfb5..00ab52a8 100644 --- a/src/geo/projection/Projection.Mercator.js +++ b/src/geo/projection/Projection.Mercator.js @@ -1,3 +1,7 @@ +import {LatLng} from '../LatLng'; +import {Bounds} from '../../geometry/Bounds'; +import {Point} from '../../geometry/Point'; + /* * @namespace Projection * @projection L.Projection.Mercator @@ -5,11 +9,11 @@ * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS. */ -L.Projection.Mercator = { +export var Mercator = { R: 6378137, R_MINOR: 6356752.314245179, - bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), + bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]), project: function (latlng) { var d = Math.PI / 180, @@ -22,7 +26,7 @@ L.Projection.Mercator = { var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); y = -r * Math.log(Math.max(ts, 1E-10)); - return new L.Point(latlng.lng * d * r, y); + return new Point(latlng.lng * d * r, y); }, unproject: function (point) { @@ -40,6 +44,6 @@ L.Projection.Mercator = { phi += dphi; } - return new L.LatLng(phi * d, point.x * d / r); + return new LatLng(phi * d, point.x * d / r); } }; diff --git a/src/geo/projection/Projection.SphericalMercator.js b/src/geo/projection/Projection.SphericalMercator.js index 6b1acc21..bba04e2d 100644 --- a/src/geo/projection/Projection.SphericalMercator.js +++ b/src/geo/projection/Projection.SphericalMercator.js @@ -1,3 +1,7 @@ +import {LatLng} from '../LatLng'; +import {Bounds} from '../../geometry/Bounds'; +import {Point} from '../../geometry/Point'; + /* * @namespace Projection * @projection L.Projection.SphericalMercator @@ -7,7 +11,7 @@ * a sphere. Used by the `EPSG:3857` CRS. */ -L.Projection.SphericalMercator = { +export var SphericalMercator = { R: 6378137, MAX_LATITUDE: 85.0511287798, @@ -18,7 +22,7 @@ L.Projection.SphericalMercator = { lat = Math.max(Math.min(max, latlng.lat), -max), sin = Math.sin(lat * d); - return new L.Point( + return new Point( this.R * latlng.lng * d, this.R * Math.log((1 + sin) / (1 - sin)) / 2); }, @@ -26,13 +30,13 @@ L.Projection.SphericalMercator = { unproject: function (point) { var d = 180 / Math.PI; - return new L.LatLng( + return new LatLng( (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, point.x * d / this.R); }, bounds: (function () { var d = 6378137 * Math.PI; - return L.bounds([-d, -d], [d, d]); + return new Bounds([-d, -d], [d, d]); })() }; diff --git a/src/geo/projection/Projection.js b/src/geo/projection/Projection.js new file mode 100644 index 00000000..b51d36d4 --- /dev/null +++ b/src/geo/projection/Projection.js @@ -0,0 +1,22 @@ +/* + * @class Projection + + * An object with methods for projecting geographical coordinates of the world onto + * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). + + * @property bounds: Bounds + * The bounds (specified in CRS units) where the projection is valid + + * @method project(latlng: LatLng): Point + * Projects geographical coordinates into a 2D point. + * Only accepts actual `L.LatLng` instances, not arrays. + + * @method unproject(point: Point): LatLng + * The inverse of `project`. Projects a 2D point into a geographical location. + * Only accepts actual `L.Point` instances, not arrays. + + */ + +export {LonLat} from './Projection.LonLat'; +export {Mercator} from './Projection.Mercator'; +export {SphericalMercator} from './Projection.SphericalMercator'; diff --git a/src/geo/projection/Projection.leafdoc b/src/geo/projection/Projection.leafdoc deleted file mode 100644 index 348e22de..00000000 --- a/src/geo/projection/Projection.leafdoc +++ /dev/null @@ -1,16 +0,0 @@ - -@class Projection - -An object with methods for projecting geographical coordinates of the world onto -a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection). - -@property bounds: Bounds -The bounds (specified in CRS units) where the projection is valid - -@method project(latlng: LatLng): Point -Projects geographical coordinates into a 2D point. Only accepts actual `L.LatLng` instances, not arrays. - -@method unproject(point: Point): LatLng -The inverse of `project`. Projects a 2D point into a geographical location. Only accepts actual `L.Point` instances, not arrays. - - diff --git a/src/geometry/Bounds.js b/src/geometry/Bounds.js index 2fa59ad7..94192a19 100644 --- a/src/geometry/Bounds.js +++ b/src/geometry/Bounds.js @@ -1,3 +1,5 @@ +import {Point, toPoint} from './Point'; + /* * @class Bounds * @aka L.Bounds @@ -19,7 +21,7 @@ * ``` */ -L.Bounds = function (a, b) { +export function Bounds(a, b) { if (!a) { return; } var points = b ? [a, b] : a; @@ -27,13 +29,13 @@ L.Bounds = function (a, b) { for (var i = 0, len = points.length; i < len; i++) { this.extend(points[i]); } -}; +} -L.Bounds.prototype = { +Bounds.prototype = { // @method extend(point: Point): this // Extends the bounds to contain the given point. extend: function (point) { // (Point) - point = L.point(point); + point = toPoint(point); // @property min: Point // The top left corner of the rectangle. @@ -54,7 +56,7 @@ L.Bounds.prototype = { // @method getCenter(round?: Boolean): Point // Returns the center point of the bounds. getCenter: function (round) { - return new L.Point( + return new Point( (this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, round); }, @@ -62,13 +64,13 @@ L.Bounds.prototype = { // @method getBottomLeft(): Point // Returns the bottom-left point of the bounds. getBottomLeft: function () { - return new L.Point(this.min.x, this.max.y); + return new Point(this.min.x, this.max.y); }, // @method getTopRight(): Point // Returns the top-right point of the bounds. getTopRight: function () { // -> Point - return new L.Point(this.max.x, this.min.y); + return new Point(this.max.x, this.min.y); }, // @method getSize(): Point @@ -85,13 +87,13 @@ L.Bounds.prototype = { contains: function (obj) { var min, max; - if (typeof obj[0] === 'number' || obj instanceof L.Point) { - obj = L.point(obj); + if (typeof obj[0] === 'number' || obj instanceof Point) { + obj = toPoint(obj); } else { - obj = L.bounds(obj); + obj = toBounds(obj); } - if (obj instanceof L.Bounds) { + if (obj instanceof Bounds) { min = obj.min; max = obj.max; } else { @@ -108,7 +110,7 @@ L.Bounds.prototype = { // Returns `true` if the rectangle intersects the given bounds. Two bounds // intersect if they have at least one point in common. intersects: function (bounds) { // (Bounds) -> Boolean - bounds = L.bounds(bounds); + bounds = toBounds(bounds); var min = this.min, max = this.max, @@ -124,7 +126,7 @@ L.Bounds.prototype = { // Returns `true` if the rectangle overlaps the given bounds. Two bounds // overlap if their intersection is an area. overlaps: function (bounds) { // (Bounds) -> Boolean - bounds = L.bounds(bounds); + bounds = toBounds(bounds); var min = this.min, max = this.max, @@ -147,9 +149,9 @@ L.Bounds.prototype = { // @alternative // @factory L.bounds(points: Point[]) // Creates a Bounds object from the points it contains -L.bounds = function (a, b) { - if (!a || a instanceof L.Bounds) { +export function toBounds(a, b) { + if (!a || a instanceof Bounds) { return a; } - return new L.Bounds(a, b); -}; + return new Bounds(a, b); +} diff --git a/src/geometry/LineUtil.js b/src/geometry/LineUtil.js index f72c6407..7d78ebb4 100644 --- a/src/geometry/LineUtil.js +++ b/src/geometry/LineUtil.js @@ -1,228 +1,236 @@ +import {Point} from './Point'; +import * as Util from '../core/Util'; + + /* * @namespace LineUtil * * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast. */ -L.LineUtil = { +// Simplify polyline with vertex reduction and Douglas-Peucker simplification. +// Improves rendering performance dramatically by lessening the number of points to draw. - // Simplify polyline with vertex reduction and Douglas-Peucker simplification. - // Improves rendering performance dramatically by lessening the number of points to draw. - - // @function simplify(points: Point[], tolerance: Number): Point[] - // Dramatically reduces the number of points in a polyline while retaining - // its shape and returns a new array of simplified points, using the - // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). - // Used for a huge performance boost when processing/displaying Leaflet polylines for - // each zoom level and also reducing visual noise. tolerance affects the amount of - // simplification (lesser value means higher quality but slower and with more points). - // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). - simplify: function (points, tolerance) { - if (!tolerance || !points.length) { - return points.slice(); - } - - var sqTolerance = tolerance * tolerance; - - // stage 1: vertex reduction - points = this._reducePoints(points, sqTolerance); - - // stage 2: Douglas-Peucker simplification - points = this._simplifyDP(points, sqTolerance); - - return points; - }, - - // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number - // Returns the distance between point `p` and segment `p1` to `p2`. - pointToSegmentDistance: function (p, p1, p2) { - return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true)); - }, - - // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number - // Returns the closest point from a point `p` on a segment `p1` to `p2`. - closestPointOnSegment: function (p, p1, p2) { - return this._sqClosestPointOnSegment(p, p1, p2); - }, - - // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm - _simplifyDP: function (points, sqTolerance) { - - var len = points.length, - ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, - markers = new ArrayConstructor(len); - - markers[0] = markers[len - 1] = 1; - - this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1); - - var i, - newPoints = []; - - for (i = 0; i < len; i++) { - if (markers[i]) { - newPoints.push(points[i]); - } - } - - return newPoints; - }, - - _simplifyDPStep: function (points, markers, sqTolerance, first, last) { - - var maxSqDist = 0, - index, i, sqDist; - - for (i = first + 1; i <= last - 1; i++) { - sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true); - - if (sqDist > maxSqDist) { - index = i; - maxSqDist = sqDist; - } - } - - if (maxSqDist > sqTolerance) { - markers[index] = 1; - - this._simplifyDPStep(points, markers, sqTolerance, first, index); - this._simplifyDPStep(points, markers, sqTolerance, index, last); - } - }, - - // reduce points that are too close to each other to a single point - _reducePoints: function (points, sqTolerance) { - var reducedPoints = [points[0]]; - - for (var i = 1, prev = 0, len = points.length; i < len; i++) { - if (this._sqDist(points[i], points[prev]) > sqTolerance) { - reducedPoints.push(points[i]); - prev = i; - } - } - if (prev < len - 1) { - reducedPoints.push(points[len - 1]); - } - return reducedPoints; - }, - - - // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean - // Clips the segment a to b by rectangular bounds with the - // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) - // (modifying the segment points directly!). Used by Leaflet to only show polyline - // points that are on the screen or near, increasing performance. - clipSegment: function (a, b, bounds, useLastCode, round) { - var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), - codeB = this._getBitCode(b, bounds), - - codeOut, p, newCode; - - // save 2nd code to avoid calculating it on the next segment - this._lastCode = codeB; - - while (true) { - // if a,b is inside the clip window (trivial accept) - if (!(codeA | codeB)) { - return [a, b]; - } - - // if a,b is outside the clip window (trivial reject) - if (codeA & codeB) { - return false; - } - - // other cases - codeOut = codeA || codeB; - p = this._getEdgeIntersection(a, b, codeOut, bounds, round); - newCode = this._getBitCode(p, bounds); - - if (codeOut === codeA) { - a = p; - codeA = newCode; - } else { - b = p; - codeB = newCode; - } - } - }, - - _getEdgeIntersection: function (a, b, code, bounds, round) { - var dx = b.x - a.x, - dy = b.y - a.y, - min = bounds.min, - max = bounds.max, - x, y; - - if (code & 8) { // top - x = a.x + dx * (max.y - a.y) / dy; - y = max.y; - - } else if (code & 4) { // bottom - x = a.x + dx * (min.y - a.y) / dy; - y = min.y; - - } else if (code & 2) { // right - x = max.x; - y = a.y + dy * (max.x - a.x) / dx; - - } else if (code & 1) { // left - x = min.x; - y = a.y + dy * (min.x - a.x) / dx; - } - - return new L.Point(x, y, round); - }, - - _getBitCode: function (p, bounds) { - var code = 0; - - if (p.x < bounds.min.x) { // left - code |= 1; - } else if (p.x > bounds.max.x) { // right - code |= 2; - } - - if (p.y < bounds.min.y) { // bottom - code |= 4; - } else if (p.y > bounds.max.y) { // top - code |= 8; - } - - return code; - }, - - // square distance (to avoid unnecessary Math.sqrt calls) - _sqDist: function (p1, p2) { - var dx = p2.x - p1.x, - dy = p2.y - p1.y; - return dx * dx + dy * dy; - }, - - // return closest point on segment or distance to that point - _sqClosestPointOnSegment: function (p, p1, p2, sqDist) { - var x = p1.x, - y = p1.y, - dx = p2.x - x, - dy = p2.y - y, - dot = dx * dx + dy * dy, - t; - - if (dot > 0) { - t = ((p.x - x) * dx + (p.y - y) * dy) / dot; - - if (t > 1) { - x = p2.x; - y = p2.y; - } else if (t > 0) { - x += dx * t; - y += dy * t; - } - } - - dx = p.x - x; - dy = p.y - y; - - return sqDist ? dx * dx + dy * dy : new L.Point(x, y); +// @function simplify(points: Point[], tolerance: Number): Point[] +// Dramatically reduces the number of points in a polyline while retaining +// its shape and returns a new array of simplified points, using the +// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). +// Used for a huge performance boost when processing/displaying Leaflet polylines for +// each zoom level and also reducing visual noise. tolerance affects the amount of +// simplification (lesser value means higher quality but slower and with more points). +// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). +export function simplify(points, tolerance) { + if (!tolerance || !points.length) { + return points.slice(); } -}; + + var sqTolerance = tolerance * tolerance; + + // stage 1: vertex reduction + points = _reducePoints(points, sqTolerance); + + // stage 2: Douglas-Peucker simplification + points = _simplifyDP(points, sqTolerance); + + return points; +} + +// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number +// Returns the distance between point `p` and segment `p1` to `p2`. +export function pointToSegmentDistance(p, p1, p2) { + return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); +} + +// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number +// Returns the closest point from a point `p` on a segment `p1` to `p2`. +export function closestPointOnSegment(p, p1, p2) { + return _sqClosestPointOnSegment(p, p1, p2); +} + +// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm +function _simplifyDP(points, sqTolerance) { + + var len = points.length, + ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array, + markers = new ArrayConstructor(len); + + markers[0] = markers[len - 1] = 1; + + _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); + + var i, + newPoints = []; + + for (i = 0; i < len; i++) { + if (markers[i]) { + newPoints.push(points[i]); + } + } + + return newPoints; +} + +function _simplifyDPStep(points, markers, sqTolerance, first, last) { + + var maxSqDist = 0, + index, i, sqDist; + + for (i = first + 1; i <= last - 1; i++) { + sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); + + if (sqDist > maxSqDist) { + index = i; + maxSqDist = sqDist; + } + } + + if (maxSqDist > sqTolerance) { + markers[index] = 1; + + _simplifyDPStep(points, markers, sqTolerance, first, index); + _simplifyDPStep(points, markers, sqTolerance, index, last); + } +} + +// reduce points that are too close to each other to a single point +function _reducePoints(points, sqTolerance) { + var reducedPoints = [points[0]]; + + for (var i = 1, prev = 0, len = points.length; i < len; i++) { + if (_sqDist(points[i], points[prev]) > sqTolerance) { + reducedPoints.push(points[i]); + prev = i; + } + } + if (prev < len - 1) { + reducedPoints.push(points[len - 1]); + } + return reducedPoints; +} + +var _lastCode; + +// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean +// Clips the segment a to b by rectangular bounds with the +// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm) +// (modifying the segment points directly!). Used by Leaflet to only show polyline +// points that are on the screen or near, increasing performance. +export function clipSegment(a, b, bounds, useLastCode, round) { + var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), + codeB = _getBitCode(b, bounds), + + codeOut, p, newCode; + + // save 2nd code to avoid calculating it on the next segment + _lastCode = codeB; + + while (true) { + // if a,b is inside the clip window (trivial accept) + if (!(codeA | codeB)) { + return [a, b]; + } + + // if a,b is outside the clip window (trivial reject) + if (codeA & codeB) { + return false; + } + + // other cases + codeOut = codeA || codeB; + p = _getEdgeIntersection(a, b, codeOut, bounds, round); + newCode = _getBitCode(p, bounds); + + if (codeOut === codeA) { + a = p; + codeA = newCode; + } else { + b = p; + codeB = newCode; + } + } +} + +export function _getEdgeIntersection(a, b, code, bounds, round) { + var dx = b.x - a.x, + dy = b.y - a.y, + min = bounds.min, + max = bounds.max, + x, y; + + if (code & 8) { // top + x = a.x + dx * (max.y - a.y) / dy; + y = max.y; + + } else if (code & 4) { // bottom + x = a.x + dx * (min.y - a.y) / dy; + y = min.y; + + } else if (code & 2) { // right + x = max.x; + y = a.y + dy * (max.x - a.x) / dx; + + } else if (code & 1) { // left + x = min.x; + y = a.y + dy * (min.x - a.x) / dx; + } + + return new Point(x, y, round); +} + +export function _getBitCode(p, bounds) { + var code = 0; + + if (p.x < bounds.min.x) { // left + code |= 1; + } else if (p.x > bounds.max.x) { // right + code |= 2; + } + + if (p.y < bounds.min.y) { // bottom + code |= 4; + } else if (p.y > bounds.max.y) { // top + code |= 8; + } + + return code; +} + +// square distance (to avoid unnecessary Math.sqrt calls) +function _sqDist(p1, p2) { + var dx = p2.x - p1.x, + dy = p2.y - p1.y; + return dx * dx + dy * dy; +} + +// return closest point on segment or distance to that point +export function _sqClosestPointOnSegment(p, p1, p2, sqDist) { + var x = p1.x, + y = p1.y, + dx = p2.x - x, + dy = p2.y - y, + dot = dx * dx + dy * dy, + t; + + if (dot > 0) { + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; + + if (t > 1) { + x = p2.x; + y = p2.y; + } else if (t > 0) { + x += dx * t; + y += dy * t; + } + } + + dx = p.x - x; + dy = p.y - y; + + return sqDist ? dx * dx + dy * dy : new Point(x, y); +} + + +export function _flat(latlngs) { + // true if it's a flat array of latlngs; false if nested + return !Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); +} diff --git a/src/geometry/Point.js b/src/geometry/Point.js index 892558cf..6bc234e5 100644 --- a/src/geometry/Point.js +++ b/src/geometry/Point.js @@ -1,3 +1,5 @@ +import {isArray, formatNum} from '../core/Util'; + /* * @class Point * @aka L.Point @@ -18,26 +20,26 @@ * ``` */ -L.Point = function (x, y, round) { +export function Point(x, y, round) { // @property x: Number; The `x` coordinate of the point this.x = (round ? Math.round(x) : x); // @property y: Number; The `y` coordinate of the point this.y = (round ? Math.round(y) : y); -}; +} -L.Point.prototype = { +Point.prototype = { // @method clone(): Point // Returns a copy of the current point. clone: function () { - return new L.Point(this.x, this.y); + return new Point(this.x, this.y); }, // @method add(otherPoint: Point): Point // Returns the result of addition of the current and the given points. add: function (point) { // non-destructive, returns a new point - return this.clone()._add(L.point(point)); + return this.clone()._add(toPoint(point)); }, _add: function (point) { @@ -50,7 +52,7 @@ L.Point.prototype = { // @method subtract(otherPoint: Point): Point // Returns the result of subtraction of the given point from the current. subtract: function (point) { - return this.clone()._subtract(L.point(point)); + return this.clone()._subtract(toPoint(point)); }, _subtract: function (point) { @@ -89,14 +91,14 @@ L.Point.prototype = { // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) // defined by `scale`. scaleBy: function (point) { - return new L.Point(this.x * point.x, this.y * point.y); + return new Point(this.x * point.x, this.y * point.y); }, // @method unscaleBy(scale: Point): Point // Inverse of `scaleBy`. Divide each coordinate of the current point by // each coordinate of `scale`. unscaleBy: function (point) { - return new L.Point(this.x / point.x, this.y / point.y); + return new Point(this.x / point.x, this.y / point.y); }, // @method round(): Point @@ -138,7 +140,7 @@ L.Point.prototype = { // @method distanceTo(otherPoint: Point): Number // Returns the cartesian distance between the current and the given points. distanceTo: function (point) { - point = L.point(point); + point = toPoint(point); var x = point.x - this.x, y = point.y - this.y; @@ -149,7 +151,7 @@ L.Point.prototype = { // @method equals(otherPoint: Point): Boolean // Returns `true` if the given point has the same coordinates. equals: function (point) { - point = L.point(point); + point = toPoint(point); return point.x === this.x && point.y === this.y; @@ -158,7 +160,7 @@ L.Point.prototype = { // @method contains(otherPoint: Point): Boolean // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). contains: function (point) { - point = L.point(point); + point = toPoint(point); return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y); @@ -168,8 +170,8 @@ L.Point.prototype = { // Returns a string representation of the point for debugging purposes. toString: function () { return 'Point(' + - L.Util.formatNum(this.x) + ', ' + - L.Util.formatNum(this.y) + ')'; + formatNum(this.x) + ', ' + + formatNum(this.y) + ')'; } }; @@ -183,18 +185,18 @@ L.Point.prototype = { // @alternative // @factory L.point(coords: Object) // Expects a plain object of the form `{x: Number, y: Number}` instead. -L.point = function (x, y, round) { - if (x instanceof L.Point) { +export function toPoint(x, y, round) { + if (x instanceof Point) { return x; } - if (L.Util.isArray(x)) { - return new L.Point(x[0], x[1]); + if (isArray(x)) { + return new Point(x[0], x[1]); } if (x === undefined || x === null) { return x; } if (typeof x === 'object' && 'x' in x && 'y' in x) { - return new L.Point(x.x, x.y); + return new Point(x.x, x.y); } - return new L.Point(x, y, round); -}; + return new Point(x, y, round); +} diff --git a/src/geometry/PolyUtil.js b/src/geometry/PolyUtil.js index 8081b3bf..f41cb28d 100644 --- a/src/geometry/PolyUtil.js +++ b/src/geometry/PolyUtil.js @@ -1,26 +1,25 @@ +import * as LineUtil from './LineUtil'; + /* * @namespace PolyUtil * Various utility functions for polygon geometries. */ -L.PolyUtil = {}; - /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)). * Used by Leaflet to only show polygon points that are on the screen or near, increasing * performance. Note that polygon points needs different algorithm for clipping * than polyline, so there's a seperate method for it. */ -L.PolyUtil.clipPolygon = function (points, bounds, round) { +export function clipPolygon(points, bounds, round) { var clippedPoints, edges = [1, 4, 2, 8], i, j, k, a, b, - len, edge, p, - lu = L.LineUtil; + len, edge, p; for (i = 0, len = points.length; i < len; i++) { - points[i]._code = lu._getBitCode(points[i], bounds); + points[i]._code = LineUtil._getBitCode(points[i], bounds); } // for each edge (left, bottom, right, top) @@ -36,16 +35,16 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) { if (!(a._code & edge)) { // if b is outside the clip window (a->b goes out of screen) if (b._code & edge) { - p = lu._getEdgeIntersection(b, a, edge, bounds, round); - p._code = lu._getBitCode(p, bounds); + p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round); + p._code = LineUtil._getBitCode(p, bounds); clippedPoints.push(p); } clippedPoints.push(a); // else if b is inside the clip window (a->b enters the screen) } else if (!(b._code & edge)) { - p = lu._getEdgeIntersection(b, a, edge, bounds, round); - p._code = lu._getBitCode(p, bounds); + p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round); + p._code = LineUtil._getBitCode(p, bounds); clippedPoints.push(p); } } @@ -53,4 +52,4 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) { } return points; -}; +} diff --git a/src/geometry/Transformation.js b/src/geometry/Transformation.js index c6331117..b99f6cb4 100644 --- a/src/geometry/Transformation.js +++ b/src/geometry/Transformation.js @@ -1,3 +1,6 @@ +import {Point} from './Point'; +import * as Util from '../core/Util'; + /* * @class Transformation * @aka L.Transformation @@ -19,8 +22,8 @@ // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) // Creates a `Transformation` object with the given coefficients. -L.Transformation = function (a, b, c, d) { - if (L.Util.isArray(a)) { +export function Transformation(a, b, c, d) { + if (Util.isArray(a)) { // use array properties this._a = a[0]; this._b = a[1]; @@ -32,9 +35,9 @@ L.Transformation = function (a, b, c, d) { this._b = b; this._c = c; this._d = d; -}; +} -L.Transformation.prototype = { +Transformation.prototype = { // @method transform(point: Point, scale?: Number): Point // Returns a transformed point, optionally multiplied by the given scale. // Only accepts actual `L.Point` instances, not arrays. @@ -55,9 +58,9 @@ L.Transformation.prototype = { // by the given scale. Only accepts actual `L.Point` instances, not arrays. untransform: function (point, scale) { scale = scale || 1; - return new L.Point( - (point.x / scale - this._b) / this._a, - (point.y / scale - this._d) / this._c); + return new Point( + (point.x / scale - this._b) / this._a, + (point.y / scale - this._d) / this._c); } }; @@ -71,6 +74,6 @@ L.Transformation.prototype = { // Expects an coeficients array of the form // `[a: Number, b: Number, c: Number, d: Number]`. -L.transformation = function (a, b, c, d) { - return new L.Transformation(a, b, c, d); -}; +export function toTransformation(a, b, c, d) { + return new Transformation(a, b, c, d); +} diff --git a/src/layer/DivOverlay.js b/src/layer/DivOverlay.js index fb2597f2..efd76f1b 100644 --- a/src/layer/DivOverlay.js +++ b/src/layer/DivOverlay.js @@ -1,3 +1,9 @@ +import {Layer} from './Layer'; +import * as Util from '../core/Util'; +import {toLatLng} from '../geo/LatLng'; +import {toPoint} from '../geometry/Point'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class DivOverlay * @inherits Layer @@ -6,7 +12,7 @@ */ // @namespace DivOverlay -L.DivOverlay = L.Layer.extend({ +export var DivOverlay = Layer.extend({ // @section // @aka DivOverlay options @@ -26,7 +32,7 @@ L.DivOverlay = L.Layer.extend({ }, initialize: function (options, source) { - L.setOptions(this, options); + Util.setOptions(this, options); this._source = source; }, @@ -39,7 +45,7 @@ L.DivOverlay = L.Layer.extend({ } if (map._fadeAnimated) { - L.DomUtil.setOpacity(this._container, 0); + DomUtil.setOpacity(this._container, 0); } clearTimeout(this._removeTimeout); @@ -47,7 +53,7 @@ L.DivOverlay = L.Layer.extend({ this.update(); if (map._fadeAnimated) { - L.DomUtil.setOpacity(this._container, 1); + DomUtil.setOpacity(this._container, 1); } this.bringToFront(); @@ -55,10 +61,10 @@ L.DivOverlay = L.Layer.extend({ onRemove: function (map) { if (map._fadeAnimated) { - L.DomUtil.setOpacity(this._container, 0); - this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); + DomUtil.setOpacity(this._container, 0); + this._removeTimeout = setTimeout(Util.bind(DomUtil.remove, undefined, this._container), 200); } else { - L.DomUtil.remove(this._container); + DomUtil.remove(this._container); } }, @@ -72,7 +78,7 @@ L.DivOverlay = L.Layer.extend({ // @method setLatLng(latlng: LatLng): this // Sets the geographical point where the popup will open. setLatLng: function (latlng) { - this._latlng = L.latLng(latlng); + this._latlng = toLatLng(latlng); if (this._map) { this._updatePosition(); this._adjustPan(); @@ -138,7 +144,7 @@ L.DivOverlay = L.Layer.extend({ // Brings this popup in front of other popups (in the same map pane). bringToFront: function () { if (this._map) { - L.DomUtil.toFront(this._container); + DomUtil.toFront(this._container); } return this; }, @@ -147,7 +153,7 @@ L.DivOverlay = L.Layer.extend({ // Brings this popup to the back of other popups (in the same map pane). bringToBack: function () { if (this._map) { - L.DomUtil.toBack(this._container); + DomUtil.toBack(this._container); } return this; }, @@ -173,11 +179,11 @@ L.DivOverlay = L.Layer.extend({ if (!this._map) { return; } var pos = this._map.latLngToLayerPoint(this._latlng), - offset = L.point(this.options.offset), + offset = toPoint(this.options.offset), anchor = this._getAnchor(); if (this._zoomAnimated) { - L.DomUtil.setPosition(this._container, pos.add(anchor)); + DomUtil.setPosition(this._container, pos.add(anchor)); } else { offset = offset.add(pos).add(anchor); } diff --git a/src/layer/FeatureGroup.js b/src/layer/FeatureGroup.js index 1008f35a..630574e4 100644 --- a/src/layer/FeatureGroup.js +++ b/src/layer/FeatureGroup.js @@ -1,3 +1,6 @@ +import {LayerGroup} from './LayerGroup'; +import {LatLngBounds} from '../geo/LatLngBounds'; + /* * @class FeatureGroup * @aka L.FeatureGroup @@ -20,7 +23,7 @@ * ``` */ -L.FeatureGroup = L.LayerGroup.extend({ +export var FeatureGroup = LayerGroup.extend({ addLayer: function (layer) { if (this.hasLayer(layer)) { @@ -29,7 +32,7 @@ L.FeatureGroup = L.LayerGroup.extend({ layer.addEventParent(this); - L.LayerGroup.prototype.addLayer.call(this, layer); + LayerGroup.prototype.addLayer.call(this, layer); // @event layeradd: LayerEvent // Fired when a layer is added to this `FeatureGroup` @@ -46,7 +49,7 @@ L.FeatureGroup = L.LayerGroup.extend({ layer.removeEventParent(this); - L.LayerGroup.prototype.removeLayer.call(this, layer); + LayerGroup.prototype.removeLayer.call(this, layer); // @event layerremove: LayerEvent // Fired when a layer is removed from this `FeatureGroup` @@ -74,7 +77,7 @@ L.FeatureGroup = L.LayerGroup.extend({ // @method getBounds(): LatLngBounds // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). getBounds: function () { - var bounds = new L.LatLngBounds(); + var bounds = new LatLngBounds(); for (var id in this._layers) { var layer = this._layers[id]; @@ -86,6 +89,6 @@ L.FeatureGroup = L.LayerGroup.extend({ // @factory L.featureGroup(layers: Layer[]) // Create a feature group, optionally given an initial set of layers. -L.featureGroup = function (layers) { - return new L.FeatureGroup(layers); +export var featureGroup = function (layers) { + return new FeatureGroup(layers); }; diff --git a/src/layer/GeoJSON.js b/src/layer/GeoJSON.js index 3318ff17..a8a64a38 100644 --- a/src/layer/GeoJSON.js +++ b/src/layer/GeoJSON.js @@ -1,3 +1,15 @@ +import {LayerGroup} from './LayerGroup'; +import {FeatureGroup} from './FeatureGroup'; +import * as Util from '../core/Util'; +import {Marker} from './marker/Marker'; +import {Circle} from './vector/Circle'; +import {CircleMarker} from './vector/CircleMarker'; +import {Polyline} from './vector/Polyline'; +import {Polygon} from './vector/Polygon'; +import {LatLng} from '../geo/LatLng'; +import * as LineUtil from '../geometry/LineUtil'; + + /* * @class GeoJSON * @aka L.GeoJSON @@ -19,7 +31,7 @@ * ``` */ -L.GeoJSON = L.FeatureGroup.extend({ +export var GeoJSON = FeatureGroup.extend({ /* @section * @aka GeoJSON options @@ -69,7 +81,7 @@ L.GeoJSON = L.FeatureGroup.extend({ */ initialize: function (geojson, options) { - L.setOptions(this, options); + Util.setOptions(this, options); this._layers = {}; @@ -81,7 +93,7 @@ L.GeoJSON = L.FeatureGroup.extend({ // @method addData( data ): this // Adds a GeoJSON object to the layer. addData: function (geojson) { - var features = L.Util.isArray(geojson) ? geojson : geojson.features, + var features = Util.isArray(geojson) ? geojson : geojson.features, i, len, feature; if (features) { @@ -99,11 +111,11 @@ L.GeoJSON = L.FeatureGroup.extend({ if (options.filter && !options.filter(geojson)) { return this; } - var layer = L.GeoJSON.geometryToLayer(geojson, options); + var layer = geometryToLayer(geojson, options); if (!layer) { return this; } - layer.feature = L.GeoJSON.asFeature(geojson); + layer.feature = asFeature(geojson); layer.defaultOptions = layer.options; this.resetStyle(layer); @@ -119,7 +131,7 @@ L.GeoJSON = L.FeatureGroup.extend({ // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. resetStyle: function (layer) { // reset any custom styles - layer.options = L.Util.extend({}, layer.defaultOptions); + layer.options = Util.extend({}, layer.defaultOptions); this._setLayerStyle(layer, this.options.style); return this; }, @@ -144,143 +156,142 @@ L.GeoJSON = L.FeatureGroup.extend({ // @section // There are several static functions which can be called without instantiating L.GeoJSON: -L.extend(L.GeoJSON, { - // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer - // Creates a `Layer` from a given GeoJSON feature. Can use a custom - // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) - // functions if provided as options. - geometryToLayer: function (geojson, options) { - var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, - coords = geometry ? geometry.coordinates : null, - layers = [], - pointToLayer = options && options.pointToLayer, - coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng, - latlng, latlngs, i, len; +// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer +// Creates a `Layer` from a given GeoJSON feature. Can use a custom +// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng) +// functions if provided as options. +export function geometryToLayer(geojson, options) { - if (!coords && !geometry) { - return null; - } + var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, + coords = geometry ? geometry.coordinates : null, + layers = [], + pointToLayer = options && options.pointToLayer, + _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, + latlng, latlngs, i, len; - switch (geometry.type) { - case 'Point': - latlng = coordsToLatLng(coords); - return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); - - case 'MultiPoint': - for (i = 0, len = coords.length; i < len; i++) { - latlng = coordsToLatLng(coords[i]); - layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); - } - return new L.FeatureGroup(layers); - - case 'LineString': - case 'MultiLineString': - latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng); - return new L.Polyline(latlngs, options); - - case 'Polygon': - case 'MultiPolygon': - latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng); - return new L.Polygon(latlngs, options); - - case 'GeometryCollection': - for (i = 0, len = geometry.geometries.length; i < len; i++) { - var layer = this.geometryToLayer({ - geometry: geometry.geometries[i], - type: 'Feature', - properties: geojson.properties - }, options); - - if (layer) { - layers.push(layer); - } - } - return new L.FeatureGroup(layers); - - default: - throw new Error('Invalid GeoJSON object.'); - } - }, - - // @function coordsToLatLng(coords: Array): LatLng - // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) - // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. - coordsToLatLng: function (coords) { - return new L.LatLng(coords[1], coords[0], coords[2]); - }, - - // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array - // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. - // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). - // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. - coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { - var latlngs = []; - - for (var i = 0, len = coords.length, latlng; i < len; i++) { - latlng = levelsDeep ? - this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) : - (coordsToLatLng || this.coordsToLatLng)(coords[i]); - - latlngs.push(latlng); - } - - return latlngs; - }, - - // @function latLngToCoords(latlng: LatLng): Array - // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) - latLngToCoords: function (latlng) { - return latlng.alt !== undefined ? - [latlng.lng, latlng.lat, latlng.alt] : - [latlng.lng, latlng.lat]; - }, - - // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array - // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) - // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. - latLngsToCoords: function (latlngs, levelsDeep, closed) { - var coords = []; - - for (var i = 0, len = latlngs.length; i < len; i++) { - coords.push(levelsDeep ? - L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) : - L.GeoJSON.latLngToCoords(latlngs[i])); - } - - if (!levelsDeep && closed) { - coords.push(coords[0]); - } - - return coords; - }, - - getFeature: function (layer, newGeometry) { - return layer.feature ? - L.extend({}, layer.feature, {geometry: newGeometry}) : - L.GeoJSON.asFeature(newGeometry); - }, - - // @function asFeature(geojson: Object): Object - // Normalize GeoJSON geometries/features into GeoJSON features. - asFeature: function (geojson) { - if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { - return geojson; - } - - return { - type: 'Feature', - properties: {}, - geometry: geojson - }; + if (!coords && !geometry) { + return null; } -}); + + switch (geometry.type) { + case 'Point': + latlng = _coordsToLatLng(coords); + return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng); + + case 'MultiPoint': + for (i = 0, len = coords.length; i < len; i++) { + latlng = _coordsToLatLng(coords[i]); + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng)); + } + return new FeatureGroup(layers); + + case 'LineString': + case 'MultiLineString': + latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng); + return new Polyline(latlngs, options); + + case 'Polygon': + case 'MultiPolygon': + latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng); + return new Polygon(latlngs, options); + + case 'GeometryCollection': + for (i = 0, len = geometry.geometries.length; i < len; i++) { + var layer = geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, options); + + if (layer) { + layers.push(layer); + } + } + return new FeatureGroup(layers); + + default: + throw new Error('Invalid GeoJSON object.'); + } +} + +// @function coordsToLatLng(coords: Array): LatLng +// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude) +// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points. +export function coordsToLatLng(coords) { + return new LatLng(coords[1], coords[0], coords[2]); +} + +// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array +// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array. +// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default). +// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function. +export function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { + var latlngs = []; + + for (var i = 0, len = coords.length, latlng; i < len; i++) { + latlng = levelsDeep ? + coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : + (_coordsToLatLng || coordsToLatLng)(coords[i]); + + latlngs.push(latlng); + } + + return latlngs; +} + +// @function latLngToCoords(latlng: LatLng): Array +// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng) +export function latLngToCoords(latlng) { + return latlng.alt !== undefined ? + [latlng.lng, latlng.lat, latlng.alt] : + [latlng.lng, latlng.lat]; +} + +// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array +// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs) +// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default. +export function latLngsToCoords(latlngs, levelsDeep, closed) { + var coords = []; + + for (var i = 0, len = latlngs.length; i < len; i++) { + coords.push(levelsDeep ? + latLngsToCoords(latlngs[i], levelsDeep - 1, closed) : + latLngToCoords(latlngs[i])); + } + + if (!levelsDeep && closed) { + coords.push(coords[0]); + } + + return coords; +} + +export function getFeature(layer, newGeometry) { + return layer.feature ? + Util.extend({}, layer.feature, {geometry: newGeometry}) : + asFeature(newGeometry); +} + +// @function asFeature(geojson: Object): Object +// Normalize GeoJSON geometries/features into GeoJSON features. +export function asFeature(geojson) { + if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') { + return geojson; + } + + return { + type: 'Feature', + properties: {}, + geometry: geojson + }; +} var PointToGeoJSON = { toGeoJSON: function () { - return L.GeoJSON.getFeature(this, { + return getFeature(this, { type: 'Point', - coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) + coordinates: latLngToCoords(this.getLatLng()) }); } }; @@ -288,51 +299,55 @@ var PointToGeoJSON = { // @namespace Marker // @method toGeoJSON(): Object // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). -L.Marker.include(PointToGeoJSON); +Marker.include(PointToGeoJSON); // @namespace CircleMarker // @method toGeoJSON(): Object // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). -L.Circle.include(PointToGeoJSON); -L.CircleMarker.include(PointToGeoJSON); +Circle.include(PointToGeoJSON); +CircleMarker.include(PointToGeoJSON); // @namespace Polyline // @method toGeoJSON(): Object // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). -L.Polyline.prototype.toGeoJSON = function () { - var multi = !L.Polyline._flat(this._latlngs); +Polyline.include({ + toGeoJSON: function () { + var multi = !LineUtil._flat(this._latlngs); - var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0); + var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0); - return L.GeoJSON.getFeature(this, { - type: (multi ? 'Multi' : '') + 'LineString', - coordinates: coords - }); -}; + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'LineString', + coordinates: coords + }); + } +}); // @namespace Polygon // @method toGeoJSON(): Object // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). -L.Polygon.prototype.toGeoJSON = function () { - var holes = !L.Polyline._flat(this._latlngs), - multi = holes && !L.Polyline._flat(this._latlngs[0]); +Polygon.include({ + toGeoJSON: function () { + var holes = !LineUtil._flat(this._latlngs), + multi = holes && !LineUtil._flat(this._latlngs[0]); - var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true); + var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true); - if (!holes) { - coords = [coords]; + if (!holes) { + coords = [coords]; + } + + return getFeature(this, { + type: (multi ? 'Multi' : '') + 'Polygon', + coordinates: coords + }); } - - return L.GeoJSON.getFeature(this, { - type: (multi ? 'Multi' : '') + 'Polygon', - coordinates: coords - }); -}; +}); // @namespace LayerGroup -L.LayerGroup.include({ +LayerGroup.include({ toMultiPoint: function () { var coords = []; @@ -340,7 +355,7 @@ L.LayerGroup.include({ coords.push(layer.toGeoJSON().geometry.coordinates); }); - return L.GeoJSON.getFeature(this, { + return getFeature(this, { type: 'MultiPoint', coordinates: coords }); @@ -362,12 +377,12 @@ L.LayerGroup.include({ this.eachLayer(function (layer) { if (layer.toGeoJSON) { var json = layer.toGeoJSON(); - jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); + jsons.push(isGeometryCollection ? json.geometry : asFeature(json)); } }); if (isGeometryCollection) { - return L.GeoJSON.getFeature(this, { + return getFeature(this, { geometries: jsons, type: 'GeometryCollection' }); @@ -385,8 +400,9 @@ L.LayerGroup.include({ // Creates a GeoJSON layer. Optionally accepts an object in // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map // (you can alternatively add it later with `addData` method) and an `options` object. -L.geoJSON = function (geojson, options) { - return new L.GeoJSON(geojson, options); -}; +export function geoJSON(geojson, options) { + return new GeoJSON(geojson, options); +} + // Backward compatibility. -L.geoJson = L.geoJSON; +export var geoJson = geoJSON; diff --git a/src/layer/ImageOverlay.js b/src/layer/ImageOverlay.js index d84ea327..a854e8ae 100644 --- a/src/layer/ImageOverlay.js +++ b/src/layer/ImageOverlay.js @@ -1,3 +1,9 @@ +import {Layer} from './Layer'; +import * as Util from '../core/Util'; +import {toLatLngBounds} from '../geo/LatLngBounds'; +import {Bounds} from '../geometry/Bounds'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class ImageOverlay * @aka L.ImageOverlay @@ -14,7 +20,7 @@ * ``` */ -L.ImageOverlay = L.Layer.extend({ +export var ImageOverlay = Layer.extend({ // @section // @aka ImageOverlay options @@ -38,9 +44,9 @@ L.ImageOverlay = L.Layer.extend({ initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) this._url = url; - this._bounds = L.latLngBounds(bounds); + this._bounds = toLatLngBounds(bounds); - L.setOptions(this, options); + Util.setOptions(this, options); }, onAdd: function () { @@ -53,7 +59,7 @@ L.ImageOverlay = L.Layer.extend({ } if (this.options.interactive) { - L.DomUtil.addClass(this._image, 'leaflet-interactive'); + DomUtil.addClass(this._image, 'leaflet-interactive'); this.addInteractiveTarget(this._image); } @@ -62,7 +68,7 @@ L.ImageOverlay = L.Layer.extend({ }, onRemove: function () { - L.DomUtil.remove(this._image); + DomUtil.remove(this._image); if (this.options.interactive) { this.removeInteractiveTarget(this._image); } @@ -90,7 +96,7 @@ L.ImageOverlay = L.Layer.extend({ // Brings the layer to the top of all overlays. bringToFront: function () { if (this._map) { - L.DomUtil.toFront(this._image); + DomUtil.toFront(this._image); } return this; }, @@ -99,7 +105,7 @@ L.ImageOverlay = L.Layer.extend({ // Brings the layer to the bottom of all overlays. bringToBack: function () { if (this._map) { - L.DomUtil.toBack(this._image); + DomUtil.toBack(this._image); } return this; }, @@ -152,13 +158,13 @@ L.ImageOverlay = L.Layer.extend({ }, _initImage: function () { - var img = this._image = L.DomUtil.create('img', + var img = this._image = DomUtil.create('img', 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '')); - img.onselectstart = L.Util.falseFn; - img.onmousemove = L.Util.falseFn; + img.onselectstart = Util.falseFn; + img.onmousemove = Util.falseFn; - img.onload = L.bind(this.fire, this, 'load'); + img.onload = Util.bind(this.fire, this, 'load'); if (this.options.crossOrigin) { img.crossOrigin = ''; @@ -172,30 +178,30 @@ L.ImageOverlay = L.Layer.extend({ var scale = this._map.getZoomScale(e.zoom), offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; - L.DomUtil.setTransform(this._image, offset, scale); + DomUtil.setTransform(this._image, offset, scale); }, _reset: function () { var image = this._image, - bounds = new L.Bounds( + bounds = new Bounds( this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getSouthEast())), size = bounds.getSize(); - L.DomUtil.setPosition(image, bounds.min); + DomUtil.setPosition(image, bounds.min); image.style.width = size.x + 'px'; image.style.height = size.y + 'px'; }, _updateOpacity: function () { - L.DomUtil.setOpacity(this._image, this.options.opacity); + DomUtil.setOpacity(this._image, this.options.opacity); } }); // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options) // Instantiates an image overlay object given the URL of the image and the // geographical bounds it is tied to. -L.imageOverlay = function (url, bounds, options) { - return new L.ImageOverlay(url, bounds, options); +export var imageOverlay = function (url, bounds, options) { + return new ImageOverlay(url, bounds, options); }; diff --git a/src/layer/Layer.js b/src/layer/Layer.js index 9182f229..0c637b80 100644 --- a/src/layer/Layer.js +++ b/src/layer/Layer.js @@ -1,3 +1,6 @@ +import {Evented} from '../core/Events'; +import {Map} from '../map/Map'; +import * as Util from '../core/Util'; /* * @class Layer @@ -24,7 +27,7 @@ */ -L.Layer = L.Evented.extend({ +export var Layer = Evented.extend({ // Classes extending `L.Layer` will inherit the following options: options: { @@ -71,12 +74,12 @@ L.Layer = L.Evented.extend({ }, addInteractiveTarget: function (targetEl) { - this._map._targets[L.stamp(targetEl)] = this; + this._map._targets[Util.stamp(targetEl)] = this; return this; }, removeInteractiveTarget: function (targetEl) { - delete this._map._targets[L.stamp(targetEl)]; + delete this._map._targets[Util.stamp(targetEl)]; return this; }, @@ -147,11 +150,11 @@ L.Layer = L.Evented.extend({ * * @section Methods for Layers and Controls */ -L.Map.include({ +Map.include({ // @method addLayer(layer: Layer): this // Adds the given layer to the map addLayer: function (layer) { - var id = L.stamp(layer); + var id = Util.stamp(layer); if (this._layers[id]) { return this; } this._layers[id] = layer; @@ -169,7 +172,7 @@ L.Map.include({ // @method removeLayer(layer: Layer): this // Removes the given layer from the map. removeLayer: function (layer) { - var id = L.stamp(layer); + var id = Util.stamp(layer); if (!this._layers[id]) { return this; } @@ -196,7 +199,7 @@ L.Map.include({ // @method hasLayer(layer: Layer): Boolean // Returns `true` if the given layer is currently added to the map hasLayer: function (layer) { - return !!layer && (L.stamp(layer) in this._layers); + return !!layer && (Util.stamp(layer) in this._layers); }, /* @method eachLayer(fn: Function, context?: Object): this @@ -215,7 +218,7 @@ L.Map.include({ }, _addLayers: function (layers) { - layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; + layers = layers ? (Util.isArray(layers) ? layers : [layers]) : []; for (var i = 0, len = layers.length; i < len; i++) { this.addLayer(layers[i]); @@ -224,13 +227,13 @@ L.Map.include({ _addZoomLimit: function (layer) { if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { - this._zoomBoundLayers[L.stamp(layer)] = layer; + this._zoomBoundLayers[Util.stamp(layer)] = layer; this._updateZoomLevels(); } }, _removeZoomLimit: function (layer) { - var id = L.stamp(layer); + var id = Util.stamp(layer); if (this._zoomBoundLayers[id]) { delete this._zoomBoundLayers[id]; diff --git a/src/layer/LayerGroup.js b/src/layer/LayerGroup.js index dc4eff40..963448e3 100644 --- a/src/layer/LayerGroup.js +++ b/src/layer/LayerGroup.js @@ -1,3 +1,7 @@ + +import {Layer} from './Layer'; +import * as Util from '../core/Util'; + /* * @class LayerGroup * @aka L.LayerGroup @@ -16,7 +20,7 @@ * ``` */ -L.LayerGroup = L.Layer.extend({ +export var LayerGroup = Layer.extend({ initialize: function (layers) { this._layers = {}; @@ -147,13 +151,13 @@ L.LayerGroup = L.Layer.extend({ // @method getLayerId(layer: Layer): Number // Returns the internal ID for a layer getLayerId: function (layer) { - return L.stamp(layer); + return Util.stamp(layer); } }); // @factory L.layerGroup(layers: Layer[]) // Create a layer group, optionally given an initial set of layers. -L.layerGroup = function (layers) { - return new L.LayerGroup(layers); +export var layerGroup = function (layers) { + return new LayerGroup(layers); }; diff --git a/src/layer/Popup.js b/src/layer/Popup.js index d3330a79..73f17898 100644 --- a/src/layer/Popup.js +++ b/src/layer/Popup.js @@ -1,3 +1,13 @@ +import {DivOverlay} from './DivOverlay'; +import * as DomEvent from '../dom/DomEvent'; +import * as DomUtil from '../dom/DomUtil'; +import {Point, toPoint} from '../geometry/Point'; +import {Map} from '../map/Map'; +import {Layer} from './Layer'; +import {FeatureGroup} from './FeatureGroup'; +import * as Util from '../core/Util'; +import {Path} from './vector/Path'; + /* * @class Popup * @inherits DivOverlay @@ -26,7 +36,7 @@ // @namespace Popup -L.Popup = L.DivOverlay.extend({ +export var Popup = DivOverlay.extend({ // @section // @aka Popup options @@ -92,7 +102,7 @@ L.Popup = L.DivOverlay.extend({ }, onAdd: function (map) { - L.DivOverlay.prototype.onAdd.call(this, map); + DivOverlay.prototype.onAdd.call(this, map); // @namespace Map // @section Popup events @@ -108,14 +118,14 @@ L.Popup = L.DivOverlay.extend({ this._source.fire('popupopen', {popup: this}, true); // For non-path layers, we toggle the popup when clicking // again the layer, so prevent the map to reopen it. - if (!(this._source instanceof L.Path)) { - this._source.on('preclick', L.DomEvent.stopPropagation); + if (!(this._source instanceof Path)) { + this._source.on('preclick', DomEvent.stopPropagation); } } }, onRemove: function (map) { - L.DivOverlay.prototype.onRemove.call(this, map); + DivOverlay.prototype.onRemove.call(this, map); // @namespace Map // @section Popup events @@ -129,14 +139,14 @@ L.Popup = L.DivOverlay.extend({ // @event popupclose: PopupEvent // Fired when a popup bound to this layer is closed this._source.fire('popupclose', {popup: this}, true); - if (!(this._source instanceof L.Path)) { - this._source.off('preclick', L.DomEvent.stopPropagation); + if (!(this._source instanceof Path)) { + this._source.off('preclick', DomEvent.stopPropagation); } } }, getEvents: function () { - var events = L.DivOverlay.prototype.getEvents.call(this); + var events = DivOverlay.prototype.getEvents.call(this); if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) { events.preclick = this._close; @@ -157,28 +167,27 @@ L.Popup = L.DivOverlay.extend({ _initLayout: function () { var prefix = 'leaflet-popup', - container = this._container = L.DomUtil.create('div', + container = this._container = DomUtil.create('div', prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-animated'); if (this.options.closeButton) { - var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container); + var closeButton = this._closeButton = DomUtil.create('a', prefix + '-close-button', container); closeButton.href = '#close'; closeButton.innerHTML = '×'; - L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); + DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this); } - var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container); - this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); + var wrapper = this._wrapper = DomUtil.create('div', prefix + '-content-wrapper', container); + this._contentNode = DomUtil.create('div', prefix + '-content', wrapper); - L.DomEvent - .disableClickPropagation(wrapper) - .disableScrollPropagation(this._contentNode) - .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); + DomEvent.disableClickPropagation(wrapper); + DomEvent.disableScrollPropagation(this._contentNode); + DomEvent.on(wrapper, 'contextmenu', DomEvent.stopPropagation); - this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); - this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); + this._tipContainer = DomUtil.create('div', prefix + '-tip-container', container); + this._tip = DomUtil.create('div', prefix + '-tip', this._tipContainer); }, _updateLayout: function () { @@ -203,9 +212,9 @@ L.Popup = L.DivOverlay.extend({ if (maxHeight && height > maxHeight) { style.height = maxHeight + 'px'; - L.DomUtil.addClass(container, scrolledClass); + DomUtil.addClass(container, scrolledClass); } else { - L.DomUtil.removeClass(container, scrolledClass); + DomUtil.removeClass(container, scrolledClass); } this._containerWidth = this._container.offsetWidth; @@ -214,24 +223,24 @@ L.Popup = L.DivOverlay.extend({ _animateZoom: function (e) { var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), anchor = this._getAnchor(); - L.DomUtil.setPosition(this._container, pos.add(anchor)); + DomUtil.setPosition(this._container, pos.add(anchor)); }, _adjustPan: function () { if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } var map = this._map, - marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, + marginBottom = parseInt(DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0, containerHeight = this._container.offsetHeight + marginBottom, containerWidth = this._containerWidth, - layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); + layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); - layerPos._add(L.DomUtil.getPosition(this._container)); + layerPos._add(DomUtil.getPosition(this._container)); var containerPos = map.layerPointToContainerPoint(layerPos), - padding = L.point(this.options.autoPanPadding), - paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), - paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), + padding = toPoint(this.options.autoPanPadding), + paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), + paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), size = map.getSize(), dx = 0, dy = 0; @@ -262,12 +271,12 @@ L.Popup = L.DivOverlay.extend({ _onCloseButtonClick: function (e) { this._close(); - L.DomEvent.stop(e); + DomEvent.stop(e); }, _getAnchor: function () { // Where should we anchor the popup on the source layer? - return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); + return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); } }); @@ -275,8 +284,8 @@ L.Popup = L.DivOverlay.extend({ // @namespace Popup // @factory L.popup(options?: Popup options, source?: Layer) // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers. -L.popup = function (options, source) { - return new L.Popup(options, source); +export var popup = function (options, source) { + return new Popup(options, source); }; @@ -285,22 +294,22 @@ L.popup = function (options, source) { * @option closePopupOnClick: Boolean = true * Set it to `false` if you don't want popups to close when user clicks the map. */ -L.Map.mergeOptions({ +Map.mergeOptions({ closePopupOnClick: true }); // @namespace Map // @section Methods for Layers and Controls -L.Map.include({ +Map.include({ // @method openPopup(popup: Popup): this // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). // @alternative // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this // Creates a popup with the specified content and options and opens it in the given point on a map. openPopup: function (popup, latlng, options) { - if (!(popup instanceof L.Popup)) { - popup = new L.Popup(options).setContent(popup); + if (!(popup instanceof Popup)) { + popup = new Popup(options).setContent(popup); } if (latlng) { @@ -349,7 +358,7 @@ L.Map.include({ */ // @section Popup methods -L.Layer.include({ +Layer.include({ // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this // Binds a popup to the layer with the passed `content` and sets up the @@ -357,13 +366,13 @@ L.Layer.include({ // the layer as the first argument and should return a `String` or `HTMLElement`. bindPopup: function (content, options) { - if (content instanceof L.Popup) { - L.setOptions(content, options); + if (content instanceof Popup) { + Util.setOptions(content, options); this._popup = content; content._source = this; } else { if (!this._popup || options) { - this._popup = new L.Popup(options, this); + this._popup = new Popup(options, this); } this._popup.setContent(content); } @@ -398,12 +407,12 @@ L.Layer.include({ // @method openPopup(latlng?: LatLng): this // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed. openPopup: function (layer, latlng) { - if (!(layer instanceof L.Layer)) { + if (!(layer instanceof Layer)) { latlng = layer; layer = this; } - if (layer instanceof L.FeatureGroup) { + if (layer instanceof FeatureGroup) { for (var id in this._layers) { layer = this._layers[id]; break; @@ -483,11 +492,11 @@ L.Layer.include({ } // prevent map click - L.DomEvent.stop(e); + DomEvent.stop(e); // if this inherits from Path its a vector and we can just // open the popup at the new location - if (layer instanceof L.Path) { + if (layer instanceof Path) { this.openPopup(e.layer || e.target, e.latlng); return; } diff --git a/src/layer/Tooltip.js b/src/layer/Tooltip.js index 1b1cef0b..e148369c 100644 --- a/src/layer/Tooltip.js +++ b/src/layer/Tooltip.js @@ -1,3 +1,13 @@ + +import * as Browser from '../core/Browser'; +import {DivOverlay} from './DivOverlay'; +import {toPoint} from '../geometry/Point'; +import {Map} from '../map/Map'; +import {Layer} from './Layer'; +import {FeatureGroup} from './FeatureGroup'; +import * as Util from '../core/Util'; +import * as DomUtil from '../dom/DomUtil'; + /* * @class Tooltip * @inherits DivOverlay @@ -20,7 +30,7 @@ // @namespace Tooltip -L.Tooltip = L.DivOverlay.extend({ +export var Tooltip = DivOverlay.extend({ // @section // @aka Tooltip options @@ -58,7 +68,7 @@ L.Tooltip = L.DivOverlay.extend({ }, onAdd: function (map) { - L.DivOverlay.prototype.onAdd.call(this, map); + DivOverlay.prototype.onAdd.call(this, map); this.setOpacity(this.options.opacity); // @namespace Map @@ -77,7 +87,7 @@ L.Tooltip = L.DivOverlay.extend({ }, onRemove: function (map) { - L.DivOverlay.prototype.onRemove.call(this, map); + DivOverlay.prototype.onRemove.call(this, map); // @namespace Map // @section Tooltip events @@ -95,9 +105,9 @@ L.Tooltip = L.DivOverlay.extend({ }, getEvents: function () { - var events = L.DivOverlay.prototype.getEvents.call(this); + var events = DivOverlay.prototype.getEvents.call(this); - if (L.Browser.touch && !this.options.permanent) { + if (Browser.touch && !this.options.permanent) { events.preclick = this._close; } @@ -114,7 +124,7 @@ L.Tooltip = L.DivOverlay.extend({ var prefix = 'leaflet-tooltip', className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); - this._contentNode = this._container = L.DomUtil.create('div', className); + this._contentNode = this._container = DomUtil.create('div', className); }, _updateLayout: function () {}, @@ -129,29 +139,29 @@ L.Tooltip = L.DivOverlay.extend({ direction = this.options.direction, tooltipWidth = container.offsetWidth, tooltipHeight = container.offsetHeight, - offset = L.point(this.options.offset), + offset = toPoint(this.options.offset), anchor = this._getAnchor(); if (direction === 'top') { - pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true)); + pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true)); } else if (direction === 'bottom') { - pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true)); + pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true)); } else if (direction === 'center') { - pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true)); + pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true)); } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) { direction = 'right'; - pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true)); + pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true)); } else { direction = 'left'; - pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true)); + pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true)); } - L.DomUtil.removeClass(container, 'leaflet-tooltip-right'); - L.DomUtil.removeClass(container, 'leaflet-tooltip-left'); - L.DomUtil.removeClass(container, 'leaflet-tooltip-top'); - L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom'); - L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction); - L.DomUtil.setPosition(container, pos); + DomUtil.removeClass(container, 'leaflet-tooltip-right'); + DomUtil.removeClass(container, 'leaflet-tooltip-left'); + DomUtil.removeClass(container, 'leaflet-tooltip-top'); + DomUtil.removeClass(container, 'leaflet-tooltip-bottom'); + DomUtil.addClass(container, 'leaflet-tooltip-' + direction); + DomUtil.setPosition(container, pos); }, _updatePosition: function () { @@ -163,7 +173,7 @@ L.Tooltip = L.DivOverlay.extend({ this.options.opacity = opacity; if (this._container) { - L.DomUtil.setOpacity(this._container, opacity); + DomUtil.setOpacity(this._container, opacity); } }, @@ -174,7 +184,7 @@ L.Tooltip = L.DivOverlay.extend({ _getAnchor: function () { // Where should we anchor the tooltip on the source layer? - return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); + return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); } }); @@ -182,13 +192,13 @@ L.Tooltip = L.DivOverlay.extend({ // @namespace Tooltip // @factory L.tooltip(options?: Tooltip options, source?: Layer) // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers. -L.tooltip = function (options, source) { - return new L.Tooltip(options, source); +export var tooltip = function (options, source) { + return new Tooltip(options, source); }; // @namespace Map // @section Methods for Layers and Controls -L.Map.include({ +Map.include({ // @method openTooltip(tooltip: Tooltip): this // Opens the specified tooltip. @@ -196,8 +206,8 @@ L.Map.include({ // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this // Creates a tooltip with the specified content and options and open it. openTooltip: function (tooltip, latlng, options) { - if (!(tooltip instanceof L.Tooltip)) { - tooltip = new L.Tooltip(options).setContent(tooltip); + if (!(tooltip instanceof Tooltip)) { + tooltip = new Tooltip(options).setContent(tooltip); } if (latlng) { @@ -236,7 +246,7 @@ L.Map.include({ */ // @section Tooltip methods -L.Layer.include({ +Layer.include({ // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this // Binds a tooltip to the layer with the passed `content` and sets up the @@ -244,13 +254,13 @@ L.Layer.include({ // the layer as the first argument and should return a `String` or `HTMLElement`. bindTooltip: function (content, options) { - if (content instanceof L.Tooltip) { - L.setOptions(content, options); + if (content instanceof Tooltip) { + Util.setOptions(content, options); this._tooltip = content; content._source = this; } else { if (!this._tooltip || options) { - this._tooltip = L.tooltip(options, this); + this._tooltip = new Tooltip(options, this); } this._tooltip.setContent(content); @@ -289,7 +299,7 @@ L.Layer.include({ if (this._tooltip.options.sticky) { events.mousemove = this._moveTooltip; } - if (L.Browser.touch) { + if (Browser.touch) { events.click = this._openTooltip; } } else { @@ -302,12 +312,12 @@ L.Layer.include({ // @method openTooltip(latlng?: LatLng): this // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed. openTooltip: function (layer, latlng) { - if (!(layer instanceof L.Layer)) { + if (!(layer instanceof Layer)) { latlng = layer; layer = this; } - if (layer instanceof L.FeatureGroup) { + if (layer instanceof FeatureGroup) { for (var id in this._layers) { layer = this._layers[id]; break; @@ -332,7 +342,7 @@ L.Layer.include({ // Tooltip container may not be defined if not permanent and never // opened. if (this._tooltip.options.interactive && this._tooltip._container) { - L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable'); + DomUtil.addClass(this._tooltip._container, 'leaflet-clickable'); this.addInteractiveTarget(this._tooltip._container); } } @@ -346,7 +356,7 @@ L.Layer.include({ if (this._tooltip) { this._tooltip._close(); if (this._tooltip.options.interactive && this._tooltip._container) { - L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable'); + DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable'); this.removeInteractiveTarget(this._tooltip._container); } } diff --git a/src/layer/marker/DivIcon.js b/src/layer/marker/DivIcon.js index eb9211af..4af9c2a5 100644 --- a/src/layer/marker/DivIcon.js +++ b/src/layer/marker/DivIcon.js @@ -1,3 +1,6 @@ +import {Icon} from './Icon'; +import {toPoint as point} from '../../geometry/Point'; + /* * @class DivIcon * @aka L.DivIcon @@ -17,7 +20,7 @@ * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow. */ -L.DivIcon = L.Icon.extend({ +export var DivIcon = Icon.extend({ options: { // @section // @aka DivIcon options @@ -44,7 +47,7 @@ L.DivIcon = L.Icon.extend({ div.innerHTML = options.html !== false ? options.html : ''; if (options.bgPos) { - var bgPos = L.point(options.bgPos); + var bgPos = point(options.bgPos); div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; } this._setIconStyles(div, 'icon'); @@ -59,6 +62,6 @@ L.DivIcon = L.Icon.extend({ // @factory L.divIcon(options: DivIcon options) // Creates a `DivIcon` instance with the given options. -L.divIcon = function (options) { - return new L.DivIcon(options); -}; +export function divIcon(options) { + return new DivIcon(options); +} diff --git a/src/layer/marker/Icon.Default.js b/src/layer/marker/Icon.Default.js index 3ee3a07e..107929f6 100644 --- a/src/layer/marker/Icon.Default.js +++ b/src/layer/marker/Icon.Default.js @@ -1,3 +1,6 @@ +import {Icon} from './Icon'; +import * as DomUtil from '../../dom/DomUtil'; + /* * @miniclass Icon.Default (Icon) * @aka L.Icon.Default @@ -14,7 +17,7 @@ * `L.Marker.prototype.options.icon` with your own icon instead. */ -L.Icon.Default = L.Icon.extend({ +export var IconDefault = Icon.extend({ options: { iconUrl: 'marker-icon.png', @@ -28,21 +31,21 @@ L.Icon.Default = L.Icon.extend({ }, _getIconUrl: function (name) { - if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only - L.Icon.Default.imagePath = this._detectIconPath(); + if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only + IconDefault.imagePath = this._detectIconPath(); } // @option imagePath: String - // `L.Icon.Default` will try to auto-detect the absolute location of the + // `Icon.Default` will try to auto-detect the absolute location of the // blue icon images. If you are placing these images in a non-standard // way, set this option to point to the right absolute path. - return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name); + return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); }, _detectIconPath: function () { - var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body); - var path = L.DomUtil.getStyle(el, 'background-image') || - L.DomUtil.getStyle(el, 'backgroundImage'); // IE8 + var el = DomUtil.create('div', 'leaflet-default-icon-path', document.body); + var path = DomUtil.getStyle(el, 'background-image') || + DomUtil.getStyle(el, 'backgroundImage'); // IE8 document.body.removeChild(el); diff --git a/src/layer/marker/Icon.js b/src/layer/marker/Icon.js index 4f65fd2b..2e623c60 100644 --- a/src/layer/marker/Icon.js +++ b/src/layer/marker/Icon.js @@ -1,3 +1,8 @@ +import {Class} from '../../core/Class'; +import {setOptions} from '../../core/Util'; +import {toPoint as point} from '../../geometry/Point'; +import {retina} from '../../core/Browser'; + /* * @class Icon * @aka L.Icon @@ -27,7 +32,7 @@ * */ -L.Icon = L.Class.extend({ +export var Icon = Class.extend({ /* @section * @aka Icon options @@ -67,7 +72,7 @@ L.Icon = L.Class.extend({ */ initialize: function (options) { - L.setOptions(this, options); + setOptions(this, options); }, // @method createIcon(oldIcon?: HTMLElement): HTMLElement @@ -107,8 +112,8 @@ L.Icon = L.Class.extend({ sizeOption = [sizeOption, sizeOption]; } - var size = L.point(sizeOption), - anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || + var size = point(sizeOption), + anchor = point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true)); img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); @@ -131,13 +136,13 @@ L.Icon = L.Class.extend({ }, _getIconUrl: function (name) { - return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; + return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url']; } }); // @factory L.icon(options: Icon options) // Creates an icon instance with the given options. -L.icon = function (options) { - return new L.Icon(options); -}; +export function icon(options) { + return new Icon(options); +} diff --git a/src/layer/marker/Marker.Drag.js b/src/layer/marker/Marker.Drag.js index 495d63ef..88239cf4 100644 --- a/src/layer/marker/Marker.Drag.js +++ b/src/layer/marker/Marker.Drag.js @@ -1,3 +1,7 @@ +import {Handler} from '../../core/Handler'; +import * as DomUtil from '../../dom/DomUtil'; +import {Draggable} from '../../dom/Draggable'; + /* * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable. */ @@ -16,7 +20,7 @@ * Marker dragging handler (by both mouse and touch). */ -L.Handler.MarkerDrag = L.Handler.extend({ +export var MarkerDrag = Handler.extend({ initialize: function (marker) { this._marker = marker; }, @@ -25,7 +29,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ var icon = this._marker._icon; if (!this._draggable) { - this._draggable = new L.Draggable(icon, icon, true); + this._draggable = new Draggable(icon, icon, true); } this._draggable.on({ @@ -34,7 +38,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ dragend: this._onDragEnd }, this).enable(); - L.DomUtil.addClass(icon, 'leaflet-marker-draggable'); + DomUtil.addClass(icon, 'leaflet-marker-draggable'); }, removeHooks: function () { @@ -45,7 +49,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ }, this).disable(); if (this._marker._icon) { - L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); + DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); } }, @@ -71,12 +75,12 @@ L.Handler.MarkerDrag = L.Handler.extend({ _onDrag: function (e) { var marker = this._marker, shadow = marker._shadow, - iconPos = L.DomUtil.getPosition(marker._icon), + iconPos = DomUtil.getPosition(marker._icon), latlng = marker._map.layerPointToLatLng(iconPos); // update shadow position if (shadow) { - L.DomUtil.setPosition(shadow, iconPos); + DomUtil.setPosition(shadow, iconPos); } marker._latlng = latlng; diff --git a/src/layer/marker/Marker.js b/src/layer/marker/Marker.js index 7561e967..10fbcad3 100644 --- a/src/layer/marker/Marker.js +++ b/src/layer/marker/Marker.js @@ -1,3 +1,10 @@ +import {Layer} from '../Layer'; +import {IconDefault} from './Icon.Default'; +import * as Util from '../../core/Util'; +import {toLatLng as latLng} from '../../geo/LatLng'; +import * as DomUtil from '../../dom/DomUtil'; +import {MarkerDrag} from './Marker.Drag'; + /* * @class Marker * @inherits Interactive layer @@ -11,14 +18,14 @@ * ``` */ -L.Marker = L.Layer.extend({ +export var Marker = Layer.extend({ // @section // @aka Marker options options: { // @option icon: Icon = * // Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used. - icon: new L.Icon.Default(), + icon: new IconDefault(), // Option inherited from "Interactive layer" abstract class interactive: true, @@ -69,8 +76,8 @@ L.Marker = L.Layer.extend({ */ initialize: function (latlng, options) { - L.setOptions(this, options); - this._latlng = L.latLng(latlng); + Util.setOptions(this, options); + this._latlng = latLng(latlng); }, onAdd: function (map) { @@ -115,7 +122,7 @@ L.Marker = L.Layer.extend({ // Changes the marker position to the given point. setLatLng: function (latlng) { var oldLatLng = this._latlng; - this._latlng = L.latLng(latlng); + this._latlng = latLng(latlng); this.update(); // @event move: Event @@ -184,7 +191,7 @@ L.Marker = L.Layer.extend({ } } - L.DomUtil.addClass(icon, classToAdd); + DomUtil.addClass(icon, classToAdd); if (options.keyboard) { icon.tabIndex = '0'; @@ -208,7 +215,7 @@ L.Marker = L.Layer.extend({ } if (newShadow) { - L.DomUtil.addClass(newShadow, classToAdd); + DomUtil.addClass(newShadow, classToAdd); newShadow.alt = ''; } this._shadow = newShadow; @@ -236,7 +243,7 @@ L.Marker = L.Layer.extend({ }); } - L.DomUtil.remove(this._icon); + DomUtil.remove(this._icon); this.removeInteractiveTarget(this._icon); this._icon = null; @@ -244,16 +251,16 @@ L.Marker = L.Layer.extend({ _removeShadow: function () { if (this._shadow) { - L.DomUtil.remove(this._shadow); + DomUtil.remove(this._shadow); } this._shadow = null; }, _setPos: function (pos) { - L.DomUtil.setPosition(this._icon, pos); + DomUtil.setPosition(this._icon, pos); if (this._shadow) { - L.DomUtil.setPosition(this._shadow, pos); + DomUtil.setPosition(this._shadow, pos); } this._zIndex = pos.y + this.options.zIndexOffset; @@ -275,18 +282,18 @@ L.Marker = L.Layer.extend({ if (!this.options.interactive) { return; } - L.DomUtil.addClass(this._icon, 'leaflet-interactive'); + DomUtil.addClass(this._icon, 'leaflet-interactive'); this.addInteractiveTarget(this._icon); - if (L.Handler.MarkerDrag) { + if (MarkerDrag) { var draggable = this.options.draggable; if (this.dragging) { draggable = this.dragging.enabled(); this.dragging.disable(); } - this.dragging = new L.Handler.MarkerDrag(this); + this.dragging = new MarkerDrag(this); if (draggable) { this.dragging.enable(); @@ -308,10 +315,10 @@ L.Marker = L.Layer.extend({ _updateOpacity: function () { var opacity = this.options.opacity; - L.DomUtil.setOpacity(this._icon, opacity); + DomUtil.setOpacity(this._icon, opacity); if (this._shadow) { - L.DomUtil.setOpacity(this._shadow, opacity); + DomUtil.setOpacity(this._shadow, opacity); } }, @@ -337,6 +344,6 @@ L.Marker = L.Layer.extend({ // @factory L.marker(latlng: LatLng, options? : Marker options) // Instantiates a Marker object given a geographical point and optionally an options object. -L.marker = function (latlng, options) { - return new L.Marker(latlng, options); -}; +export function marker(latlng, options) { + return new Marker(latlng, options); +} diff --git a/src/layer/tile/GridLayer.js b/src/layer/tile/GridLayer.js index 8e285c10..ae026987 100644 --- a/src/layer/tile/GridLayer.js +++ b/src/layer/tile/GridLayer.js @@ -1,3 +1,11 @@ +import {Layer} from '../Layer'; +import * as Browser from '../../core/Browser'; +import * as Util from '../../core/Util'; +import * as DomUtil from '../../dom/DomUtil'; +import {Point} from '../../geometry/Point'; +import {Bounds} from '../../geometry/Bounds'; +import {LatLngBounds, toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds'; + /* * @class GridLayer * @inherits Layer @@ -64,7 +72,7 @@ */ -L.GridLayer = L.Layer.extend({ +export var GridLayer = Layer.extend({ // @section // @aka GridLayer options @@ -79,7 +87,7 @@ L.GridLayer = L.Layer.extend({ // @option updateWhenIdle: Boolean = depends // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`. - updateWhenIdle: L.Browser.mobile, + updateWhenIdle: Browser.mobile, // @option updateWhenZooming: Boolean = true // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends. @@ -127,7 +135,7 @@ L.GridLayer = L.Layer.extend({ }, initialize: function (options) { - L.setOptions(this, options); + Util.setOptions(this, options); }, onAdd: function () { @@ -146,7 +154,7 @@ L.GridLayer = L.Layer.extend({ onRemove: function (map) { this._removeAllTiles(); - L.DomUtil.remove(this._container); + DomUtil.remove(this._container); map._removeZoomLimit(this); this._container = null; this._tileZoom = null; @@ -156,7 +164,7 @@ L.GridLayer = L.Layer.extend({ // Brings the tile layer to the top of all tile layers. bringToFront: function () { if (this._map) { - L.DomUtil.toFront(this._container); + DomUtil.toFront(this._container); this._setAutoZIndex(Math.max); } return this; @@ -166,7 +174,7 @@ L.GridLayer = L.Layer.extend({ // Brings the tile layer to the bottom of all tile layers. bringToBack: function () { if (this._map) { - L.DomUtil.toBack(this._container); + DomUtil.toBack(this._container); this._setAutoZIndex(Math.min); } return this; @@ -222,7 +230,7 @@ L.GridLayer = L.Layer.extend({ if (!this.options.updateWhenIdle) { // update tiles on move, but not more often than once per given interval if (!this._onMove) { - this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this); + this._onMove = Util.throttle(this._onMoveEnd, this.options.updateInterval, this); } events.move = this._onMove; @@ -250,7 +258,7 @@ L.GridLayer = L.Layer.extend({ // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. getTileSize: function () { var s = this.options.tileSize; - return s instanceof L.Point ? s : new L.Point(s, s); + return s instanceof Point ? s : new Point(s, s); }, _updateZIndex: function () { @@ -284,9 +292,9 @@ L.GridLayer = L.Layer.extend({ if (!this._map) { return; } // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles - if (L.Browser.ielt9) { return; } + if (Browser.ielt9) { return; } - L.DomUtil.setOpacity(this._container, this.options.opacity); + DomUtil.setOpacity(this._container, this.options.opacity); var now = +new Date(), nextFrame = false, @@ -298,7 +306,7 @@ L.GridLayer = L.Layer.extend({ var fade = Math.min(1, (now - tile.loaded) / 200); - L.DomUtil.setOpacity(tile.el, fade); + DomUtil.setOpacity(tile.el, fade); if (fade < 1) { nextFrame = true; } else { @@ -310,15 +318,15 @@ L.GridLayer = L.Layer.extend({ if (willPrune && !this._noPrune) { this._pruneTiles(); } if (nextFrame) { - L.Util.cancelAnimFrame(this._fadeFrame); - this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); + Util.cancelAnimFrame(this._fadeFrame); + this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this); } }, _initContainer: function () { if (this._container) { return; } - this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || '')); + this._container = DomUtil.create('div', 'leaflet-layer ' + (this.options.className || '')); this._updateZIndex(); if (this.options.opacity < 1) { @@ -339,7 +347,7 @@ L.GridLayer = L.Layer.extend({ if (this._levels[z].el.children.length || z === zoom) { this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); } else { - L.DomUtil.remove(this._levels[z].el); + DomUtil.remove(this._levels[z].el); this._removeTilesAtZoom(z); delete this._levels[z]; } @@ -351,7 +359,7 @@ L.GridLayer = L.Layer.extend({ if (!level) { level = this._levels[zoom] = {}; - level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); + level.el = DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container); level.el.style.zIndex = maxZoom; level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); @@ -360,7 +368,7 @@ L.GridLayer = L.Layer.extend({ this._setZoomTransform(level, map.getCenter(), map.getZoom()); // force the browser to consider the newly added element for transition - L.Util.falseFn(level.el.offsetWidth); + Util.falseFn(level.el.offsetWidth); } this._level = level; @@ -421,7 +429,7 @@ L.GridLayer = L.Layer.extend({ _invalidateAll: function () { for (var z in this._levels) { - L.DomUtil.remove(this._levels[z].el); + DomUtil.remove(this._levels[z].el); delete this._levels[z]; } this._removeAllTiles(); @@ -433,7 +441,7 @@ L.GridLayer = L.Layer.extend({ var x2 = Math.floor(x / 2), y2 = Math.floor(y / 2), z2 = z - 1, - coords2 = new L.Point(+x2, +y2); + coords2 = new Point(+x2, +y2); coords2.z = +z2; var key = this._tileCoordsToKey(coords2), @@ -459,7 +467,7 @@ L.GridLayer = L.Layer.extend({ for (var i = 2 * x; i < 2 * x + 2; i++) { for (var j = 2 * y; j < 2 * y + 2; j++) { - var coords = new L.Point(i, j); + var coords = new Point(i, j); coords.z = z + 1; var key = this._tileCoordsToKey(coords), @@ -536,10 +544,10 @@ L.GridLayer = L.Layer.extend({ translate = level.origin.multiplyBy(scale) .subtract(this._map._getNewPixelOrigin(center, zoom)).round(); - if (L.Browser.any3d) { - L.DomUtil.setTransform(level.el, translate, scale); + if (Browser.any3d) { + DomUtil.setTransform(level.el, translate, scale); } else { - L.DomUtil.setPosition(level.el, translate); + DomUtil.setPosition(level.el, translate); } }, @@ -577,7 +585,7 @@ L.GridLayer = L.Layer.extend({ pixelCenter = map.project(center, this._tileZoom).floor(), halfSize = map.getSize().divideBy(scale * 2); - return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); + return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); }, // Private method to load tiles in the grid's active zoom level according to map bounds @@ -594,12 +602,12 @@ L.GridLayer = L.Layer.extend({ tileCenter = tileRange.getCenter(), queue = [], margin = this.options.keepBuffer, - noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), + noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]), tileRange.getTopRight().add([margin, -margin])); for (var key in this._tiles) { var c = this._tiles[key].coords; - if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) { + if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { this._tiles[key].current = false; } } @@ -611,7 +619,7 @@ L.GridLayer = L.Layer.extend({ // create a queue of coordinates to load tiles from for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { - var coords = new L.Point(i, j); + var coords = new Point(i, j); coords.z = this._tileZoom; if (!this._isValidTile(coords)) { continue; } @@ -664,7 +672,7 @@ L.GridLayer = L.Layer.extend({ // don't load tile if it doesn't intersect the bounds in options var tileBounds = this._tileCoordsToBounds(coords); - return L.latLngBounds(this.options.bounds).overlaps(tileBounds); + return latLngBounds(this.options.bounds).overlaps(tileBounds); }, _keyToBounds: function (key) { @@ -682,7 +690,7 @@ L.GridLayer = L.Layer.extend({ nw = map.unproject(nwPoint, coords.z), se = map.unproject(sePoint, coords.z), - bounds = new L.LatLngBounds(nw, se); + bounds = new LatLngBounds(nw, se); if (!this.options.noWrap) { map.wrapLatLngBounds(bounds); @@ -699,7 +707,7 @@ L.GridLayer = L.Layer.extend({ // converts tile cache key to coordinates _keyToTileCoords: function (key) { var k = key.split(':'), - coords = new L.Point(+k[0], +k[1]); + coords = new Point(+k[0], +k[1]); coords.z = +k[2]; return coords; }, @@ -708,7 +716,7 @@ L.GridLayer = L.Layer.extend({ var tile = this._tiles[key]; if (!tile) { return; } - L.DomUtil.remove(tile.el); + DomUtil.remove(tile.el); delete this._tiles[key]; @@ -721,23 +729,23 @@ L.GridLayer = L.Layer.extend({ }, _initTile: function (tile) { - L.DomUtil.addClass(tile, 'leaflet-tile'); + DomUtil.addClass(tile, 'leaflet-tile'); var tileSize = this.getTileSize(); tile.style.width = tileSize.x + 'px'; tile.style.height = tileSize.y + 'px'; - tile.onselectstart = L.Util.falseFn; - tile.onmousemove = L.Util.falseFn; + tile.onselectstart = Util.falseFn; + tile.onmousemove = Util.falseFn; // update opacity on tiles in IE7-8 because of filter inheritance problems - if (L.Browser.ielt9 && this.options.opacity < 1) { - L.DomUtil.setOpacity(tile, this.options.opacity); + if (Browser.ielt9 && this.options.opacity < 1) { + DomUtil.setOpacity(tile, this.options.opacity); } // without this hack, tiles disappear after zoom on Chrome for Android // https://github.com/Leaflet/Leaflet/issues/2078 - if (L.Browser.android && !L.Browser.android23) { + if (Browser.android && !Browser.android23) { tile.style.WebkitBackfaceVisibility = 'hidden'; } }, @@ -746,7 +754,7 @@ L.GridLayer = L.Layer.extend({ var tilePos = this._getTilePos(coords), key = this._tileCoordsToKey(coords); - var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords)); + var tile = this.createTile(this._wrapCoords(coords), Util.bind(this._tileReady, this, coords)); this._initTile(tile); @@ -754,10 +762,10 @@ L.GridLayer = L.Layer.extend({ // we know that tile is async and will be ready later; otherwise if (this.createTile.length < 2) { // mark tile as ready, but delay one frame for opacity animation to happen - L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile)); + Util.requestAnimFrame(Util.bind(this._tileReady, this, coords, null, tile)); } - L.DomUtil.setPosition(tile, tilePos); + DomUtil.setPosition(tile, tilePos); // save tile in cache this._tiles[key] = { @@ -795,16 +803,16 @@ L.GridLayer = L.Layer.extend({ tile.loaded = +new Date(); if (this._map._fadeAnimated) { - L.DomUtil.setOpacity(tile.el, 0); - L.Util.cancelAnimFrame(this._fadeFrame); - this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); + DomUtil.setOpacity(tile.el, 0); + Util.cancelAnimFrame(this._fadeFrame); + this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this); } else { tile.active = true; this._pruneTiles(); } if (!err) { - L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); + DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); // @event tileload: TileEvent // Fired when a tile loads. @@ -820,12 +828,12 @@ L.GridLayer = L.Layer.extend({ // Fired when the grid layer loaded all visible tiles. this.fire('load'); - if (L.Browser.ielt9 || !this._map._fadeAnimated) { - L.Util.requestAnimFrame(this._pruneTiles, this); + if (Browser.ielt9 || !this._map._fadeAnimated) { + Util.requestAnimFrame(this._pruneTiles, this); } else { // Wait a bit more than 0.2 secs (the duration of the tile fade-in) // to trigger a pruning. - setTimeout(L.bind(this._pruneTiles, this), 250); + setTimeout(Util.bind(this._pruneTiles, this), 250); } } }, @@ -835,16 +843,16 @@ L.GridLayer = L.Layer.extend({ }, _wrapCoords: function (coords) { - var newCoords = new L.Point( - this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x, - this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y); + var newCoords = new Point( + this._wrapX ? Util.wrapNum(coords.x, this._wrapX) : coords.x, + this._wrapY ? Util.wrapNum(coords.y, this._wrapY) : coords.y); newCoords.z = coords.z; return newCoords; }, _pxBoundsToTileRange: function (bounds) { var tileSize = this.getTileSize(); - return new L.Bounds( + return new Bounds( bounds.min.unscaleBy(tileSize).floor(), bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); }, @@ -859,6 +867,6 @@ L.GridLayer = L.Layer.extend({ // @factory L.gridLayer(options?: GridLayer options) // Creates a new instance of GridLayer with the supplied options. -L.gridLayer = function (options) { - return new L.GridLayer(options); -}; +export function gridLayer(options) { + return new GridLayer(options); +} diff --git a/src/layer/tile/TileLayer.WMS.js b/src/layer/tile/TileLayer.WMS.js index 3345c0b8..01cd6a0d 100644 --- a/src/layer/tile/TileLayer.WMS.js +++ b/src/layer/tile/TileLayer.WMS.js @@ -1,3 +1,8 @@ +import {TileLayer} from './TileLayer'; +import {extend, setOptions, getParamString} from '../../core/Util'; +import {retina} from '../../core/Browser'; +import {EPSG4326} from '../../geo/crs/CRS.EPSG4326'; + /* * @class TileLayer.WMS * @inherits TileLayer @@ -16,7 +21,7 @@ * ``` */ -L.TileLayer.WMS = L.TileLayer.extend({ +export var TileLayerWMS = TileLayer.extend({ // @section // @aka TileLayer.WMS options @@ -63,7 +68,7 @@ L.TileLayer.WMS = L.TileLayer.extend({ this._url = url; - var wmsParams = L.extend({}, this.defaultWmsParams); + var wmsParams = extend({}, this.defaultWmsParams); // all keys that are not TileLayer options go to WMS params for (var i in options) { @@ -72,9 +77,9 @@ L.TileLayer.WMS = L.TileLayer.extend({ } } - options = L.setOptions(this, options); + options = setOptions(this, options); - wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1); + wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1); this.wmsParams = wmsParams; }, @@ -87,7 +92,7 @@ L.TileLayer.WMS = L.TileLayer.extend({ var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; this.wmsParams[projectionKey] = this._crs.code; - L.TileLayer.prototype.onAdd.call(this, map); + TileLayer.prototype.onAdd.call(this, map); }, getTileUrl: function (coords) { @@ -96,14 +101,14 @@ L.TileLayer.WMS = L.TileLayer.extend({ nw = this._crs.project(tileBounds.getNorthWest()), se = this._crs.project(tileBounds.getSouthEast()), - bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? + bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? [se.y, nw.x, nw.y, se.x] : [nw.x, se.y, se.x, nw.y]).join(','), - url = L.TileLayer.prototype.getTileUrl.call(this, coords); + url = TileLayer.prototype.getTileUrl.call(this, coords); return url + - L.Util.getParamString(this.wmsParams, url, this.options.uppercase) + + getParamString(this.wmsParams, url, this.options.uppercase) + (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; }, @@ -111,7 +116,7 @@ L.TileLayer.WMS = L.TileLayer.extend({ // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). setParams: function (params, noRedraw) { - L.extend(this.wmsParams, params); + extend(this.wmsParams, params); if (!noRedraw) { this.redraw(); @@ -124,6 +129,6 @@ L.TileLayer.WMS = L.TileLayer.extend({ // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object. -L.tileLayer.wms = function (url, options) { - return new L.TileLayer.WMS(url, options); -}; +export function tileLayerWMS(url, options) { + return new TileLayerWMS(url, options); +} diff --git a/src/layer/tile/TileLayer.js b/src/layer/tile/TileLayer.js index bbe37188..438e9694 100644 --- a/src/layer/tile/TileLayer.js +++ b/src/layer/tile/TileLayer.js @@ -1,3 +1,10 @@ +import {GridLayer} from './GridLayer'; +import * as Browser from '../../core/Browser'; +import * as Util from '../../core/Util'; +import * as DomEvent from '../../dom/DomEvent'; +import * as DomUtil from '../../dom/DomUtil'; + + /* * @class TileLayer * @inherits GridLayer @@ -29,7 +36,7 @@ */ -L.TileLayer = L.GridLayer.extend({ +export var TileLayer = GridLayer.extend({ // @section // @aka TileLayer options @@ -87,10 +94,10 @@ L.TileLayer = L.GridLayer.extend({ this._url = url; - options = L.setOptions(this, options); + options = Util.setOptions(this, options); // detecting retina displays, adjusting tileSize and zoom levels - if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { + if (options.detectRetina && Browser.retina && options.maxZoom > 0) { options.tileSize = Math.floor(options.tileSize / 2); @@ -110,7 +117,7 @@ L.TileLayer = L.GridLayer.extend({ } // for https://github.com/Leaflet/Leaflet/issues/137 - if (!L.Browser.android) { + if (!Browser.android) { this.on('tileunload', this._onTileRemove); } }, @@ -133,8 +140,8 @@ L.TileLayer = L.GridLayer.extend({ createTile: function (coords, done) { var tile = document.createElement('img'); - L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile)); - L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile)); + DomEvent.on(tile, 'load', Util.bind(this._tileOnLoad, this, done, tile)); + DomEvent.on(tile, 'error', Util.bind(this._tileOnError, this, done, tile)); if (this.options.crossOrigin) { tile.crossOrigin = ''; @@ -165,7 +172,7 @@ L.TileLayer = L.GridLayer.extend({ // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. getTileUrl: function (coords) { var data = { - r: L.Browser.retina ? '@2x' : '', + r: Browser.retina ? '@2x' : '', s: this._getSubdomain(coords), x: coords.x, y: coords.y, @@ -179,13 +186,13 @@ L.TileLayer = L.GridLayer.extend({ data['-y'] = invertedY; } - return L.Util.template(this._url, L.extend(data, this.options)); + return Util.template(this._url, Util.extend(data, this.options)); }, _tileOnLoad: function (done, tile) { // For https://github.com/Leaflet/Leaflet/issues/3332 - if (L.Browser.ielt9) { - setTimeout(L.bind(done, this, null, tile), 0); + if (Browser.ielt9) { + setTimeout(Util.bind(done, this, null, tile), 0); } else { done(null, tile); } @@ -201,10 +208,10 @@ L.TileLayer = L.GridLayer.extend({ getTileSize: function () { var map = this._map, - tileSize = L.GridLayer.prototype.getTileSize.call(this), - zoom = this._tileZoom + this.options.zoomOffset, - minNativeZoom = this.options.minNativeZoom, - maxNativeZoom = this.options.maxNativeZoom; + tileSize = GridLayer.prototype.getTileSize.call(this), + zoom = this._tileZoom + this.options.zoomOffset, + minNativeZoom = this.options.minNativeZoom, + maxNativeZoom = this.options.maxNativeZoom; // decrease tile size when scaling below minNativeZoom if (minNativeZoom !== null && zoom < minNativeZoom) { @@ -260,12 +267,12 @@ L.TileLayer = L.GridLayer.extend({ if (this._tiles[i].coords.z !== this._tileZoom) { tile = this._tiles[i].el; - tile.onload = L.Util.falseFn; - tile.onerror = L.Util.falseFn; + tile.onload = Util.falseFn; + tile.onerror = Util.falseFn; if (!tile.complete) { - tile.src = L.Util.emptyImageUrl; - L.DomUtil.remove(tile); + tile.src = Util.emptyImageUrl; + DomUtil.remove(tile); } } } @@ -276,6 +283,6 @@ L.TileLayer = L.GridLayer.extend({ // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) // Instantiates a tile layer object given a `URL template` and optionally an options object. -L.tileLayer = function (url, options) { - return new L.TileLayer(url, options); -}; +export function tileLayer(url, options) { + return new TileLayer(url, options); +} diff --git a/src/layer/vector/Canvas.js b/src/layer/vector/Canvas.js index 27d3981c..5e3e1d94 100644 --- a/src/layer/vector/Canvas.js +++ b/src/layer/vector/Canvas.js @@ -1,3 +1,10 @@ +import {Renderer} from './Renderer'; +import * as DomUtil from '../../dom/DomUtil'; +import * as DomEvent from '../../dom/DomEvent'; +import * as Browser from '../../core/Browser'; +import * as Util from '../../core/Util'; +import {Bounds} from '../../geometry/Bounds'; + /* * @class Canvas * @inherits Renderer @@ -30,7 +37,7 @@ * ``` */ -L.Canvas = L.Renderer.extend({ +export var Canvas = Renderer.extend({ getEvents: function () { var events = L.Renderer.prototype.getEvents.call(this); events.viewprereset = this._onViewPreReset; @@ -43,7 +50,7 @@ L.Canvas = L.Renderer.extend({ }, onAdd: function () { - L.Renderer.prototype.onAdd.call(this); + Renderer.prototype.onAdd.call(this); // Redraw vectors since canvas is cleared upon removal, // in case of removing the renderer itself from the map. @@ -53,10 +60,9 @@ L.Canvas = L.Renderer.extend({ _initContainer: function () { var container = this._container = document.createElement('canvas'); - L.DomEvent - .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this) - .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this) - .on(container, 'mouseout', this._handleMouseOut, this); + DomEvent.on(container, 'mousemove', Util.throttle(this._onMouseMove, 32, this), this); + DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this); + DomEvent.on(container, 'mouseout', this._handleMouseOut, this); this._ctx = container.getContext('2d'); }, @@ -78,14 +84,14 @@ L.Canvas = L.Renderer.extend({ this._drawnLayers = {}; - L.Renderer.prototype._update.call(this); + Renderer.prototype._update.call(this); var b = this._bounds, container = this._container, size = b.getSize(), - m = L.Browser.retina ? 2 : 1; + m = Browser.retina ? 2 : 1; - L.DomUtil.setPosition(container, b.min); + DomUtil.setPosition(container, b.min); // set canvas size (also clearing it); use double size on retina container.width = m * size.x; @@ -93,7 +99,7 @@ L.Canvas = L.Renderer.extend({ container.style.width = size.x + 'px'; container.style.height = size.y + 'px'; - if (L.Browser.retina) { + if (Browser.retina) { this._ctx.scale(2, 2); } @@ -115,7 +121,7 @@ L.Canvas = L.Renderer.extend({ _initPath: function (layer) { this._updateDashArray(layer); - this._layers[L.stamp(layer)] = layer; + this._layers[Util.stamp(layer)] = layer; var order = layer._order = { layer: layer, @@ -186,12 +192,12 @@ L.Canvas = L.Renderer.extend({ if (!this._map) { return; } this._extendRedrawBounds(layer); - this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this); + this._redrawRequest = this._redrawRequest || Util.requestAnimFrame(this._redraw, this); }, _extendRedrawBounds: function (layer) { var padding = (layer.options.weight || 0) + 1; - this._redrawBounds = this._redrawBounds || new L.Bounds(); + this._redrawBounds = this._redrawBounds || new Bounds(); this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); }, @@ -335,7 +341,7 @@ L.Canvas = L.Renderer.extend({ } } if (clickedLayer) { - L.DomEvent._fakeStop(e); + DomEvent.fakeStop(e); this._fireEvent([clickedLayer], e); } }, @@ -352,7 +358,7 @@ L.Canvas = L.Renderer.extend({ var layer = this._hoveredLayer; if (layer) { // if we're leaving the layer, fire mouseout - L.DomUtil.removeClass(this._container, 'leaflet-interactive'); + DomUtil.removeClass(this._container, 'leaflet-interactive'); this._fireEvent([layer], e, 'mouseout'); this._hoveredLayer = null; } @@ -372,7 +378,7 @@ L.Canvas = L.Renderer.extend({ this._handleMouseOut(e); if (candidateHoveredLayer) { - L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor + DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor this._fireEvent([candidateHoveredLayer], e, 'mouseover'); this._hoveredLayer = candidateHoveredLayer; } @@ -444,64 +450,8 @@ L.Canvas = L.Renderer.extend({ } }); -// @namespace Browser; @property canvas: Boolean -// `true` when the browser supports [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). -L.Browser.canvas = (function () { - return !!document.createElement('canvas').getContext; -}()); - -// @namespace Canvas // @factory L.canvas(options?: Renderer options) // Creates a Canvas renderer with the given options. -L.canvas = function (options) { - return L.Browser.canvas ? new L.Canvas(options) : null; -}; - -L.Polyline.prototype._containsPoint = function (p, closed) { - var i, j, k, len, len2, part, - w = this._clickTolerance(); - - if (!this._pxBounds.contains(p)) { return false; } - - // hit detection for polylines - for (i = 0, len = this._parts.length; i < len; i++) { - part = this._parts[i]; - - for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { - if (!closed && (j === 0)) { continue; } - - if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) { - return true; - } - } - } - return false; -}; - -L.Polygon.prototype._containsPoint = function (p) { - var inside = false, - part, p1, p2, i, j, k, len, len2; - - if (!this._pxBounds.contains(p)) { return false; } - - // ray casting algorithm for detecting if point is in polygon - for (i = 0, len = this._parts.length; i < len; i++) { - part = this._parts[i]; - - for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { - p1 = part[j]; - p2 = part[k]; - - if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { - inside = !inside; - } - } - } - - // also check if it's on polygon stroke - return inside || L.Polyline.prototype._containsPoint.call(this, p, true); -}; - -L.CircleMarker.prototype._containsPoint = function (p) { - return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); -}; +export function canvas(options) { + return Browser.canvas ? new Canvas(options) : null; +} diff --git a/src/layer/vector/Circle.js b/src/layer/vector/Circle.js index de1e4596..23c1d26d 100644 --- a/src/layer/vector/Circle.js +++ b/src/layer/vector/Circle.js @@ -1,3 +1,11 @@ +import {CircleMarker} from './CircleMarker'; +import {Path} from './Path'; +import * as Util from '../../core/Util'; +import {toLatLng} from '../../geo/LatLng'; +import {LatLngBounds} from '../../geo/LatLngBounds'; +import {Earth} from '../../geo/crs/CRS.Earth'; + + /* * @class Circle * @aka L.Circle @@ -14,15 +22,15 @@ * ``` */ -L.Circle = L.CircleMarker.extend({ +export var Circle = CircleMarker.extend({ initialize: function (latlng, options, legacyOptions) { if (typeof options === 'number') { // Backwards compatibility with 0.7.x factory (latlng, radius, options?) - options = L.extend({}, legacyOptions, {radius: options}); + options = Util.extend({}, legacyOptions, {radius: options}); } - L.setOptions(this, options); - this._latlng = L.latLng(latlng); + Util.setOptions(this, options); + this._latlng = toLatLng(latlng); if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } @@ -50,12 +58,12 @@ L.Circle = L.CircleMarker.extend({ getBounds: function () { var half = [this._radius, this._radiusY || this._radius]; - return new L.LatLngBounds( + return new LatLngBounds( this._map.layerPointToLatLng(this._point.subtract(half)), this._map.layerPointToLatLng(this._point.add(half))); }, - setStyle: L.Path.prototype.setStyle, + setStyle: Path.prototype.setStyle, _project: function () { @@ -64,9 +72,9 @@ L.Circle = L.CircleMarker.extend({ map = this._map, crs = map.options.crs; - if (crs.distance === L.CRS.Earth.distance) { + if (crs.distance === Earth.distance) { var d = Math.PI / 180, - latR = (this._mRadius / L.CRS.Earth.R) / d, + latR = (this._mRadius / Earth.R) / d, top = map.project([lat + latR, lng]), bottom = map.project([lat - latR, lng]), p = top.add(bottom).divideBy(2), @@ -100,6 +108,6 @@ L.Circle = L.CircleMarker.extend({ // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options) // Obsolete way of instantiating a circle, for compatibility with 0.7.x code. // Do not use in new applications or plugins. -L.circle = function (latlng, options, legacyOptions) { - return new L.Circle(latlng, options, legacyOptions); -}; +export function circle(latlng, options, legacyOptions) { + return new Circle(latlng, options, legacyOptions); +} diff --git a/src/layer/vector/CircleMarker.js b/src/layer/vector/CircleMarker.js index 7690247b..994ecd05 100644 --- a/src/layer/vector/CircleMarker.js +++ b/src/layer/vector/CircleMarker.js @@ -1,3 +1,9 @@ +import {Path} from './Path'; +import * as Util from '../../core/Util'; +import {toLatLng} from '../../geo/LatLng'; +import {Bounds} from '../../geometry/Bounds'; + + /* * @class CircleMarker * @aka L.CircleMarker @@ -6,7 +12,7 @@ * A circle of a fixed size with radius specified in pixels. Extends `Path`. */ -L.CircleMarker = L.Path.extend({ +export var CircleMarker = Path.extend({ // @section // @aka CircleMarker options @@ -19,15 +25,15 @@ L.CircleMarker = L.Path.extend({ }, initialize: function (latlng, options) { - L.setOptions(this, options); - this._latlng = L.latLng(latlng); + Util.setOptions(this, options); + this._latlng = toLatLng(latlng); this._radius = this.options.radius; }, // @method setLatLng(latLng: LatLng): this // Sets the position of a circle marker to a new location. setLatLng: function (latlng) { - this._latlng = L.latLng(latlng); + this._latlng = toLatLng(latlng); this.redraw(); return this.fire('move', {latlng: this._latlng}); }, @@ -53,7 +59,7 @@ L.CircleMarker = L.Path.extend({ setStyle : function (options) { var radius = options && options.radius || this._radius; - L.Path.prototype.setStyle.call(this, options); + Path.prototype.setStyle.call(this, options); this.setRadius(radius); return this; }, @@ -68,7 +74,7 @@ L.CircleMarker = L.Path.extend({ r2 = this._radiusY || r, w = this._clickTolerance(), p = [r + w, r2 + w]; - this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p)); + this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); }, _update: function () { @@ -83,12 +89,17 @@ L.CircleMarker = L.Path.extend({ _empty: function () { return this._radius && !this._renderer._bounds.intersects(this._pxBounds); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); } }); // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options) // Instantiates a circle marker object given a geographical point, and an optional options object. -L.circleMarker = function (latlng, options) { - return new L.CircleMarker(latlng, options); -}; +export function circleMarker(latlng, options) { + return new CircleMarker(latlng, options); +} diff --git a/src/layer/vector/Path.js b/src/layer/vector/Path.js index dca7a032..33c73b5d 100644 --- a/src/layer/vector/Path.js +++ b/src/layer/vector/Path.js @@ -1,3 +1,7 @@ +import {Layer} from '../Layer'; +import * as Util from '../../core/Util'; +import {touch} from '../../core/Browser'; + /* * @class Path * @aka L.Path @@ -7,7 +11,7 @@ * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. */ -L.Path = L.Layer.extend({ +export var Path = Layer.extend({ // @section // @aka Path options @@ -94,7 +98,7 @@ L.Path = L.Layer.extend({ // @method setStyle(style: Path options): this // Changes the appearance of a Path based on the options in the `Path options` object. setStyle: function (style) { - L.setOptions(this, style); + Util.setOptions(this, style); if (this._renderer) { this._renderer._updateStyle(this); } @@ -131,6 +135,6 @@ L.Path = L.Layer.extend({ _clickTolerance: function () { // used when doing hit detection for Canvas layers - return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0); + return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0); } }); diff --git a/src/layer/vector/Polygon.js b/src/layer/vector/Polygon.js index 4448e21b..b710bf05 100644 --- a/src/layer/vector/Polygon.js +++ b/src/layer/vector/Polygon.js @@ -1,3 +1,10 @@ +import {Polyline} from './Polyline'; +import {LatLng} from '../../geo/LatLng'; +import * as LineUtil from '../../geometry/LineUtil'; +import {Point} from '../../geometry/Point'; +import {Bounds} from '../../geometry/Bounds'; +import * as PolyUtil from '../../geometry/PolyUtil'; + /* * @class Polygon * @aka L.Polygon @@ -44,7 +51,7 @@ * ``` */ -L.Polygon = L.Polyline.extend({ +export var Polygon = Polyline.extend({ options: { fill: true @@ -90,25 +97,25 @@ L.Polygon = L.Polyline.extend({ }, _convertLatLngs: function (latlngs) { - var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs), + var result = Polyline.prototype._convertLatLngs.call(this, latlngs), len = result.length; // remove last point if it equals first one - if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) { + if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { result.pop(); } return result; }, _setLatLngs: function (latlngs) { - L.Polyline.prototype._setLatLngs.call(this, latlngs); - if (L.Polyline._flat(this._latlngs)) { + Polyline.prototype._setLatLngs.call(this, latlngs); + if (LineUtil._flat(this._latlngs)) { this._latlngs = [this._latlngs]; } }, _defaultShape: function () { - return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; + return LineUtil._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; }, _clipPoints: function () { @@ -116,10 +123,10 @@ L.Polygon = L.Polyline.extend({ var bounds = this._renderer._bounds, w = this.options.weight, - p = new L.Point(w, w); + p = new Point(w, w); // increase clip padding by stroke width to avoid stroke on clip edges - bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p)); + bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); this._parts = []; if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { @@ -132,7 +139,7 @@ L.Polygon = L.Polyline.extend({ } for (var i = 0, len = this._rings.length, clipped; i < len; i++) { - clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true); + clipped = PolyUtil.clipPolygon(this._rings[i], bounds, true); if (clipped.length) { this._parts.push(clipped); } @@ -141,11 +148,37 @@ L.Polygon = L.Polyline.extend({ _updatePath: function () { this._renderer._updatePoly(this, true); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p) { + var inside = false, + part, p1, p2, i, j, k, len, len2; + + if (!this._pxBounds.contains(p)) { return false; } + + // ray casting algorithm for detecting if point is in polygon + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + p1 = part[j]; + p2 = part[k]; + + if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { + inside = !inside; + } + } + } + + // also check if it's on polygon stroke + return inside || Polyline.prototype._containsPoint.call(this, p, true); } + }); // @factory L.polygon(latlngs: LatLng[], options?: Polyline options) -L.polygon = function (latlngs, options) { - return new L.Polygon(latlngs, options); -}; +export function polygon(latlngs, options) { + return new Polygon(latlngs, options); +} diff --git a/src/layer/vector/Polyline.js b/src/layer/vector/Polyline.js index b69212d0..aebb014d 100644 --- a/src/layer/vector/Polyline.js +++ b/src/layer/vector/Polyline.js @@ -1,3 +1,11 @@ +import {Path} from './Path'; +import * as Util from '../../core/Util'; +import * as LineUtil from '../../geometry/LineUtil'; +import {LatLng, toLatLng} from '../../geo/LatLng'; +import {LatLngBounds} from '../../geo/LatLngBounds'; +import {Bounds} from '../../geometry/Bounds'; +import {Point} from '../../geometry/Point'; + /* * @class Polyline * @aka L.Polyline @@ -36,7 +44,8 @@ * ``` */ -L.Polyline = L.Path.extend({ + +export var Polyline = Path.extend({ // @section // @aka Polyline options @@ -52,7 +61,7 @@ L.Polyline = L.Path.extend({ }, initialize: function (latlngs, options) { - L.setOptions(this, options); + Util.setOptions(this, options); this._setLatLngs(latlngs); }, @@ -78,7 +87,7 @@ L.Polyline = L.Path.extend({ closestLayerPoint: function (p) { var minDistance = Infinity, minPoint = null, - closest = L.LineUtil._sqClosestPointOnSegment, + closest = LineUtil._sqClosestPointOnSegment, p1, p2; for (var j = 0, jLen = this._parts.length; j < jLen; j++) { @@ -155,29 +164,29 @@ L.Polyline = L.Path.extend({ // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). addLatLng: function (latlng, latlngs) { latlngs = latlngs || this._defaultShape(); - latlng = L.latLng(latlng); + latlng = toLatLng(latlng); latlngs.push(latlng); this._bounds.extend(latlng); return this.redraw(); }, _setLatLngs: function (latlngs) { - this._bounds = new L.LatLngBounds(); + this._bounds = new LatLngBounds(); this._latlngs = this._convertLatLngs(latlngs); }, _defaultShape: function () { - return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; + return LineUtil._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; }, // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way _convertLatLngs: function (latlngs) { var result = [], - flat = L.Polyline._flat(latlngs); + flat = LineUtil._flat(latlngs); for (var i = 0, len = latlngs.length; i < len; i++) { if (flat) { - result[i] = L.latLng(latlngs[i]); + result[i] = toLatLng(latlngs[i]); this._bounds.extend(result[i]); } else { result[i] = this._convertLatLngs(latlngs[i]); @@ -188,12 +197,12 @@ L.Polyline = L.Path.extend({ }, _project: function () { - var pxBounds = new L.Bounds(); + var pxBounds = new Bounds(); this._rings = []; this._projectLatlngs(this._latlngs, this._rings, pxBounds); var w = this._clickTolerance(), - p = new L.Point(w, w); + p = new Point(w, w); if (this._bounds.isValid() && pxBounds.isValid()) { pxBounds.min._subtract(p); @@ -204,7 +213,7 @@ L.Polyline = L.Path.extend({ // recursively turns latlngs into a set of rings with projected coordinates _projectLatlngs: function (latlngs, result, projectedBounds) { - var flat = latlngs[0] instanceof L.LatLng, + var flat = latlngs[0] instanceof LatLng, len = latlngs.length, i, ring; @@ -243,7 +252,7 @@ L.Polyline = L.Path.extend({ points = this._rings[i]; for (j = 0, len2 = points.length; j < len2 - 1; j++) { - segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true); + segment = LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true); if (!segment) { continue; } @@ -265,7 +274,7 @@ L.Polyline = L.Path.extend({ tolerance = this.options.smoothFactor; for (var i = 0, len = parts.length; i < len; i++) { - parts[i] = L.LineUtil.simplify(parts[i], tolerance); + parts[i] = LineUtil.simplify(parts[i], tolerance); } }, @@ -279,6 +288,28 @@ L.Polyline = L.Path.extend({ _updatePath: function () { this._renderer._updatePoly(this); + }, + + // Needed by the `Canvas` renderer for interactivity + _containsPoint: function (p, closed) { + var i, j, k, len, len2, part, + w = this._clickTolerance(); + + if (!this._pxBounds.contains(p)) { return false; } + + // hit detection for polylines + for (i = 0, len = this._parts.length; i < len; i++) { + part = this._parts[i]; + + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { + if (!closed && (j === 0)) { continue; } + + if (LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) { + return true; + } + } + } + return false; } }); @@ -287,11 +318,7 @@ L.Polyline = L.Path.extend({ // optionally an options object. You can create a `Polyline` object with // multiple separate lines (`MultiPolyline`) by passing an array of arrays // of geographic points. -L.polyline = function (latlngs, options) { - return new L.Polyline(latlngs, options); -}; +export function polyline(latlngs, options) { + return new Polyline(latlngs, options); +} -L.Polyline._flat = function (latlngs) { - // true if it's a flat array of latlngs; false if nested - return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined'); -}; diff --git a/src/layer/vector/Rectangle.js b/src/layer/vector/Rectangle.js index a31e6511..fcac7585 100644 --- a/src/layer/vector/Rectangle.js +++ b/src/layer/vector/Rectangle.js @@ -1,3 +1,6 @@ +import {Polygon} from './Polygon'; +import {toLatLngBounds} from '../../geo/LatLngBounds'; + /* * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. */ @@ -25,9 +28,9 @@ */ -L.Rectangle = L.Polygon.extend({ +export var Rectangle = Polygon.extend({ initialize: function (latLngBounds, options) { - L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); + Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); }, // @method setBounds(latLngBounds: LatLngBounds): this @@ -37,7 +40,7 @@ L.Rectangle = L.Polygon.extend({ }, _boundsToLatLngs: function (latLngBounds) { - latLngBounds = L.latLngBounds(latLngBounds); + latLngBounds = toLatLngBounds(latLngBounds); return [ latLngBounds.getSouthWest(), latLngBounds.getNorthWest(), @@ -49,6 +52,6 @@ L.Rectangle = L.Polygon.extend({ // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) -L.rectangle = function (latLngBounds, options) { - return new L.Rectangle(latLngBounds, options); -}; +export function rectangle(latLngBounds, options) { + return new Rectangle(latLngBounds, options); +} diff --git a/src/layer/vector/Renderer.getRenderer.js b/src/layer/vector/Renderer.getRenderer.js new file mode 100644 index 00000000..92cd615f --- /dev/null +++ b/src/layer/vector/Renderer.getRenderer.js @@ -0,0 +1,41 @@ +import {Map} from '../../map/Map'; +import {Canvas, canvas} from './Canvas'; +import {SVG, svg} from './SVG'; + +Map.include({ + // @namespace Map; @method getRenderer(layer: Path): Renderer + // Returns the instance of `Renderer` that should be used to render the given + // `Path`. It will ensure that the `renderer` options of the map and paths + // are respected, and that the renderers do exist on the map. + getRenderer: function (layer) { + // @namespace Path; @option renderer: Renderer + // Use this specific instance of `Renderer` for this path. Takes + // precedence over the map's [default renderer](#map-renderer). + var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; + + if (!renderer) { + // @namespace Map; @option preferCanvas: Boolean = false + // Whether `Path`s should be rendered on a `Canvas` renderer. + // By default, all `Path`s are rendered in a `SVG` renderer. + renderer = this._renderer = (this.options.preferCanvas && canvas()) || svg(); + } + + if (!this.hasLayer(renderer)) { + this.addLayer(renderer); + } + return renderer; + }, + + _getPaneRenderer: function (name) { + if (name === 'overlayPane' || name === undefined) { + return false; + } + + var renderer = this._paneRenderers[name]; + if (renderer === undefined) { + renderer = (SVG && svg({pane: name})) || (Canvas && canvas({pane: name})); + this._paneRenderers[name] = renderer; + } + return renderer; + } +}); diff --git a/src/layer/vector/Renderer.js b/src/layer/vector/Renderer.js index b7638de1..64e005cb 100644 --- a/src/layer/vector/Renderer.js +++ b/src/layer/vector/Renderer.js @@ -1,3 +1,11 @@ +import {Layer} from '../Layer'; +import * as DomUtil from '../../dom/DomUtil'; +import * as Util from '../../core/Util'; +import * as Browser from '../../core/Browser'; +import {Bounds} from '../../geometry/Bounds'; + + + /* * @class Renderer * @inherits Layer @@ -18,7 +26,7 @@ * its map has moved */ -L.Renderer = L.Layer.extend({ +export var Renderer = Layer.extend({ // @section // @aka Renderer options @@ -30,8 +38,8 @@ L.Renderer = L.Layer.extend({ }, initialize: function (options) { - L.setOptions(this, options); - L.stamp(this); + Util.setOptions(this, options); + Util.stamp(this); this._layers = this._layers || {}; }, @@ -40,7 +48,7 @@ L.Renderer = L.Layer.extend({ this._initContainer(); // defined by renderer implementations if (this._zoomAnimated) { - L.DomUtil.addClass(this._container, 'leaflet-zoom-animated'); + DomUtil.addClass(this._container, 'leaflet-zoom-animated'); } } @@ -50,7 +58,7 @@ L.Renderer = L.Layer.extend({ }, onRemove: function () { - L.DomUtil.remove(this._container); + DomUtil.remove(this._container); this.off('update', this._updatePaths, this); }, @@ -77,7 +85,7 @@ L.Renderer = L.Layer.extend({ _updateTransform: function (center, zoom) { var scale = this._map.getZoomScale(zoom, this._zoom), - position = L.DomUtil.getPosition(this._container), + position = DomUtil.getPosition(this._container), viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), currentCenterPoint = this._map.project(this._center, zoom), destCenterPoint = this._map.project(center, zoom), @@ -85,10 +93,10 @@ L.Renderer = L.Layer.extend({ topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset); - if (L.Browser.any3d) { - L.DomUtil.setTransform(this._container, topLeftOffset, scale); + if (Browser.any3d) { + DomUtil.setTransform(this._container, topLeftOffset, scale); } else { - L.DomUtil.setPosition(this._container, topLeftOffset); + DomUtil.setPosition(this._container, topLeftOffset); } }, @@ -120,48 +128,9 @@ L.Renderer = L.Layer.extend({ size = this._map.getSize(), min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); - this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); + this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); this._center = this._map.getCenter(); this._zoom = this._map.getZoom(); } }); - - -L.Map.include({ - // @namespace Map; @method getRenderer(layer: Path): Renderer - // Returns the instance of `Renderer` that should be used to render the given - // `Path`. It will ensure that the `renderer` options of the map and paths - // are respected, and that the renderers do exist on the map. - getRenderer: function (layer) { - // @namespace Path; @option renderer: Renderer - // Use this specific instance of `Renderer` for this path. Takes - // precedence over the map's [default renderer](#map-renderer). - var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; - - if (!renderer) { - // @namespace Map; @option preferCanvas: Boolean = false - // Whether `Path`s should be rendered on a `Canvas` renderer. - // By default, all `Path`s are rendered in a `SVG` renderer. - renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg(); - } - - if (!this.hasLayer(renderer)) { - this.addLayer(renderer); - } - return renderer; - }, - - _getPaneRenderer: function (name) { - if (name === 'overlayPane' || name === undefined) { - return false; - } - - var renderer = this._paneRenderers[name]; - if (renderer === undefined) { - renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name})); - this._paneRenderers[name] = renderer; - } - return renderer; - } -}); diff --git a/src/layer/vector/SVG.Util.js b/src/layer/vector/SVG.Util.js new file mode 100644 index 00000000..2a547972 --- /dev/null +++ b/src/layer/vector/SVG.Util.js @@ -0,0 +1,39 @@ +import * as Browser from '../../core/Browser'; + +// @namespace SVG; @section +// There are several static functions which can be called without instantiating L.SVG: + +// @function create(name: String): SVGElement +// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), +// corresponding to the class name passed. For example, using 'line' will return +// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). +export function svgCreate(name) { + return document.createElementNS('http://www.w3.org/2000/svg', name); +} + +// @function pointsToPath(rings: Point[], closed: Boolean): String +// Generates a SVG path string for multiple rings, with each ring turning +// into "M..L..L.." instructions +export function pointsToPath(rings, closed) { + var str = '', + i, j, len, len2, points, p; + + for (i = 0, len = rings.length; i < len; i++) { + points = rings[i]; + + for (j = 0, len2 = points.length; j < len2; j++) { + p = points[j]; + str += (j ? 'L' : 'M') + p.x + ' ' + p.y; + } + + // closes the ring for polygons; "x" is VML syntax + str += closed ? (Browser.svg ? 'z' : 'x') : ''; + } + + // SVG complains about empty path strings + return str || 'M0 0'; +} + + + + diff --git a/src/layer/vector/SVG.VML.js b/src/layer/vector/SVG.VML.js index 9dedba11..71559f87 100644 --- a/src/layer/vector/SVG.VML.js +++ b/src/layer/vector/SVG.VML.js @@ -1,7 +1,26 @@ +import * as DomUtil from '../../dom/DomUtil'; +import * as Util from '../../core/Util'; +import {Renderer} from './Renderer'; + /* * Thanks to Dmitry Baranovsky and his Raphael library for inspiration! */ + +export var vmlCreate = (function () { + try { + document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); + return function (name) { + return document.createElement(''); + }; + } catch (e) { + return function (name) { + return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); + }; + } +})(); + + /* * @class SVG * @@ -11,48 +30,31 @@ * with old versions of Internet Explorer. */ -// @namespace Browser; @property vml: Boolean -// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). -L.Browser.vml = !L.Browser.svg && (function () { - try { - var div = document.createElement('div'); - div.innerHTML = ''; - - var shape = div.firstChild; - shape.style.behavior = 'url(#default#VML)'; - - return shape && (typeof shape.adj === 'object'); - - } catch (e) { - return false; - } -}()); - -// redefine some SVG methods to handle VML syntax which is similar but with some differences -L.SVG.include(!L.Browser.vml ? {} : { +// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences +export var vmlMixin = { _initContainer: function () { - this._container = L.DomUtil.create('div', 'leaflet-vml-container'); + this._container = DomUtil.create('div', 'leaflet-vml-container'); }, _update: function () { if (this._map._animatingZoom) { return; } - L.Renderer.prototype._update.call(this); + Renderer.prototype._update.call(this); this.fire('update'); }, _initPath: function (layer) { - var container = layer._container = L.SVG.create('shape'); + var container = layer._container = vmlCreate('shape'); - L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); + DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || '')); container.coordsize = '1 1'; - layer._path = L.SVG.create('path'); + layer._path = vmlCreate('path'); container.appendChild(layer._path); this._updateStyle(layer); - this._layers[L.stamp(layer)] = layer; + this._layers[Util.stamp(layer)] = layer; }, _addPath: function (layer) { @@ -66,9 +68,9 @@ L.SVG.include(!L.Browser.vml ? {} : { _removePath: function (layer) { var container = layer._container; - L.DomUtil.remove(container); + DomUtil.remove(container); layer.removeInteractiveTarget(container); - delete this._layers[L.stamp(layer)]; + delete this._layers[Util.stamp(layer)]; }, _updateStyle: function (layer) { @@ -82,7 +84,7 @@ L.SVG.include(!L.Browser.vml ? {} : { if (options.stroke) { if (!stroke) { - stroke = layer._stroke = L.SVG.create('stroke'); + stroke = layer._stroke = vmlCreate('stroke'); } container.appendChild(stroke); stroke.weight = options.weight + 'px'; @@ -90,7 +92,7 @@ L.SVG.include(!L.Browser.vml ? {} : { stroke.opacity = options.opacity; if (options.dashArray) { - stroke.dashStyle = L.Util.isArray(options.dashArray) ? + stroke.dashStyle = Util.isArray(options.dashArray) ? options.dashArray.join(' ') : options.dashArray.replace(/( *, *)/g, ' '); } else { @@ -106,7 +108,7 @@ L.SVG.include(!L.Browser.vml ? {} : { if (options.fill) { if (!fill) { - fill = layer._fill = L.SVG.create('fill'); + fill = layer._fill = vmlCreate('fill'); } container.appendChild(fill); fill.color = options.fillColor || options.color; @@ -132,25 +134,10 @@ L.SVG.include(!L.Browser.vml ? {} : { }, _bringToFront: function (layer) { - L.DomUtil.toFront(layer._container); + DomUtil.toFront(layer._container); }, _bringToBack: function (layer) { - L.DomUtil.toBack(layer._container); + DomUtil.toBack(layer._container); } -}); - -if (L.Browser.vml) { - L.SVG.create = (function () { - try { - document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml'); - return function (name) { - return document.createElement(''); - }; - } catch (e) { - return function (name) { - return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); - }; - } - })(); -} +}; diff --git a/src/layer/vector/SVG.js b/src/layer/vector/SVG.js index 66be5b67..12a38be0 100644 --- a/src/layer/vector/SVG.js +++ b/src/layer/vector/SVG.js @@ -1,3 +1,12 @@ +import {Renderer} from './Renderer'; +import * as DomUtil from '../../dom/DomUtil'; +import * as Browser from '../../core/Browser'; +import {svgCreate, pointsToPath} from './SVG.Util'; +export {pointsToPath}; +import {vmlMixin, vmlCreate} from './SVG.VML'; + +export var create = Browser.vml ? vmlCreate : svgCreate; + /* * @class SVG * @inherits Renderer @@ -34,21 +43,21 @@ * ``` */ -L.SVG = L.Renderer.extend({ +export var SVG = Renderer.extend({ getEvents: function () { - var events = L.Renderer.prototype.getEvents.call(this); + var events = Renderer.prototype.getEvents.call(this); events.zoomstart = this._onZoomStart; return events; }, _initContainer: function () { - this._container = L.SVG.create('svg'); + this._container = create('svg'); // makes it possible to click through svg root; we'll reset it back in individual paths this._container.setAttribute('pointer-events', 'none'); - this._rootGroup = L.SVG.create('g'); + this._rootGroup = create('g'); this._container.appendChild(this._rootGroup); }, @@ -62,7 +71,7 @@ L.SVG = L.Renderer.extend({ _update: function () { if (this._map._animatingZoom && this._bounds) { return; } - L.Renderer.prototype._update.call(this); + Renderer.prototype._update.call(this); var b = this._bounds, size = b.getSize(), @@ -76,7 +85,7 @@ L.SVG = L.Renderer.extend({ } // movement: update container viewBox so that we don't have to change coordinates of individual layers - L.DomUtil.setPosition(container, b.min); + DomUtil.setPosition(container, b.min); container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' ')); this.fire('update'); @@ -85,17 +94,17 @@ L.SVG = L.Renderer.extend({ // methods below are called by vector layers implementations _initPath: function (layer) { - var path = layer._path = L.SVG.create('path'); + var path = layer._path = create('path'); // @namespace Path // @option className: String = null // Custom class name set on an element. Only for SVG renderer. if (layer.options.className) { - L.DomUtil.addClass(path, layer.options.className); + DomUtil.addClass(path, layer.options.className); } if (layer.options.interactive) { - L.DomUtil.addClass(path, 'leaflet-interactive'); + DomUtil.addClass(path, 'leaflet-interactive'); } this._updateStyle(layer); @@ -108,7 +117,7 @@ L.SVG = L.Renderer.extend({ }, _removePath: function (layer) { - L.DomUtil.remove(layer._path); + DomUtil.remove(layer._path); layer.removeInteractiveTarget(layer._path); delete this._layers[L.stamp(layer)]; }, @@ -156,7 +165,7 @@ L.SVG = L.Renderer.extend({ }, _updatePoly: function (layer, closed) { - this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); + this._setPath(layer, pointsToPath(layer._parts, closed)); }, _updateCircle: function (layer) { @@ -180,58 +189,20 @@ L.SVG = L.Renderer.extend({ // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements _bringToFront: function (layer) { - L.DomUtil.toFront(layer._path); + DomUtil.toFront(layer._path); }, _bringToBack: function (layer) { - L.DomUtil.toBack(layer._path); + DomUtil.toBack(layer._path); } }); +if (Browser.vml) { + SVG.include(vmlMixin); +} -// @namespace SVG; @section -// There are several static functions which can be called without instantiating L.SVG: -L.extend(L.SVG, { - // @function create(name: String): SVGElement - // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), - // corresponding to the class name passed. For example, using 'line' will return - // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). - create: function (name) { - return document.createElementNS('http://www.w3.org/2000/svg', name); - }, - - // @function pointsToPath(rings: Point[], closed: Boolean): String - // Generates a SVG path string for multiple rings, with each ring turning - // into "M..L..L.." instructions - pointsToPath: function (rings, closed) { - var str = '', - i, j, len, len2, points, p; - - for (i = 0, len = rings.length; i < len; i++) { - points = rings[i]; - - for (j = 0, len2 = points.length; j < len2; j++) { - p = points[j]; - str += (j ? 'L' : 'M') + p.x + ' ' + p.y; - } - - // closes the ring for polygons; "x" is VML syntax - str += closed ? (L.Browser.svg ? 'z' : 'x') : ''; - } - - // SVG complains about empty path strings - return str || 'M0 0'; - } -}); - -// @namespace Browser; @property svg: Boolean -// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). -L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect); - - -// @namespace SVG // @factory L.svg(options?: Renderer options) // Creates a SVG renderer with the given options. -L.svg = function (options) { - return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null; -}; +export function svg(options) { + return Browser.svg || Browser.vml ? new SVG(options) : null; +} diff --git a/src/map/Map.js b/src/map/Map.js index e1723aab..085dd1f2 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -1,3 +1,15 @@ +import * as Util from '../core/Util'; +import {Evented} from '../core/Events'; +import {EPSG3857} from '../geo/crs/CRS.EPSG3857'; +import {Point, toPoint} from '../geometry/Point'; +import {Bounds, toBounds} from '../geometry/Bounds'; +import {LatLng, toLatLng} from '../geo/LatLng'; +import {LatLngBounds, toLatLngBounds} from '../geo/LatLngBounds'; +import * as Browser from '../core/Browser'; +import * as DomEvent from '../dom/DomEvent'; +import * as DomUtil from '../dom/DomUtil'; +import {PosAnimation} from '../dom/PosAnimation'; + /* * @class Map * @aka L.Map @@ -17,14 +29,14 @@ * */ -L.Map = L.Evented.extend({ +export var Map = Evented.extend({ options: { // @section Map State Options // @option crs: CRS = L.CRS.EPSG3857 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not // sure what it means. - crs: L.CRS.EPSG3857, + crs: EPSG3857, // @option center: LatLng = undefined // Initial geographic center of the map @@ -108,13 +120,13 @@ L.Map = L.Evented.extend({ }, initialize: function (id, options) { // (HTMLElement or String, Object) - options = L.setOptions(this, options); + options = Util.setOptions(this, options); this._initContainer(id); this._initLayout(); // hack for https://github.com/Leaflet/Leaflet/issues/1980 - this._onResize = L.bind(this._onResize, this); + this._onResize = Util.bind(this._onResize, this); this._initEvents(); @@ -127,7 +139,7 @@ L.Map = L.Evented.extend({ } if (options.center && options.zoom !== undefined) { - this.setView(L.latLng(options.center), options.zoom, {reset: true}); + this.setView(toLatLng(options.center), options.zoom, {reset: true}); } this._handlers = []; @@ -138,14 +150,14 @@ L.Map = L.Evented.extend({ this.callInitHooks(); // don't animate on browsers without hardware-accelerated transitions or old Android/Opera - this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera && + this._zoomAnimated = DomUtil.TRANSITION && Browser.any3d && !Browser.mobileOpera && this.options.zoomAnimation; // zoom transitions run with the same duration for all layers, so if one of transitionend events // happens after starting zoom animation (propagating to the map pane), we know that it ended globally if (this._zoomAnimated) { this._createAnimProxy(); - L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); + DomEvent.on(this._proxy, DomUtil.TRANSITION_END, this._catchTransitionEnd, this); } this._addLayers(this.options.layers); @@ -160,7 +172,7 @@ L.Map = L.Evented.extend({ setView: function (center, zoom, options) { zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); - center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds); + center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds); options = options || {}; this._stop(); @@ -168,8 +180,8 @@ L.Map = L.Evented.extend({ if (this._loaded && !options.reset && options !== true) { if (options.animate !== undefined) { - options.zoom = L.extend({animate: options.animate}, options.zoom); - options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan); + options.zoom = Util.extend({animate: options.animate}, options.zoom); + options.pan = Util.extend({animate: options.animate, duration: options.duration}, options.pan); } // try animating pan or zoom @@ -203,14 +215,14 @@ L.Map = L.Evented.extend({ // @method zoomIn(delta?: Number, options?: Zoom options): this // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). zoomIn: function (delta, options) { - delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); + delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); return this.setZoom(this._zoom + delta, options); }, // @method zoomOut(delta?: Number, options?: Zoom options): this // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). zoomOut: function (delta, options) { - delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1); + delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); return this.setZoom(this._zoom - delta, options); }, @@ -223,7 +235,7 @@ L.Map = L.Evented.extend({ setZoomAround: function (latlng, zoom, options) { var scale = this.getZoomScale(zoom), viewHalf = this.getSize().divideBy(2), - containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), + containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale), newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); @@ -234,10 +246,10 @@ L.Map = L.Evented.extend({ _getBoundsCenterZoom: function (bounds, options) { options = options || {}; - bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); + bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); - var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]), - paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), + paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); @@ -260,7 +272,7 @@ L.Map = L.Evented.extend({ // maximum zoom level possible. fitBounds: function (bounds, options) { - bounds = L.latLngBounds(bounds); + bounds = toLatLngBounds(bounds); if (!bounds.isValid()) { throw new Error('Bounds are not valid.'); @@ -286,7 +298,7 @@ L.Map = L.Evented.extend({ // @method panBy(offset: Point): this // Pans the map by a given number of pixels (animated). panBy: function (offset, options) { - offset = L.point(offset).round(); + offset = toPoint(offset).round(); options = options || {}; if (!offset.x && !offset.y) { @@ -300,7 +312,7 @@ L.Map = L.Evented.extend({ } if (!this._panAnim) { - this._panAnim = new L.PosAnimation(); + this._panAnim = new PosAnimation(); this._panAnim.on({ 'step': this._onPanTransitionStep, @@ -315,7 +327,7 @@ L.Map = L.Evented.extend({ // animate pan unless animate: false specified if (options.animate !== false) { - L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); + DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); var newPos = this._getMapPanePos().subtract(offset).round(); this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); @@ -333,7 +345,7 @@ L.Map = L.Evented.extend({ flyTo: function (targetCenter, targetZoom, options) { options = options || {}; - if (options.animate === false || !L.Browser.any3d) { + if (options.animate === false || !Browser.any3d) { return this.setView(targetCenter, targetZoom, options); } @@ -344,7 +356,7 @@ L.Map = L.Evented.extend({ size = this.getSize(), startZoom = this._zoom; - targetCenter = L.latLng(targetCenter); + targetCenter = toLatLng(targetCenter); targetZoom = targetZoom === undefined ? startZoom : targetZoom; var w0 = Math.max(size.x, size.y), @@ -388,7 +400,7 @@ L.Map = L.Evented.extend({ s = easeOut(t) * S; if (t <= 1) { - this._flyToFrame = L.Util.requestAnimFrame(frame, this); + this._flyToFrame = Util.requestAnimFrame(frame, this); this._move( this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), @@ -419,7 +431,7 @@ L.Map = L.Evented.extend({ // @method setMaxBounds(bounds: Bounds): this // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). setMaxBounds: function (bounds) { - bounds = L.latLngBounds(bounds); + bounds = toLatLngBounds(bounds); if (!bounds.isValid()) { this.options.maxBounds = null; @@ -466,7 +478,7 @@ L.Map = L.Evented.extend({ panInsideBounds: function (bounds, options) { this._enforcingBounds = true; var center = this.getCenter(), - newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds)); + newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); if (!center.equals(newCenter)) { this.panTo(newCenter, options); @@ -492,7 +504,7 @@ L.Map = L.Evented.extend({ invalidateSize: function (options) { if (!this._loaded) { return this; } - options = L.extend({ + options = Util.extend({ animate: false, pan: true }, options === true ? {animate: true} : options); @@ -520,7 +532,7 @@ L.Map = L.Evented.extend({ if (options.debounceMoveend) { clearTimeout(this._sizeTimer); - this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); + this._sizeTimer = setTimeout(Util.bind(this.fire, this, 'moveend'), 200); } else { this.fire('moveend'); } @@ -557,7 +569,7 @@ L.Map = L.Evented.extend({ // See `Locate options` for more details. locate: function (options) { - options = this._locateOptions = L.extend({ + options = this._locateOptions = Util.extend({ timeout: 10000, watch: false // setView: false @@ -574,8 +586,8 @@ L.Map = L.Evented.extend({ return this; } - var onResponse = L.bind(this._handleGeolocationResponse, this), - onError = L.bind(this._handleGeolocationError, this); + var onResponse = Util.bind(this._handleGeolocationResponse, this), + onError = Util.bind(this._handleGeolocationError, this); if (options.watch) { this._locationWatchId = @@ -622,7 +634,7 @@ L.Map = L.Evented.extend({ _handleGeolocationResponse: function (pos) { var lat = pos.coords.latitude, lng = pos.coords.longitude, - latlng = new L.LatLng(lat, lng), + latlng = new LatLng(lat, lng), bounds = latlng.toBounds(pos.coords.accuracy), options = this._locateOptions; @@ -689,7 +701,7 @@ L.Map = L.Evented.extend({ this._containerId = undefined; } - L.DomUtil.remove(this._mapPane); + DomUtil.remove(this._mapPane); if (this._clearControlPos) { this._clearControlPos(); @@ -718,7 +730,7 @@ L.Map = L.Evented.extend({ // as a children of the main map pane if not set. createPane: function (name, container) { var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), - pane = L.DomUtil.create('div', className, container || this._mapPane); + pane = DomUtil.create('div', className, container || this._mapPane); if (name) { this._panes[name] = pane; @@ -752,7 +764,7 @@ L.Map = L.Evented.extend({ sw = this.unproject(bounds.getBottomLeft()), ne = this.unproject(bounds.getTopRight()); - return new L.LatLngBounds(sw, ne); + return new LatLngBounds(sw, ne); }, // @method getMinZoom(): Number @@ -775,8 +787,8 @@ L.Map = L.Evented.extend({ // instead returns the minimum zoom level on which the map view fits into // the given bounds in its entirety. getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number - bounds = L.latLngBounds(bounds); - padding = L.point(padding || [0, 0]); + bounds = toLatLngBounds(bounds); + padding = toPoint(padding || [0, 0]); var zoom = this.getZoom() || 0, min = this.getMinZoom(), @@ -784,8 +796,8 @@ L.Map = L.Evented.extend({ nw = bounds.getNorthWest(), se = bounds.getSouthEast(), size = this.getSize().subtract(padding), - boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), - snap = L.Browser.any3d ? this.options.zoomSnap : 1; + boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), + snap = Browser.any3d ? this.options.zoomSnap : 1; var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y); zoom = this.getScaleZoom(scale, zoom); @@ -802,7 +814,7 @@ L.Map = L.Evented.extend({ // Returns the current size of the map container (in pixels). getSize: function () { if (!this._size || this._sizeChanged) { - this._size = new L.Point( + this._size = new Point( this._container.clientWidth || 0, this._container.clientHeight || 0); @@ -816,7 +828,7 @@ L.Map = L.Evented.extend({ // coordinates (sometimes useful in layer and overlay implementations). getPixelBounds: function (center, zoom) { var topLeftPoint = this._getTopLeftPoint(center, zoom); - return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); + return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to @@ -889,21 +901,21 @@ L.Map = L.Evented.extend({ // the CRS origin. project: function (latlng, zoom) { zoom = zoom === undefined ? this._zoom : zoom; - return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); + return this.options.crs.latLngToPoint(toLatLng(latlng), zoom); }, // @method unproject(point: Point, zoom: Number): LatLng // Inverse of [`project`](#map-project). unproject: function (point, zoom) { zoom = zoom === undefined ? this._zoom : zoom; - return this.options.crs.pointToLatLng(L.point(point), zoom); + return this.options.crs.pointToLatLng(toPoint(point), zoom); }, // @method layerPointToLatLng(point: Point): LatLng // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), // returns the corresponding geographical coordinate (for the current zoom level). layerPointToLatLng: function (point) { - var projectedPoint = L.point(point).add(this.getPixelOrigin()); + var projectedPoint = toPoint(point).add(this.getPixelOrigin()); return this.unproject(projectedPoint); }, @@ -911,7 +923,7 @@ L.Map = L.Evented.extend({ // Given a geographical coordinate, returns the corresponding pixel coordinate // relative to the [origin pixel](#map-getpixelorigin). latLngToLayerPoint: function (latlng) { - var projectedPoint = this.project(L.latLng(latlng))._round(); + var projectedPoint = this.project(toLatLng(latlng))._round(); return projectedPoint._subtract(this.getPixelOrigin()); }, @@ -922,7 +934,7 @@ L.Map = L.Evented.extend({ // By default this means longitude is wrapped around the dateline so its // value is between -180 and +180 degrees. wrapLatLng: function (latlng) { - return this.options.crs.wrapLatLng(L.latLng(latlng)); + return this.options.crs.wrapLatLng(toLatLng(latlng)); }, // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds @@ -939,28 +951,28 @@ L.Map = L.Evented.extend({ // Returns the distance between two geographical coordinates according to // the map's CRS. By default this measures distance in meters. distance: function (latlng1, latlng2) { - return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2)); + return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); }, // @method containerPointToLayerPoint(point: Point): Point // Given a pixel coordinate relative to the map container, returns the corresponding // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). containerPointToLayerPoint: function (point) { // (Point) - return L.point(point).subtract(this._getMapPanePos()); + return toPoint(point).subtract(this._getMapPanePos()); }, // @method layerPointToContainerPoint(point: Point): Point // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), // returns the corresponding pixel coordinate relative to the map container. layerPointToContainerPoint: function (point) { // (Point) - return L.point(point).add(this._getMapPanePos()); + return toPoint(point).add(this._getMapPanePos()); }, // @method containerPointToLatLng(point: Point): LatLng // Given a pixel coordinate relative to the map container, returns // the corresponding geographical coordinate (for the current zoom level). containerPointToLatLng: function (point) { - var layerPoint = this.containerPointToLayerPoint(L.point(point)); + var layerPoint = this.containerPointToLayerPoint(toPoint(point)); return this.layerPointToLatLng(layerPoint); }, @@ -968,14 +980,14 @@ L.Map = L.Evented.extend({ // Given a geographical coordinate, returns the corresponding pixel coordinate // relative to the map container. latLngToContainerPoint: function (latlng) { - return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); + return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); }, // @method mouseEventToContainerPoint(ev: MouseEvent): Point // Given a MouseEvent object, returns the pixel coordinate relative to the // map container where the event took place. mouseEventToContainerPoint: function (e) { - return L.DomEvent.getMousePosition(e, this._container); + return DomEvent.getMousePosition(e, this._container); }, // @method mouseEventToLayerPoint(ev: MouseEvent): Point @@ -996,7 +1008,7 @@ L.Map = L.Evented.extend({ // map initialization methods _initContainer: function (id) { - var container = this._container = L.DomUtil.get(id); + var container = this._container = DomUtil.get(id); if (!container) { throw new Error('Map container not found.'); @@ -1004,23 +1016,23 @@ L.Map = L.Evented.extend({ throw new Error('Map container is already initialized.'); } - L.DomEvent.addListener(container, 'scroll', this._onScroll, this); - this._containerId = L.Util.stamp(container); + DomEvent.on(container, 'scroll', this._onScroll, this); + this._containerId = Util.stamp(container); }, _initLayout: function () { var container = this._container; - this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d; + this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; - L.DomUtil.addClass(container, 'leaflet-container' + - (L.Browser.touch ? ' leaflet-touch' : '') + - (L.Browser.retina ? ' leaflet-retina' : '') + - (L.Browser.ielt9 ? ' leaflet-oldie' : '') + - (L.Browser.safari ? ' leaflet-safari' : '') + + DomUtil.addClass(container, 'leaflet-container' + + (Browser.touch ? ' leaflet-touch' : '') + + (Browser.retina ? ' leaflet-retina' : '') + + (Browser.ielt9 ? ' leaflet-oldie' : '') + + (Browser.safari ? ' leaflet-safari' : '') + (this._fadeAnimated ? ' leaflet-fade-anim' : '')); - var position = L.DomUtil.getStyle(container, 'position'); + var position = DomUtil.getStyle(container, 'position'); if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') { container.style.position = 'relative'; @@ -1050,7 +1062,7 @@ L.Map = L.Evented.extend({ // Pane that contains all other map panes this._mapPane = this.createPane('mapPane', this._container); - L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + DomUtil.setPosition(this._mapPane, new Point(0, 0)); // @pane tilePane: HTMLElement = 200 // Pane for `GridLayer`s and `TileLayer`s @@ -1072,8 +1084,8 @@ L.Map = L.Evented.extend({ this.createPane('popupPane'); if (!this.options.markerZoomAnimation) { - L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); - L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); + DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); + DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); } }, @@ -1082,7 +1094,7 @@ L.Map = L.Evented.extend({ // @section Map state change events _resetView: function (center, zoom) { - L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + DomUtil.setPosition(this._mapPane, new Point(0, 0)); var loading = !this._loaded; this._loaded = true; @@ -1157,7 +1169,7 @@ L.Map = L.Evented.extend({ }, _stop: function () { - L.Util.cancelAnimFrame(this._flyToFrame); + Util.cancelAnimFrame(this._flyToFrame); if (this._panAnim) { this._panAnim.stop(); } @@ -1165,7 +1177,7 @@ L.Map = L.Evented.extend({ }, _rawPanBy: function (offset) { - L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); + DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); }, _getZoomSpan: function () { @@ -1188,12 +1200,10 @@ L.Map = L.Evented.extend({ // @section Interaction events _initEvents: function (remove) { - if (!L.DomEvent) { return; } - this._targets = {}; - this._targets[L.stamp(this._container)] = this; + this._targets[Util.stamp(this._container)] = this; - var onOff = remove ? 'off' : 'on'; + var onOff = remove ? DomEvent.off : DomEvent.on; // @event click: MouseEvent // Fired when the user clicks (or taps) the map. @@ -1216,21 +1226,21 @@ L.Map = L.Evented.extend({ // for a second (also called long press). // @event keypress: KeyboardEvent // Fired when the user presses a key from the keyboard while the map is focused. - L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' + + onOff(this._container, 'click dblclick mousedown mouseup ' + 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); if (this.options.trackResize) { - L.DomEvent[onOff](window, 'resize', this._onResize, this); + onOff(window, 'resize', this._onResize, this); } - if (L.Browser.any3d && this.options.transform3DLimit) { - this[onOff]('moveend', this._onMoveEnd); + if (Browser.any3d && this.options.transform3DLimit) { + (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd); } }, _onResize: function () { - L.Util.cancelAnimFrame(this._resizeRequest); - this._resizeRequest = L.Util.requestAnimFrame( + Util.cancelAnimFrame(this._resizeRequest); + this._resizeRequest = Util.requestAnimFrame( function () { this.invalidateSize({debounceMoveend: true}); }, this); }, @@ -1256,34 +1266,34 @@ L.Map = L.Evented.extend({ dragging = false; while (src) { - target = this._targets[L.stamp(src)]; + target = this._targets[Util.stamp(src)]; if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) { // Prevent firing click after you just dragged an object. dragging = true; break; } if (target && target.listens(type, true)) { - if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; } + if (isHover && !DomEvent.isExternalTarget(src, e)) { break; } targets.push(target); if (isHover) { break; } } if (src === this._container) { break; } src = src.parentNode; } - if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) { + if (!targets.length && !dragging && !isHover && DomEvent.isExternalTarget(src, e)) { targets = [this]; } return targets; }, _handleDOMEvent: function (e) { - if (!this._loaded || L.DomEvent._skipped(e)) { return; } + if (!this._loaded || DomEvent.skipped(e)) { return; } var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type; if (type === 'mousedown') { // prevents outline when clicking on keyboard-focusable element - L.DomUtil.preventOutline(e.target || e.srcElement); + DomUtil.preventOutline(e.target || e.srcElement); } this._fireDOMEvent(e, type); @@ -1297,7 +1307,7 @@ L.Map = L.Evented.extend({ // Fired before mouse click on the map (sometimes useful when you // want something to happen on click before any existing click // handlers start running). - var synth = L.Util.extend({}, e); + var synth = Util.extend({}, e); synth.type = 'preclick'; this._fireDOMEvent(synth, synth.type, targets); } @@ -1311,7 +1321,7 @@ L.Map = L.Evented.extend({ var target = targets[0]; if (type === 'contextmenu' && target.listens(type, true)) { - L.DomEvent.preventDefault(e); + DomEvent.preventDefault(e); } var data = { @@ -1319,7 +1329,7 @@ L.Map = L.Evented.extend({ }; if (e.type !== 'keypress') { - var isMarker = target instanceof L.Marker; + var isMarker = (target.options && 'icon' in target.options); data.containerPoint = isMarker ? this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); @@ -1329,7 +1339,7 @@ L.Map = L.Evented.extend({ for (var i = 0; i < targets.length; i++) { targets[i].fire(type, data, true); if (data.originalEvent._stopped || - (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; } + (targets[i].options.nonBubblingEvents && Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; } } }, @@ -1363,7 +1373,7 @@ L.Map = L.Evented.extend({ // private methods for getting map state _getMapPanePos: function () { - return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); + return DomUtil.getPosition(this._mapPane) || new Point(0, 0); }, _moved: function () { @@ -1390,7 +1400,7 @@ L.Map = L.Evented.extend({ _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { var topLeft = this._getNewPixelOrigin(center, zoom); - return L.bounds([ + return toBounds([ this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft), @@ -1415,7 +1425,7 @@ L.Map = L.Evented.extend({ var centerPoint = this.project(center, zoom), viewHalf = this.getSize().divideBy(2), - viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), + viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), offset = this._getBoundsOffset(viewBounds, bounds, zoom); // If offset is less than a pixel, ignore. @@ -1433,14 +1443,14 @@ L.Map = L.Evented.extend({ if (!bounds) { return offset; } var viewBounds = this.getPixelBounds(), - newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); + newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); return offset.add(this._getBoundsOffset(newBounds, bounds)); }, // returns offset needed for pxBounds to get inside maxBounds at a specified zoom _getBoundsOffset: function (pxBounds, maxBounds, zoom) { - var projectedMaxBounds = L.bounds( + var projectedMaxBounds = toBounds( this.project(maxBounds.getNorthEast(), zoom), this.project(maxBounds.getSouthWest(), zoom) ), @@ -1450,7 +1460,7 @@ L.Map = L.Evented.extend({ dx = this._rebound(minOffset.x, -maxOffset.x), dy = this._rebound(minOffset.y, -maxOffset.y); - return new L.Point(dx, dy); + return new Point(dx, dy); }, _rebound: function (left, right) { @@ -1462,7 +1472,7 @@ L.Map = L.Evented.extend({ _limitZoom: function (zoom) { var min = this.getMinZoom(), max = this.getMaxZoom(), - snap = L.Browser.any3d ? this.options.zoomSnap : 1; + snap = Browser.any3d ? this.options.zoomSnap : 1; if (snap) { zoom = Math.round(zoom / snap) * snap; } @@ -1474,7 +1484,7 @@ L.Map = L.Evented.extend({ }, _onPanTransitionEnd: function () { - L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); + DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); this.fire('moveend'); }, @@ -1492,14 +1502,14 @@ L.Map = L.Evented.extend({ _createAnimProxy: function () { - var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated'); + var proxy = this._proxy = DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated'); this._panes.mapPane.appendChild(proxy); this.on('zoomanim', function (e) { - var prop = L.DomUtil.TRANSFORM, + var prop = DomUtil.TRANSFORM, transform = proxy.style[prop]; - L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); + DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); // workaround for case when transform is the same and so transitionend event is not fired if (transform === proxy.style[prop] && this._animatingZoom) { @@ -1510,7 +1520,7 @@ L.Map = L.Evented.extend({ this.on('load moveend', function () { var c = this.getCenter(), z = this.getZoom(); - L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); + DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); }, this); }, @@ -1541,7 +1551,7 @@ L.Map = L.Evented.extend({ // don't animate if the zoom origin isn't within one screen from the current center, unless forced if (options.animate !== true && !this.getSize().contains(offset)) { return false; } - L.Util.requestAnimFrame(function () { + Util.requestAnimFrame(function () { this ._moveStart(true) ._animateZoom(center, zoom, true); @@ -1558,7 +1568,7 @@ L.Map = L.Evented.extend({ this._animateToCenter = center; this._animateToZoom = zoom; - L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); + DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); } // @event zoomanim: ZoomAnimEvent @@ -1570,20 +1580,20 @@ L.Map = L.Evented.extend({ }); // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 - setTimeout(L.bind(this._onZoomTransitionEnd, this), 250); + setTimeout(Util.bind(this._onZoomTransitionEnd, this), 250); }, _onZoomTransitionEnd: function () { if (!this._animatingZoom) { return; } - L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); + DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); this._animatingZoom = false; this._move(this._animateToCenter, this._animateToZoom); // This anim frame should prevent an obscure iOS webkit tile loading race condition. - L.Util.requestAnimFrame(function () { + Util.requestAnimFrame(function () { this._moveEnd(true); }, this); } @@ -1599,6 +1609,6 @@ L.Map = L.Evented.extend({ // @factory L.map(el: HTMLElement, options?: Map options) // Instantiates a map object given an instance of a `
` HTML element // and optionally an object literal with `Map options`. -L.map = function (id, options) { - return new L.Map(id, options); -}; +export function createMap(id, options) { + return new Map(id, options); +} diff --git a/src/map/handler/Map.BoxZoom.js b/src/map/handler/Map.BoxZoom.js index a942919e..b24c3b5e 100644 --- a/src/map/handler/Map.BoxZoom.js +++ b/src/map/handler/Map.BoxZoom.js @@ -1,3 +1,11 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; +import * as Util from '../../core/Util'; +import * as DomUtil from '../../dom/DomUtil'; +import * as DomEvent from '../../dom/DomEvent'; +import {LatLngBounds} from '../../geo/LatLngBounds'; +import {Bounds} from '../../geometry/Bounds'; + /* * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map * (zoom to a selected bounding box), enabled by default. @@ -5,14 +13,14 @@ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @option boxZoom: Boolean = true // Whether the map can be zoomed to a rectangular area specified by // dragging the mouse while pressing the shift key. boxZoom: true }); -L.Map.BoxZoom = L.Handler.extend({ +export var BoxZoom = Handler.extend({ initialize: function (map) { this._map = map; this._container = map._container; @@ -20,11 +28,11 @@ L.Map.BoxZoom = L.Handler.extend({ }, addHooks: function () { - L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); + DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); }, removeHooks: function () { - L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); + DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); }, moved: function () { @@ -40,13 +48,13 @@ L.Map.BoxZoom = L.Handler.extend({ this._resetState(); - L.DomUtil.disableTextSelection(); - L.DomUtil.disableImageDrag(); + DomUtil.disableTextSelection(); + DomUtil.disableImageDrag(); this._startPoint = this._map.mouseEventToContainerPoint(e); - L.DomEvent.on(document, { - contextmenu: L.DomEvent.stop, + DomEvent.on(document, { + contextmenu: DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown @@ -57,18 +65,18 @@ L.Map.BoxZoom = L.Handler.extend({ if (!this._moved) { this._moved = true; - this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container); - L.DomUtil.addClass(this._container, 'leaflet-crosshair'); + this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container); + DomUtil.addClass(this._container, 'leaflet-crosshair'); this._map.fire('boxzoomstart'); } this._point = this._map.mouseEventToContainerPoint(e); - var bounds = new L.Bounds(this._point, this._startPoint), + var bounds = new Bounds(this._point, this._startPoint), size = bounds.getSize(); - L.DomUtil.setPosition(this._box, bounds.min); + DomUtil.setPosition(this._box, bounds.min); this._box.style.width = size.x + 'px'; this._box.style.height = size.y + 'px'; @@ -76,15 +84,15 @@ L.Map.BoxZoom = L.Handler.extend({ _finish: function () { if (this._moved) { - L.DomUtil.remove(this._box); - L.DomUtil.removeClass(this._container, 'leaflet-crosshair'); + DomUtil.remove(this._box); + DomUtil.removeClass(this._container, 'leaflet-crosshair'); } - L.DomUtil.enableTextSelection(); - L.DomUtil.enableImageDrag(); + DomUtil.enableTextSelection(); + DomUtil.enableImageDrag(); - L.DomEvent.off(document, { - contextmenu: L.DomEvent.stop, + DomEvent.off(document, { + contextmenu: DomEvent.stop, mousemove: this._onMouseMove, mouseup: this._onMouseUp, keydown: this._onKeyDown @@ -99,9 +107,9 @@ L.Map.BoxZoom = L.Handler.extend({ if (!this._moved) { return; } // Postpone to next JS tick so internal click event handling // still see it as "moved". - setTimeout(L.bind(this._resetState, this), 0); + setTimeout(Util.bind(this._resetState, this), 0); - var bounds = new L.LatLngBounds( + var bounds = new LatLngBounds( this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); @@ -120,4 +128,4 @@ L.Map.BoxZoom = L.Handler.extend({ // @section Handlers // @property boxZoom: Handler // Box (shift-drag with mouse) zoom handler. -L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); +Map.addInitHook('addHandler', 'boxZoom', BoxZoom); diff --git a/src/map/handler/Map.DoubleClickZoom.js b/src/map/handler/Map.DoubleClickZoom.js index e4227782..c105e8e0 100644 --- a/src/map/handler/Map.DoubleClickZoom.js +++ b/src/map/handler/Map.DoubleClickZoom.js @@ -1,3 +1,6 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; + /* * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. */ @@ -5,7 +8,7 @@ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @option doubleClickZoom: Boolean|String = true // Whether the map can be zoomed in by double clicking on it and // zoomed out by double clicking while holding shift. If passed @@ -14,7 +17,7 @@ L.Map.mergeOptions({ doubleClickZoom: true }); -L.Map.DoubleClickZoom = L.Handler.extend({ +export var DoubleClickZoom = Handler.extend({ addHooks: function () { this._map.on('dblclick', this._onDoubleClick, this); }, @@ -49,4 +52,4 @@ L.Map.DoubleClickZoom = L.Handler.extend({ // // @property doubleClickZoom: Handler // Double click zoom handler. -L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); +Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom); diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index 9ee42099..fb839754 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -1,10 +1,19 @@ +import {Map} from '../Map'; +import * as Browser from '../../core/Browser'; +import {Handler} from '../../core/Handler'; +import {Draggable} from '../../dom/Draggable'; +import * as Util from '../../core/Util'; +import * as DomUtil from '../../dom/DomUtil'; +import {toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds'; +import {toBounds} from '../../geometry/Bounds'; + /* * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. */ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @option dragging: Boolean = true // Whether the map be draggable with mouse/touch or not. dragging: true, @@ -15,7 +24,7 @@ L.Map.mergeOptions({ // the map builds momentum while dragging and continues moving in // the same direction for some time. Feels especially nice on touch // devices. Enabled by default unless running on old Android devices. - inertia: !L.Browser.android23, + inertia: !Browser.android23, // @option inertiaDeceleration: Number = 3000 // The rate with which the inertial movement slows down, in pixels/second². @@ -44,12 +53,12 @@ L.Map.mergeOptions({ maxBoundsViscosity: 0.0 }); -L.Map.Drag = L.Handler.extend({ +export var Drag = Handler.extend({ addHooks: function () { if (!this._draggable) { var map = this._map; - this._draggable = new L.Draggable(map._mapPane, map._container); + this._draggable = new Draggable(map._mapPane, map._container); this._draggable.on({ down: this._onDown, @@ -66,15 +75,15 @@ L.Map.Drag = L.Handler.extend({ map.whenReady(this._onZoomEnd, this); } } - L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); + DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag'); this._draggable.enable(); this._positions = []; this._times = []; }, removeHooks: function () { - L.DomUtil.removeClass(this._map._container, 'leaflet-grab'); - L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag'); + DomUtil.removeClass(this._map._container, 'leaflet-grab'); + DomUtil.removeClass(this._map._container, 'leaflet-touch-drag'); this._draggable.disable(); }, @@ -94,9 +103,9 @@ L.Map.Drag = L.Handler.extend({ var map = this._map; if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { - var bounds = L.latLngBounds(this._map.options.maxBounds); + var bounds = latLngBounds(this._map.options.maxBounds); - this._offsetLimit = L.bounds( + this._offsetLimit = toBounds( this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) .add(this._map.getSize())); @@ -207,7 +216,7 @@ L.Map.Drag = L.Handler.extend({ } else { offset = map._limitOffset(offset, map.options.maxBounds); - L.Util.requestAnimFrame(function () { + Util.requestAnimFrame(function () { map.panBy(offset, { duration: decelerationDuration, easeLinearity: ease, @@ -223,4 +232,4 @@ L.Map.Drag = L.Handler.extend({ // @section Handlers // @property dragging: Handler // Map dragging handler (by both mouse and touch). -L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); +Map.addInitHook('addHandler', 'dragging', Drag); diff --git a/src/map/handler/Map.Keyboard.js b/src/map/handler/Map.Keyboard.js index 98cb367e..a0fd68d3 100644 --- a/src/map/handler/Map.Keyboard.js +++ b/src/map/handler/Map.Keyboard.js @@ -1,10 +1,16 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; +import {on, off, stop} from '../../dom/DomEvent'; +import {toPoint} from '../../geometry/Point'; + + /* * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. */ // @namespace Map // @section Keyboard Navigation Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @option keyboard: Boolean = true // Makes the map focusable and allows users to navigate the map with keyboard // arrows and `+`/`-` keys. @@ -15,7 +21,7 @@ L.Map.mergeOptions({ keyboardPanDelta: 80 }); -L.Map.Keyboard = L.Handler.extend({ +export var Keyboard = Handler.extend({ keyCodes: { left: [37], @@ -41,7 +47,7 @@ L.Map.Keyboard = L.Handler.extend({ container.tabIndex = '0'; } - L.DomEvent.on(container, { + on(container, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown @@ -56,7 +62,7 @@ L.Map.Keyboard = L.Handler.extend({ removeHooks: function () { this._removeHooks(); - L.DomEvent.off(this._map._container, { + off(this._map._container, { focus: this._onFocus, blur: this._onBlur, mousedown: this._onMouseDown @@ -124,11 +130,11 @@ L.Map.Keyboard = L.Handler.extend({ }, _addHooks: function () { - L.DomEvent.on(document, 'keydown', this._onKeyDown, this); + on(document, 'keydown', this._onKeyDown, this); }, _removeHooks: function () { - L.DomEvent.off(document, 'keydown', this._onKeyDown, this); + off(document, 'keydown', this._onKeyDown, this); }, _onKeyDown: function (e) { @@ -144,7 +150,7 @@ L.Map.Keyboard = L.Handler.extend({ offset = this._panKeys[key]; if (e.shiftKey) { - offset = L.point(offset).multiplyBy(3); + offset = toPoint(offset).multiplyBy(3); } map.panBy(offset); @@ -163,7 +169,7 @@ L.Map.Keyboard = L.Handler.extend({ return; } - L.DomEvent.stop(e); + stop(e); } }); @@ -171,4 +177,4 @@ L.Map.Keyboard = L.Handler.extend({ // @section Handlers // @property keyboard: Handler // Keyboard navigation handler. -L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); +Map.addInitHook('addHandler', 'keyboard', Keyboard); diff --git a/src/map/handler/Map.ScrollWheelZoom.js b/src/map/handler/Map.ScrollWheelZoom.js index cb4e9d89..d61d33d9 100644 --- a/src/map/handler/Map.ScrollWheelZoom.js +++ b/src/map/handler/Map.ScrollWheelZoom.js @@ -1,10 +1,15 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; +import * as DomEvent from '../../dom/DomEvent'; +import * as Util from '../../core/Util'; + /* * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. */ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @section Mousewheel options // @option scrollWheelZoom: Boolean|String = true // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, @@ -23,19 +28,19 @@ L.Map.mergeOptions({ wheelPxPerZoomLevel: 60 }); -L.Map.ScrollWheelZoom = L.Handler.extend({ +export var ScrollWheelZoom = Handler.extend({ addHooks: function () { - L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); + DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); this._delta = 0; }, removeHooks: function () { - L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this); + DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this); }, _onWheelScroll: function (e) { - var delta = L.DomEvent.getWheelDelta(e); + var delta = DomEvent.getWheelDelta(e); var debounce = this._map.options.wheelDebounceTime; @@ -49,9 +54,9 @@ L.Map.ScrollWheelZoom = L.Handler.extend({ var left = Math.max(debounce - (+new Date() - this._startTime), 0); clearTimeout(this._timer); - this._timer = setTimeout(L.bind(this._performZoom, this), left); + this._timer = setTimeout(Util.bind(this._performZoom, this), left); - L.DomEvent.stop(e); + DomEvent.stop(e); }, _performZoom: function () { @@ -83,4 +88,4 @@ L.Map.ScrollWheelZoom = L.Handler.extend({ // @section Handlers // @property scrollWheelZoom: Handler // Scroll wheel zoom handler. -L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); +Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom); diff --git a/src/map/handler/Map.Tap.js b/src/map/handler/Map.Tap.js index 9e1b1941..9162637e 100644 --- a/src/map/handler/Map.Tap.js +++ b/src/map/handler/Map.Tap.js @@ -1,10 +1,19 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; +import * as DomEvent from '../../dom/DomEvent'; +import {Point} from '../../geometry/Point'; +import * as Util from '../../core/Util'; +import * as DomUtil from '../../dom/DomUtil'; +import * as Browser from '../../core/Browser'; + + /* * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. */ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @section Touch interaction options // @option tap: Boolean = true // Enables mobile hacks for supporting instant taps (fixing 200ms click @@ -17,19 +26,19 @@ L.Map.mergeOptions({ tapTolerance: 15 }); -L.Map.Tap = L.Handler.extend({ +export var Tap = Handler.extend({ addHooks: function () { - L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); + DomEvent.on(this._map._container, 'touchstart', this._onDown, this); }, removeHooks: function () { - L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); + DomEvent.off(this._map._container, 'touchstart', this._onDown, this); }, _onDown: function (e) { if (!e.touches) { return; } - L.DomEvent.preventDefault(e); + DomEvent.preventDefault(e); this._fireClick = true; @@ -43,15 +52,15 @@ L.Map.Tap = L.Handler.extend({ var first = e.touches[0], el = first.target; - this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); + this._startPos = this._newPos = new Point(first.clientX, first.clientY); // if touching a link, highlight it if (el.tagName && el.tagName.toLowerCase() === 'a') { - L.DomUtil.addClass(el, 'leaflet-active'); + DomUtil.addClass(el, 'leaflet-active'); } // simulate long hold but setting a timeout - this._holdTimeout = setTimeout(L.bind(function () { + this._holdTimeout = setTimeout(Util.bind(function () { if (this._isTapValid()) { this._fireClick = false; this._onUp(); @@ -61,7 +70,7 @@ L.Map.Tap = L.Handler.extend({ this._simulateEvent('mousedown', first); - L.DomEvent.on(document, { + DomEvent.on(document, { touchmove: this._onMove, touchend: this._onUp }, this); @@ -70,7 +79,7 @@ L.Map.Tap = L.Handler.extend({ _onUp: function (e) { clearTimeout(this._holdTimeout); - L.DomEvent.off(document, { + DomEvent.off(document, { touchmove: this._onMove, touchend: this._onUp }, this); @@ -81,7 +90,7 @@ L.Map.Tap = L.Handler.extend({ el = first.target; if (el && el.tagName && el.tagName.toLowerCase() === 'a') { - L.DomUtil.removeClass(el, 'leaflet-active'); + DomUtil.removeClass(el, 'leaflet-active'); } this._simulateEvent('mouseup', first); @@ -99,7 +108,7 @@ L.Map.Tap = L.Handler.extend({ _onMove: function (e) { var first = e.touches[0]; - this._newPos = new L.Point(first.clientX, first.clientY); + this._newPos = new Point(first.clientX, first.clientY); this._simulateEvent('mousemove', first); }, @@ -122,6 +131,6 @@ L.Map.Tap = L.Handler.extend({ // @section Handlers // @property tap: Handler // Mobile touch hacks (quick tap and touch hold) handler. -if (L.Browser.touch && !L.Browser.pointer) { - L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); +if (Browser.touch && !Browser.pointer) { + Map.addInitHook('addHandler', 'tap', Tap); } diff --git a/src/map/handler/Map.TouchZoom.js b/src/map/handler/Map.TouchZoom.js index dff65f97..65d222aa 100644 --- a/src/map/handler/Map.TouchZoom.js +++ b/src/map/handler/Map.TouchZoom.js @@ -1,17 +1,24 @@ +import {Map} from '../Map'; +import {Handler} from '../../core/Handler'; +import * as DomEvent from '../../dom/DomEvent'; +import * as Util from '../../core/Util'; +import * as DomUtil from '../../dom/DomUtil'; +import * as Browser from '../../core/Browser'; + /* * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. */ // @namespace Map // @section Interaction Options -L.Map.mergeOptions({ +Map.mergeOptions({ // @section Touch interaction options // @option touchZoom: Boolean|String = * // Whether the map can be zoomed by touch-dragging with two fingers. If // passed `'center'`, it will zoom to the center of the view regardless of // where the touch events (fingers) were. Enabled for touch-capable web // browsers except for old Androids. - touchZoom: L.Browser.touch && !L.Browser.android23, + touchZoom: Browser.touch && !Browser.android23, // @option bounceAtZoomLimits: Boolean = true // Set it to false if you don't want the map to zoom beyond min/max zoom @@ -19,15 +26,15 @@ L.Map.mergeOptions({ bounceAtZoomLimits: true }); -L.Map.TouchZoom = L.Handler.extend({ +export var TouchZoom = Handler.extend({ addHooks: function () { - L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); - L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); + DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); + DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); }, removeHooks: function () { - L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); - L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); + DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); + DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); }, _onTouchStart: function (e) { @@ -51,11 +58,10 @@ L.Map.TouchZoom = L.Handler.extend({ map._stop(); - L.DomEvent - .on(document, 'touchmove', this._onTouchMove, this) - .on(document, 'touchend', this._onTouchEnd, this); + DomEvent.on(document, 'touchmove', this._onTouchMove, this); + DomEvent.on(document, 'touchend', this._onTouchEnd, this); - L.DomEvent.preventDefault(e); + DomEvent.preventDefault(e); }, _onTouchMove: function (e) { @@ -66,7 +72,6 @@ L.Map.TouchZoom = L.Handler.extend({ p2 = map.mouseEventToContainerPoint(e.touches[1]), scale = p1.distanceTo(p2) / this._startDist; - this._zoom = map.getScaleZoom(scale, this._startZoom); if (!map.options.bounceAtZoomLimits && ( @@ -90,12 +95,12 @@ L.Map.TouchZoom = L.Handler.extend({ this._moved = true; } - L.Util.cancelAnimFrame(this._animRequest); + Util.cancelAnimFrame(this._animRequest); - var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); - this._animRequest = L.Util.requestAnimFrame(moveFn, this, true); + var moveFn = Util.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}); + this._animRequest = Util.requestAnimFrame(moveFn, this, true); - L.DomEvent.preventDefault(e); + DomEvent.preventDefault(e); }, _onTouchEnd: function () { @@ -105,11 +110,10 @@ L.Map.TouchZoom = L.Handler.extend({ } this._zooming = false; - L.Util.cancelAnimFrame(this._animRequest); + Util.cancelAnimFrame(this._animRequest); - L.DomEvent - .off(document, 'touchmove', this._onTouchMove) - .off(document, 'touchend', this._onTouchEnd); + DomEvent.off(document, 'touchmove', this._onTouchMove); + DomEvent.off(document, 'touchend', this._onTouchEnd); // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. if (this._map.options.zoomAnimation) { @@ -123,4 +127,4 @@ L.Map.TouchZoom = L.Handler.extend({ // @section Handlers // @property touchZoom: Handler // Touch zoom handler. -L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); +Map.addInitHook('addHandler', 'touchZoom', TouchZoom);