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
This commit is contained in:
Vladimir Agafonkin 2017-01-30 12:35:16 +02:00 committed by Iván Sánchez Ortega
parent 3ac37c29a4
commit 703ae02aa8
94 changed files with 3382 additions and 3434 deletions

View File

@ -72,9 +72,11 @@ To set up the Leaflet build system, install Node then run the following commands
npm install -g jake npm install -g jake
npm install npm install
``` ```
or, if you prefer [`yarn`](https://yarnpkg.com/) over `npm`:
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. yarn install -g jake
yarn install
```
### Making Changes to Leaflet Source ### Making Changes to Leaflet Source
@ -96,6 +98,25 @@ Also, please make sure that you have [line endings configured properly](https://
Happy coding! 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 ## Running the Tests
To run the tests from the command line, 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 that feature. In order to edit the API documentation, just edit these comments in the
source code. 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 jake docs

View File

@ -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. For a custom build, open build/build.html in the browser and follow the instructions.
*/ */
var build = require('./build/build.js'), var buildDocs = require('./build/docs'),
buildDocs = require('./build/docs'), git = require('git-rev-sync'),
git = require('git-rev'); path = require('path');
function hint(msg, args) { function hint(msg, args) {
return function () { return function () {
@ -29,16 +29,14 @@ function hint(msg, args) {
// Returns the version string in package.json, plus a semver build metadata if // Returns the version string in package.json, plus a semver build metadata if
// this is not an official release // this is not an official release
function calculateVersion(officialRelease, callback) { function calculateVersion(officialRelease) {
var version = require('./package.json').version; var version = require('./package.json').version;
if (officialRelease) { if (officialRelease) {
callback(version); return version;
} else { } else {
git.short(function(str) { return version + '+' + git.short();
callback (version + '+' + str);
});
} }
} }
@ -48,16 +46,53 @@ task('lint', {async: true}, hint('Checking for JS errors...', 'src'));
desc('Check Leaflet specs source for errors with ESLint'); desc('Check Leaflet specs source for errors with ESLint');
task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/suites')); 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'); desc('Run PhantomJS tests');
task('test', ['lint', 'lintspec'], {async: true}, function () { 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'); desc('Build documentation');

View File

@ -6,7 +6,7 @@
"dist/leaflet.css", "dist/leaflet.css",
"dist/leaflet-src.js" "dist/leaflet-src.js"
], ],
"ignore": [ "ignore": [
".*", ".*",
"CHANGELOG.json", "CHANGELOG.json",
"FAQ.md", "FAQ.md",

View File

@ -1,227 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet Build Helper</title>
<script type="text/javascript" src="deps.js"></script>
<style type="text/css">
body {
font: 12px/1.4 Verdana, sans-serif;
text-align: center;
padding: 2em 0;
}
#container {
text-align: left;
margin: 0 auto;
width: 780px;
}
#deplist {
list-style: none;
padding: 0;
}
#deplist li {
padding-top: 7px;
padding-bottom: 7px;
border-bottom: 1px solid #ddd;
}
#deplist li.heading {
border: none;
background: #ddd;
padding: 5px 10px;
margin-top: 25px;
border-radius: 5px;
}
#deplist input {
float: left;
margin-right: 5px;
display: inline;
}
#deplist label {
float: left;
width: 160px;
font-weight: bold;
}
#deplist div {
display: table-cell;
height: 1%;
}
#deplist .desc {
}
#deplist .deps {
color: #777;
}
#command {
width: 100%;
}
#command2 {
width: 200px;
}
#toolbar {
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
}
h2 {
margin-top: 2em;
}
</style>
</head>
<body>
<div id="container">
<h1>Leaflet Build Helper</h1>
<p id="toolbar">
<a id="select-all" href="#all">Select All</a> |
<a id="deselect-all" href="#none">Deselect All</a>
</p>
<ul id="deplist"></ul>
<h2>Building using Node and UglifyJS</h2>
<ol>
<li><a href="http://nodejs.org/#download">Download and install Node</a></li>
<li>Run this in the command line:<br />
<pre><code>npm install -g jake
npm install</code></pre></li>
<li>Run this command inside the Leaflet directory: <br /><input type="text" id="command2" />
</ol>
</div>
<script type="text/javascript">
var deplist = document.getElementById('deplist'),
commandInput = document.getElementById('command2');
document.getElementById('select-all').onclick = function() {
var checks = deplist.getElementsByTagName('input');
for (var i = 0; i < checks.length; i++) {
checks[i].checked = true;
}
updateCommand();
return false;
};
document.getElementById('deselect-all').onclick = function() {
var checks = deplist.getElementsByTagName('input');
for (var i = 0; i < checks.length; i++) {
if (!checks[i].disabled) {
checks[i].checked = false;
}
}
updateCommand();
return false;
};
function updateCommand() {
var files = {};
var checks = deplist.getElementsByTagName('input');
var compsStr = '';
for (var i = 0, len = checks.length; i < len; i++) {
if (checks[i].checked) {
var srcs = deps[checks[i].id].src;
for (var j = 0, len2 = srcs.length; j < len2; j++) {
files[srcs[j]] = true;
}
compsStr = '1' + compsStr;
} else {
compsStr = '0' + compsStr;
}
}
commandInput.value = 'jake build[' + parseInt(compsStr, 2).toString(32) + ',custom]';
}
function inputSelect() {
this.focus();
this.select();
};
commandInput.onclick = inputSelect;
function onCheckboxChange() {
if (this.checked) {
var depDeps = deps[this.id].deps;
if (depDeps) {
for (var i = 0; i < depDeps.length; i++) {
var check = document.getElementById(depDeps[i]);
if (!check.checked) {
check.checked = true;
check.onchange();
}
}
}
} else {
var checks = deplist.getElementsByTagName('input');
for (var i = 0; i < checks.length; i++) {
var dep = deps[checks[i].id];
if (!dep.deps) { continue; }
for (var j = 0; j < dep.deps.length; j++) {
if (dep.deps[j] === this.id) {
if (checks[i].checked) {
checks[i].checked = false;
checks[i].onchange();
}
}
}
}
}
updateCommand();
}
for (var name in deps) {
var li = document.createElement('li');
if (deps[name].heading) {
var heading = document.createElement('li');
heading.className = 'heading';
heading.appendChild(document.createTextNode(deps[name].heading));
deplist.appendChild(heading);
}
var div = document.createElement('div');
var label = document.createElement('label');
var check = document.createElement('input');
check.type = 'checkbox';
check.id = name;
label.appendChild(check);
check.onchange = onCheckboxChange;
if (name == 'Core') {
check.checked = true;
check.disabled = true;
}
label.appendChild(document.createTextNode(name));
label.htmlFor = name;
li.appendChild(label);
var desc = document.createElement('span');
desc.className = 'desc';
desc.appendChild(document.createTextNode(deps[name].desc));
var depText = deps[name].deps && deps[name].deps.join(', ');
if (depText) {
var depspan = document.createElement('span');
depspan.className = 'deps';
depspan.appendChild(document.createTextNode('Deps: ' + depText));
}
div.appendChild(desc);
div.appendChild(document.createElement('br'));
if (depText) { div.appendChild(depspan); }
li.appendChild(div);
deplist.appendChild(li);
}
updateCommand();
</script>
</body>
</html>

View File

@ -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<lineCount; j++) {
fileNode.add(new SourceNode(j+1, 0, files[i], lines[j] + '\n'));
}
node.add(fileNode);
node.add(new SourceNode(null, null, null, '\n\n'));
}
node.add(new SourceNode(null, null, null, '}(window, document));'));
var bundle = node.toStringWithSourceMap();
return {
src: bundle.code,
srcmap: bundle.map.toString()
};
}
function bytesToKB(bytes) {
return (bytes / 1024).toFixed(2) + ' KB';
};
exports.build = function (callback, version, compsBase32, buildName) {
var files = getFiles(compsBase32);
console.log('Bundling and compressing ' + files.length + ' files...');
var copy = fs.readFileSync('src/copyright.js', 'utf8').replace('{VERSION}', version),
filenamePart = 'leaflet' + (buildName ? '-' + buildName : ''),
pathPart = 'dist/' + filenamePart,
srcPath = pathPart + '-src.js',
mapPath = pathPart + '-src.map',
srcFilename = filenamePart + '-src.js',
mapFilename = filenamePart + '-src.map',
bundle = bundleFiles(files, copy, version),
newSrc = bundle.src + '\n//# sourceMappingURL=' + mapFilename,
oldSrc = loadSilently(srcPath),
srcDelta = getSizeDelta(newSrc, oldSrc, true);
console.log('\tUncompressed: ' + bytesToKB(newSrc.length) + srcDelta);
if (newSrc !== oldSrc) {
fs.writeFileSync(srcPath, newSrc);
fs.writeFileSync(mapPath, bundle.srcmap);
console.log('\tSaved to ' + srcPath);
}
var path = pathPart + '.js',
oldCompressed = loadSilently(path),
newCompressed;
try {
newCompressed = copy + UglifyJS.minify(newSrc, {
warnings: true,
fromString: true
}).code;
} catch(err) {
console.error('UglifyJS failed to minify the files');
console.error(err);
callback(err);
}
var delta = getSizeDelta(newCompressed, oldCompressed);
console.log('\tCompressed: ' + bytesToKB(newCompressed.length) + delta);
var newGzipped,
gzippedDelta = '';
function done() {
if (newCompressed !== oldCompressed) {
fs.writeFileSync(path, newCompressed);
console.log('\tSaved to ' + path);
}
console.log('\tGzipped: ' + bytesToKB(newGzipped.length) + gzippedDelta);
callback();
}
zlib.gzip(newCompressed, function (err, gzipped) {
if (err) { return; }
newGzipped = gzipped;
if (oldCompressed && (oldCompressed !== newCompressed)) {
zlib.gzip(oldCompressed, function (err, oldGzipped) {
if (err) { return; }
gzippedDelta = getSizeDelta(gzipped, oldGzipped);
done();
});
} else {
done();
}
});
};
exports.test = function(complete, fail) {
var karma = require('karma'),
testConfig = {configFile : __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();
};

View File

@ -1,240 +0,0 @@
var deps = {
Core: {
src: ['Leaflet.js',
'core/Util.js',
'core/Class.js',
'core/Events.js',
'core/Browser.js',
'geometry/Point.js',
'geometry/Bounds.js',
'geometry/Transformation.js',
'dom/DomUtil.js',
'geo/LatLng.js',
'geo/LatLngBounds.js',
'geo/projection/Projection.LonLat.js',
'geo/projection/Projection.SphericalMercator.js',
'geo/crs/CRS.js',
'geo/crs/CRS.Simple.js',
'geo/crs/CRS.Earth.js',
'geo/crs/CRS.EPSG3857.js',
'geo/crs/CRS.EPSG4326.js',
'map/Map.js',
'layer/Layer.js',
'dom/DomEvent.js',
'dom/PosAnimation.js'
],
desc: 'The core of the library, including OOP, events, DOM facilities, basic units, projections (EPSG:3857 and EPSG:4326) and the base Map class.'
},
EPSG3395: {
src: ['geo/projection/Projection.Mercator.js',
'geo/crs/CRS.EPSG3395.js'],
desc: 'EPSG:3395 projection (used by some map providers).',
heading: 'Additional projections'
},
GridLayer: {
src: ['layer/tile/GridLayer.js'],
desc: 'Used as base class for grid-like layers like TileLayer.',
heading: 'Layers'
},
TileLayer: {
src: ['layer/tile/TileLayer.js'],
desc: 'The base class for displaying tile layers on the map.',
deps: ['GridLayer']
},
TileLayerWMS: {
src: ['layer/tile/TileLayer.WMS.js'],
desc: 'WMS tile layer.',
deps: ['TileLayer']
},
ImageOverlay: {
src: ['layer/ImageOverlay.js'],
desc: 'Used to display an image over a particular rectangular area of the map.'
},
Marker: {
src: ['layer/marker/Icon.js',
'layer/marker/Icon.Default.js',
'layer/marker/Marker.js'],
desc: 'Markers to put on the map.'
},
DivIcon: {
src: ['layer/marker/DivIcon.js'],
deps: ['Marker'],
desc: 'Lightweight div-based icon for markers.'
},
Popup: {
src: [
'layer/DivOverlay.js',
'layer/Popup.js'
],
deps: ['Marker'],
desc: 'Used to display the map popup (used mostly for binding HTML data to markers and paths on click).'
},
Tooltip: {
src: [
'layer/Tooltip.js'
],
deps: ['Popup', 'Marker'],
desc: 'Used to display the map tooltip (used mostly for binding short descriptions to markers and paths on mouseover).'
},
LayerGroup: {
src: ['layer/LayerGroup.js'],
desc: 'Allows grouping several layers to handle them as one.'
},
FeatureGroup: {
src: ['layer/FeatureGroup.js'],
deps: ['LayerGroup', 'Popup'],
desc: 'Extends LayerGroup with mouse events and bindPopup method shared between layers.'
},
Path: {
src: [
'layer/vector/Renderer.js',
'layer/vector/Path.js'
],
desc: 'Vector rendering core.',
heading: 'Vector layers'
},
Polyline: {
src: ['geometry/LineUtil.js',
'layer/vector/Polyline.js'],
deps: ['Path'],
desc: 'Polyline overlays.'
},
Polygon: {
src: ['geometry/PolyUtil.js',
'layer/vector/Polygon.js'],
deps: ['Polyline'],
desc: 'Polygon overlays.'
},
Rectangle: {
src: ['layer/vector/Rectangle.js'],
deps: ['Polygon'],
desc: ['Rectangle overlays.']
},
CircleMarker: {
src: ['layer/vector/CircleMarker.js'],
deps: ['Path'],
desc: 'Circle overlays with a constant pixel radius.'
},
Circle: {
src: ['layer/vector/Circle.js'],
deps: ['CircleMarker'],
desc: 'Circle overlays (with radius in meters).'
},
SVG: {
src: ['layer/vector/SVG.js'],
deps: ['Path'],
desc: 'SVG backend for vector layers.'
},
VML: {
src: ['layer/vector/SVG.VML.js'],
deps: ['SVG'],
desc: 'VML fallback for vector layers in IE7-8.'
},
Canvas: {
src: ['layer/vector/Canvas.js'],
deps: ['CircleMarker', 'Path', 'Polygon', 'Polyline'],
desc: 'Canvas backend for vector layers.'
},
GeoJSON: {
src: ['layer/GeoJSON.js'],
deps: ['Polygon', 'Circle', 'CircleMarker', 'Marker', 'FeatureGroup'],
desc: 'GeoJSON layer, parses the data and adds corresponding layers above.'
},
MapDrag: {
src: ['dom/DomEvent.js',
'dom/Draggable.js',
'core/Handler.js',
'map/handler/Map.Drag.js'],
desc: 'Makes the map draggable (by mouse or touch).',
heading: 'Interaction'
},
MouseZoom: {
src: ['dom/DomEvent.js',
'core/Handler.js',
'map/handler/Map.DoubleClickZoom.js',
'map/handler/Map.ScrollWheelZoom.js'],
desc: 'Scroll wheel zoom and double click zoom on the map.'
},
TouchZoom: {
src: ['dom/DomEvent.js',
'dom/DomEvent.DoubleTap.js',
'dom/DomEvent.Pointer.js',
'core/Handler.js',
'map/handler/Map.TouchZoom.js',
'map/handler/Map.Tap.js'],
deps: ['AnimationZoom'],
desc: 'Enables smooth touch zoom / tap / longhold / doubletap on iOS, IE10, Android.'
},
BoxZoom: {
src: ['map/handler/Map.BoxZoom.js'],
deps: ['MouseZoom'],
desc: 'Enables zooming to bounding box by shift-dragging the map.'
},
Keyboard: {
src: ['map/handler/Map.Keyboard.js'],
desc: 'Enables keyboard pan/zoom when the map is focused.'
},
MarkerDrag: {
src: ['layer/marker/Marker.Drag.js'],
deps: ['Marker'],
desc: 'Makes markers draggable (by mouse or touch).'
},
ControlZoom: {
src: ['control/Control.js',
'control/Control.Zoom.js'],
heading: 'Controls',
desc: 'Basic zoom control with two buttons (zoom in / zoom out).'
},
ControlAttrib: {
src: ['control/Control.js',
'control/Control.Attribution.js'],
desc: 'Attribution control.'
},
ControlScale: {
src: ['control/Control.js',
'control/Control.Scale.js'],
desc: 'Scale control.'
},
ControlLayers: {
src: ['control/Control.js',
'control/Control.Layers.js'],
desc: 'Layer Switcher control.'
}
};
if (typeof exports !== 'undefined') {
exports.deps = deps;
}

View File

@ -6,9 +6,18 @@ VERSION=$(node --eval "console.log(require('./package.json').version);")
npm test || exit 1 npm test || exit 1
echo "Ready to publish Leaflet version $VERSION."
echo "Has the version number been bumped?"
read -n1 -r -p "Press Ctrl+C to cancel, or any other key to continue." key
git checkout -b build git checkout -b build
jake build[,,true] export NODE_ENV=release
npm run-script build
echo "Creating git tag v$VERSION..."
git add dist/leaflet-src.js dist/leaflet.js dist/leaflet-src.map -f git add dist/leaflet-src.js dist/leaflet.js dist/leaflet-src.map -f
git commit -m "v$VERSION" git commit -m "v$VERSION"
@ -16,7 +25,11 @@ git commit -m "v$VERSION"
git tag v$VERSION -f git tag v$VERSION -f
git push --tags -f git push --tags -f
echo "Uploading to NPM..."
npm publish npm publish
git checkout master git checkout master
git branch -D build git branch -D build
echo "All done."

40
build/rollup-config.js Normal file
View File

@ -0,0 +1,40 @@
// Config file for running Rollup in "normal" mode (non-watch)
import rollupGitVersion from 'rollup-plugin-git-version'
import json from 'rollup-plugin-json'
import gitRev from 'git-rev-sync'
let version = require('../package.json').version;
let release;
// Skip the git branch+rev in the banner when doing a release build
if (process.env.NODE_ENV === 'release') {
release = true;
} else {
release = false;
const branch = gitRev.branch();
const rev = gitRev.short();
version += '+' + branch + '.' + rev;
}
const banner = `/*
* 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: banner,
entry: 'src/Leaflet.js',
dest: 'dist/leaflet-src.js',
plugins: [
release ? json() : rollupGitVersion(),
],
sourceMap: true,
legacy: true // Needed to create files loadable by IE8
};

View File

@ -0,0 +1,43 @@
// Config file for running Rollup in "watch" mode
// This adds a sanity check to help ourselves to run 'rollup -w' as needed.
// Needed to create files loadable by IE8
import rollupGitVersion from 'rollup-plugin-git-version'
import gitRev from 'git-rev-sync'
const branch = gitRev.branch();
const rev = gitRev.short();
const version = require('../package.json').version + '+' + branch + '.' + rev;
const now = (new Date()).getTime();
const limit = now + 5 * 60 * 1000; // 5 minutes, in milliseconds
const warningCode = `
if ((new Date()).getTime() > ` + 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
};

View File

@ -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("<script src='" + path + scripts[i] + "'></script>");
}
})();
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);
}

1
debug/leaflet-include.js Symbolic link
View File

@ -0,0 +1 @@
leaflet-rollup-src.js

105
debug/map/rollup.html Normal file
View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../css/screen.css" />
<!-- <script type="text/javascript" src="../../dist/leaflet-rollup-src.js"></script> -->
<script type="text/javascript" src="../leaflet-rollup-src.js"></script>
</head>
<body>
<div id="map"></div>
<button id="populate-markers">Populate with markers</button>
<button id="populate-circles">Populate with circles</button>
<button id="populate-lines">Populate with lines</button>
<button id="populate-polygons">Populate with polygons</button>
<script type="text/javascript">
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
var map = L.map('map')
.setView([50.5, 30.51], 15)
.addLayer(osm);
var markers = new L.FeatureGroup();
function getRandomLatLng(llbounds) {
var s = llbounds.getSouth(),
n = llbounds.getNorth(),
w = llbounds.getWest(),
e = llbounds.getEast();
return L.latLng(
s + (Math.random() * (n - s)),
w + (Math.random() * (e - w))
)
}
function populateMarker() {
for (var i = 0; i < 5; i++) {
L.marker(getRandomLatLng(map.getBounds())).addTo(markers);
}
return false;
}
function populateCircle() {
for (var i = 0; i < 5; i++) {
L.circleMarker(getRandomLatLng(map.getBounds())).addTo(markers);
}
return false;
}
function populateLine() {
var lineCoords = [];
for (var i = 0; i < 10; i++) {
lineCoords.push(getRandomLatLng(map.getBounds()));
}
L.polyline(lineCoords).addTo(map);
return false;
}
function populatePoly() {
var lineCoords = [];
for (var i = 0; i < 10; i++) {
lineCoords.push(getRandomLatLng(map.getBounds()));
}
L.polygon(lineCoords).addTo(map);
return false;
}
markers.bindPopup("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede.</p><p>Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque.</p>").addTo(map);
L.DomUtil.get('populate-markers').onclick = populateMarker;
L.DomUtil.get('populate-circles').onclick = populateCircle;
L.DomUtil.get('populate-lines').onclick = populateLine;
L.DomUtil.get('populate-polygons').onclick = populatePoly;
function logEvent(e) { console.log(e.type); }
populateMarker();
populateCircle();
populateLine();
populatePoly();
// map.on('click', logEvent);
// map.on('contextmenu', logEvent);
// map.on('movestart', logEvent);
// map.on('move', logEvent);
// map.on('moveend', logEvent);
// map.on('zoomstart', logEvent);
// map.on('zoomend', logEvent);
</script>
</body>
</html>

View File

@ -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 f1 = hand.growFinger('touch');
var f2 = hand.growFinger('touch'); var f2 = hand.growFinger('touch');
@ -152,4 +153,4 @@
</script> </script>
</body> </body>
</html> </html>

View File

@ -5,7 +5,7 @@ layout: v2
<div class="announcement">Jan 23, 2017 — <a href="http://leafletjs.com/2017/01/23/leaflet-1.0.3.html">Leaflet 1.0.3</a>, a bugfix release, is out.</div> <div class="announcement">Jan 23, 2017 — <a href="http://leafletjs.com/2017/01/23/leaflet-1.0.3.html">Leaflet 1.0.3</a>, a bugfix release, is out.</div>
<p>Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps. <p>Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps.
Weighing just about <abbr title="33 KB gzipped &mdash; that's 123 KB minified and 218 KB in the source form, with 10 KB of CSS (2 KB gzipped) and 11 KB of images.">33 KB of JS</abbr>, Weighing just about <abbr title="38 KB gzipped &mdash; that's 133 KB minified and 378 KB in the source form, with 10 KB of CSS (2 KB gzipped) and 11 KB of images.">38 KB of JS</abbr>,
it&nbsp;has all the mapping <a href="#features">features</a> most developers ever need.</p> it&nbsp;has all the mapping <a href="#features">features</a> most developers ever need.</p>
<p>Leaflet is designed with <em>simplicity</em>, <em>performance</em> and <em>usability</em> in mind. <p>Leaflet is designed with <em>simplicity</em>, <em>performance</em> and <em>usability</em> in mind.

View File

@ -5,7 +5,7 @@
"devDependencies": { "devDependencies": {
"eslint": "^3.5.0 <3.6.0", "eslint": "^3.5.0 <3.6.0",
"eslint-config-mourner": "^2.0.1", "eslint-config-mourner": "^2.0.1",
"git-rev": "^0.2.1", "git-rev-sync": "^1.8.0",
"happen": "~0.3.1", "happen": "~0.3.1",
"jake": "~8.0.12", "jake": "~8.0.12",
"karma": "^1.3.0", "karma": "^1.3.0",
@ -14,20 +14,32 @@
"karma-firefox-launcher": "~1.0.0", "karma-firefox-launcher": "~1.0.0",
"karma-mocha": "^1.2.0", "karma-mocha": "^1.2.0",
"karma-phantomjs-launcher": "^1.0.2", "karma-phantomjs-launcher": "^1.0.2",
"karma-rollup-preprocessor": "^2.0.2",
"karma-safari-launcher": "~1.0.0", "karma-safari-launcher": "~1.0.0",
"leafdoc": "^1.4.1", "leafdoc": "^1.4.1",
"mocha": "^3.1.0", "mocha": "^3.1.0",
"phantomjs-prebuilt": "^2.1.12", "phantomjs-prebuilt": "^2.1.12",
"prosthetic-hand": "^1.3.1", "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", "source-map": "^0.5.6",
"uglify-js": "~2.7.3" "uglify-js": "~2.7.3"
}, },
"main": "dist/leaflet-src.js", "main": "dist/leaflet-src.js",
"style": "dist/leaflet.css", "style": "dist/leaflet.css",
"scripts": { "scripts": {
"test-jake": "jake test",
"test": "jake test", "test": "jake test",
"build": "jake build", "build-jake": "jake build",
"release": "./build/publish.sh" "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": { "eslintConfig": {
"root": true, "root": true,
@ -40,7 +52,15 @@
"node": false "node": false
}, },
"extends": "mourner", "extends": "mourner",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"rules": { "rules": {
"linebreak-style": [
0,
"unix"
],
"no-mixed-spaces-and-tabs": [ "no-mixed-spaces-and-tabs": [
2, 2,
"smart-tabs" "smart-tabs"

View File

@ -19,7 +19,7 @@
// Trick Leaflet into believing we have a touchscreen (for Chrome) // Trick Leaflet into believing we have a touchscreen (for Chrome)
if (window.Touch) { window.ontouchstart = function(){} }; if (window.Touch) { window.ontouchstart = function(){} };
</script> </script>
<script type="text/javascript" src="../build/deps.js"></script> <!-- <script type="text/javascript" src="../build/deps.js"></script> -->
<script type="text/javascript" src="../debug/leaflet-include.js"></script> <script type="text/javascript" src="../debug/leaflet-include.js"></script>

View File

@ -1,12 +1,17 @@
// var es3 = require('rollup-plugin-es3');
var json = require('rollup-plugin-json');
// Karma configuration // Karma configuration
module.exports = function (config) { module.exports = function (config) {
var libSources = require(__dirname + '/../build/build.js').getFiles(); // var libSources = require(__dirname + '/../build/build.js').getFiles();
var files = [ var files = [
"spec/sinon.js", "spec/sinon.js",
"spec/expect.js" "spec/expect.js",
].concat(libSources, [
"src/Leaflet.js",
"spec/after.js", "spec/after.js",
"node_modules/happen/happen.js", "node_modules/happen/happen.js",
"node_modules/prosthetic-hand/dist/prosthetic-hand.js", "node_modules/prosthetic-hand/dist/prosthetic-hand.js",
@ -14,13 +19,14 @@ module.exports = function (config) {
"spec/suites/**/*.js", "spec/suites/**/*.js",
"dist/*.css", "dist/*.css",
{pattern: "dist/images/*.png", included: false, serve: true} {pattern: "dist/images/*.png", included: false, serve: true}
]); ];
config.set({ config.set({
// base path, that will be used to resolve files and exclude // base path, that will be used to resolve files and exclude
basePath: '../', basePath: '../',
plugins: [ plugins: [
'karma-rollup-preprocessor',
'karma-mocha', 'karma-mocha',
'karma-coverage', 'karma-coverage',
'karma-phantomjs-launcher', 'karma-phantomjs-launcher',
@ -38,6 +44,24 @@ module.exports = function (config) {
}, },
exclude: [], 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 // test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
reporters: ['dots'], reporters: ['dots'],

View File

@ -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 // 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; 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 // A couple of tests need the browser to be touch-capable
it.skipIfNotTouch = window.TouchEvent ? it : it.skip; it.skipIfNotTouch = window.TouchEvent ? it : it.skip;

View File

@ -1,14 +1,17 @@
describe('Path', function () { 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 () { describe('#bringToBack', function () {
it('is a no-op for layers not on a map', 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); expect(path.bringToBack()).to.equal(path);
}); });
}); });
describe('#bringToFront', function () { describe('#bringToFront', function () {
it('is a no-op for layers not on a map', 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); expect(path.bringToFront()).to.equal(path);
}); });
}); });

View File

@ -12,7 +12,7 @@ describe('Polygon', function () {
var polygon = new L.Polygon(latLngs); 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); expect(polygon.getLatLngs()).to.eql(polygon._latlngs);
}); });

View File

@ -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);
});
});
});

View File

@ -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 () { describe("#_defaultShape", function () {

View File

@ -188,11 +188,11 @@ describe("Map", function () {
expect(map.getBoundsZoom(bounds, false, padding)).to.be.equal(19); 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(); var container = map.getContainer();
container.style.height = height; container.style.height = height;
document.body.appendChild(container); document.body.appendChild(container);
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.options.zoomSnap = 0.5; map.options.zoomSnap = 0.5;
expect(map.getBoundsZoom(bounds, false, padding)).to.be.equal(19.5); expect(map.getBoundsZoom(bounds, false, padding)).to.be.equal(19.5);
map.options.zoomSnap = 0.2; map.options.zoomSnap = 0.2;
@ -770,8 +770,8 @@ describe("Map", function () {
map.zoomOut(null, {animate: false}); map.zoomOut(null, {animate: false});
}); });
it('zoomIn ignores the zoomDelta option on non-any3d browsers', function (done) { it.skipInNonPhantom('zoomIn ignores the zoomDelta option on non-any3d browsers', function (done) {
L.Browser.any3d = false; // L.Browser.any3d = false; // L.Browser is frozen since ES6ication
map.options.zoomSnap = 0.25; map.options.zoomSnap = 0.25;
map.options.zoomDelta = 0.25; map.options.zoomDelta = 0.25;
map.once('zoomend', function () { map.once('zoomend', function () {
@ -782,8 +782,8 @@ describe("Map", function () {
map.zoomIn(null, {animate: false}); map.zoomIn(null, {animate: false});
}); });
it('zoomIn respects the zoomDelta option on any3d browsers', function (done) { it.skipInPhantom('zoomIn respects the zoomDelta option on any3d browsers', function (done) {
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.options.zoomSnap = 0.25; map.options.zoomSnap = 0.25;
map.options.zoomDelta = 0.25; map.options.zoomDelta = 0.25;
map.setView(center, 10); map.setView(center, 10);
@ -795,8 +795,8 @@ describe("Map", function () {
map.zoomIn(null, {animate: false}); map.zoomIn(null, {animate: false});
}); });
it('zoomOut respects the zoomDelta option on any3d browsers', function (done) { it.skipInPhantom('zoomOut respects the zoomDelta option on any3d browsers', function (done) {
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.options.zoomSnap = 0.25; map.options.zoomSnap = 0.25;
map.options.zoomDelta = 0.25; map.options.zoomDelta = 0.25;
map.setView(center, 10); map.setView(center, 10);
@ -808,7 +808,7 @@ describe("Map", function () {
map.zoomOut(null, {animate: false}); 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.options.zoomSnap = 0.25;
map.setView(center, 10); map.setView(center, 10);
map.once('zoomend', function () { map.once('zoomend', function () {
@ -816,11 +816,11 @@ describe("Map", function () {
expect(map.getCenter()).to.eql(center); expect(map.getCenter()).to.eql(center);
done(); done();
}); });
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.zoomIn(0.22, {animate: false}); 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.options.zoomSnap = 0.25;
map.setView(center, 10); map.setView(center, 10);
map.once('zoomend', function () { map.once('zoomend', function () {
@ -828,7 +828,7 @@ describe("Map", function () {
expect(map.getCenter()).to.eql(center); expect(map.getCenter()).to.eql(center);
done(); done();
}); });
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.zoomOut(0.22, {animate: false}); map.zoomOut(0.22, {animate: false});
}); });
}); });
@ -859,9 +859,9 @@ describe("Map", function () {
map.fitBounds(bounds, {animate: false}); 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; map.options.zoomSnap = 0.25;
L.Browser.any3d = true; // L.Browser.any3d = true; // L.Browser is frozen since ES6ication
map.once('zoomend', function () { map.once('zoomend', function () {
expect(map.getZoom()).to.eql(2.75); expect(map.getZoom()).to.eql(2.75);
expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true); expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true);
@ -870,9 +870,9 @@ describe("Map", function () {
map.fitBounds(bounds, {animate: false}); 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; map.options.zoomSnap = 0.25;
L.Browser.any3d = false; // L.Browser.any3d = false; // L.Browser is frozen since ES6ication
map.once('zoomend', function () { map.once('zoomend', function () {
expect(map.getZoom()).to.eql(2); expect(map.getZoom()).to.eql(2);
expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true); expect(map.getCenter().equals(boundsCenter, 0.05)).to.eql(true);

View File

@ -1,29 +1,170 @@
var L = { import {version} from '../package.json';
version: '1.0.3' export {version};
};
function expose() { // control
var oldL = window.L;
L.noConflict = function () { import {Control, control} from './control/Control';
window.L = oldL; import {Layers, layers} from './control/Control.Layers';
return this; 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; Control.Layers = Layers;
} Control.Zoom = Zoom;
Control.Scale = Scale;
// define Leaflet for Node module pattern loaders, including Browserify Control.Attribution = Attribution;
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = L; control.layers = layers;
control.zoom = zoom;
// define Leaflet as an AMD module control.scale = scale;
} else if (typeof define === 'function' && define.amd) { control.attribution = attribution;
define(L);
} export {Control, control};
// define Leaflet as a global L variable, saving the original L to restore later if needed // core
if (typeof window !== 'undefined') {
expose(); 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;
} }

View File

@ -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 * @class Control.Attribution
* @aka L.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. * 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 // @section
// @aka Control.Attribution options // @aka Control.Attribution options
options: { options: {
@ -18,17 +25,15 @@ L.Control.Attribution = L.Control.extend({
}, },
initialize: function (options) { initialize: function (options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._attributions = {}; this._attributions = {};
}, },
onAdd: function (map) { onAdd: function (map) {
map.attributionControl = this; map.attributionControl = this;
this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); this._container = DomUtil.create('div', 'leaflet-control-attribution');
if (L.DomEvent) { DomEvent.disableClickPropagation(this._container);
L.DomEvent.disableClickPropagation(this._container);
}
// TODO ugly, refactor // TODO ugly, refactor
for (var i in map._layers) { for (var i in map._layers) {
@ -106,19 +111,19 @@ L.Control.Attribution = L.Control.extend({
// @section Control options // @section Control options
// @option attributionControl: Boolean = true // @option attributionControl: Boolean = true
// Whether a [attribution control](#control-attribution) is added to the map by default. // Whether a [attribution control](#control-attribution) is added to the map by default.
L.Map.mergeOptions({ Map.mergeOptions({
attributionControl: true attributionControl: true
}); });
L.Map.addInitHook(function () { Map.addInitHook(function () {
if (this.options.attributionControl) { if (this.options.attributionControl) {
new L.Control.Attribution().addTo(this); new Attribution().addTo(this);
} }
}); });
// @namespace Control.Attribution // @namespace Control.Attribution
// @factory L.control.attribution(options: Control.Attribution options) // @factory L.control.attribution(options: Control.Attribution options)
// Creates an attribution control. // Creates an attribution control.
L.control.attribution = function (options) { export var attribution = function (options) {
return new L.Control.Attribution(options); return new Attribution(options);
}; };

View File

@ -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 * @class Control.Layers
* @aka L.Control.Layers * @aka L.Control.Layers
@ -37,8 +44,7 @@
* ``` * ```
*/ */
export var Layers = Control.extend({
L.Control.Layers = L.Control.extend({
// @section // @section
// @aka Control.Layers options // @aka Control.Layers options
options: { options: {
@ -72,7 +78,7 @@ L.Control.Layers = L.Control.extend({
}, },
initialize: function (baseLayers, overlays, options) { initialize: function (baseLayers, overlays, options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._layers = []; this._layers = [];
this._lastZIndex = 0; this._lastZIndex = 0;
@ -124,7 +130,7 @@ L.Control.Layers = L.Control.extend({
removeLayer: function (layer) { removeLayer: function (layer) {
layer.off('add remove', this._onLayerChange, this); layer.off('add remove', this._onLayerChange, this);
var obj = this._getLayer(L.stamp(layer)); var obj = this._getLayer(Util.stamp(layer));
if (obj) { if (obj) {
this._layers.splice(this._layers.indexOf(obj), 1); this._layers.splice(this._layers.indexOf(obj), 1);
} }
@ -134,14 +140,14 @@ L.Control.Layers = L.Control.extend({
// @method expand(): this // @method expand(): this
// Expand the control container if collapsed. // Expand the control container if collapsed.
expand: function () { expand: function () {
L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded'); DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
this._form.style.height = null; this._form.style.height = null;
var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
if (acceptableHeight < this._form.clientHeight) { 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'; this._form.style.height = acceptableHeight + 'px';
} else { } else {
L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
} }
this._checkDisabledLayers(); this._checkDisabledLayers();
return this; return this;
@ -150,51 +156,50 @@ L.Control.Layers = L.Control.extend({
// @method collapse(): this // @method collapse(): this
// Collapse the control container if expanded. // Collapse the control container if expanded.
collapse: function () { collapse: function () {
L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded'); DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
return this; return this;
}, },
_initLayout: function () { _initLayout: function () {
var className = 'leaflet-control-layers', var className = 'leaflet-control-layers',
container = this._container = L.DomUtil.create('div', className), container = this._container = DomUtil.create('div', className),
collapsed = this.options.collapsed; collapsed = this.options.collapsed;
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released // 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); container.setAttribute('aria-haspopup', true);
L.DomEvent.disableClickPropagation(container); DomEvent.disableClickPropagation(container);
if (!L.Browser.touch) { if (!Browser.touch) {
L.DomEvent.disableScrollPropagation(container); DomEvent.disableScrollPropagation(container);
} }
var form = this._form = L.DomUtil.create('form', className + '-list'); var form = this._form = DomUtil.create('form', className + '-list');
if (collapsed) { if (collapsed) {
this._map.on('click', this.collapse, this); this._map.on('click', this.collapse, this);
if (!L.Browser.android) { if (!Browser.android) {
L.DomEvent.on(container, { DomEvent.on(container, {
mouseenter: this.expand, mouseenter: this.expand,
mouseleave: this.collapse mouseleave: this.collapse
}, this); }, 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.href = '#';
link.title = 'Layers'; link.title = 'Layers';
if (L.Browser.touch) { if (Browser.touch) {
L.DomEvent DomEvent.on(link, 'click', DomEvent.stop);
.on(link, 'click', L.DomEvent.stop) DomEvent.on(link, 'click', this.expand, this);
.on(link, 'click', this.expand, this);
} else { } 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 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
L.DomEvent.on(form, 'click', function () { DomEvent.on(form, 'click', function () {
setTimeout(L.bind(this._onInputClick, this), 0); setTimeout(Util.bind(this._onInputClick, this), 0);
}, this); }, this);
// TODO keyboard accessibility // TODO keyboard accessibility
@ -203,9 +208,9 @@ L.Control.Layers = L.Control.extend({
this.expand(); this.expand();
} }
this._baseLayersList = L.DomUtil.create('div', className + '-base', form); this._baseLayersList = DomUtil.create('div', className + '-base', form);
this._separator = L.DomUtil.create('div', className + '-separator', form); this._separator = DomUtil.create('div', className + '-separator', form);
this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); this._overlaysList = DomUtil.create('div', className + '-overlays', form);
container.appendChild(form); container.appendChild(form);
}, },
@ -213,7 +218,7 @@ L.Control.Layers = L.Control.extend({
_getLayer: function (id) { _getLayer: function (id) {
for (var i = 0; i < this._layers.length; i++) { 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]; return this._layers[i];
} }
} }
@ -243,8 +248,8 @@ L.Control.Layers = L.Control.extend({
_update: function () { _update: function () {
if (!this._container) { return this; } if (!this._container) { return this; }
L.DomUtil.empty(this._baseLayersList); DomUtil.empty(this._baseLayersList);
L.DomUtil.empty(this._overlaysList); DomUtil.empty(this._overlaysList);
var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
@ -272,7 +277,7 @@ L.Control.Layers = L.Control.extend({
this._update(); this._update();
} }
var obj = this._getLayer(L.stamp(e.target)); var obj = this._getLayer(Util.stamp(e.target));
// @namespace Map // @namespace Map
// @section Layer events // @section Layer events
@ -318,9 +323,9 @@ L.Control.Layers = L.Control.extend({
input = this._createRadioElement('leaflet-base-layers', checked); 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'); var name = document.createElement('span');
name.innerHTML = ' ' + obj.name; 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) // @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. // 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) { export var layers = function (baseLayers, overlays, options) {
return new L.Control.Layers(baseLayers, overlays, options); return new Layers(baseLayers, overlays, options);
}; };

View File

@ -1,3 +1,7 @@
import {Control} from './Control';
import * as DomUtil from '../dom/DomUtil';
/* /*
* @class Control.Scale * @class Control.Scale
* @aka L.Control.Scale * @aka L.Control.Scale
@ -12,7 +16,7 @@
* ``` * ```
*/ */
L.Control.Scale = L.Control.extend({ export var Scale = Control.extend({
// @section // @section
// @aka Control.Scale options // @aka Control.Scale options
options: { options: {
@ -36,7 +40,7 @@ L.Control.Scale = L.Control.extend({
onAdd: function (map) { onAdd: function (map) {
var className = 'leaflet-control-scale', var className = 'leaflet-control-scale',
container = L.DomUtil.create('div', className), container = DomUtil.create('div', className),
options = this.options; options = this.options;
this._addScales(options, className + '-line', container); this._addScales(options, className + '-line', container);
@ -53,10 +57,10 @@ L.Control.Scale = L.Control.extend({
_addScales: function (options, className, container) { _addScales: function (options, className, container) {
if (options.metric) { if (options.metric) {
this._mScale = L.DomUtil.create('div', className, container); this._mScale = DomUtil.create('div', className, container);
} }
if (options.imperial) { 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) // @factory L.control.scale(options?: Control.Scale options)
// Creates an scale control with the given options. // Creates an scale control with the given options.
L.control.scale = function (options) { export var scale = function (options) {
return new L.Control.Scale(options); return new Scale(options);
}; };

View File

@ -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 * @class Control.Zoom
* @aka L.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`. * 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 // @section
// @aka Control.Zoom options // @aka Control.Zoom options
options: { options: {
@ -31,7 +37,7 @@ L.Control.Zoom = L.Control.extend({
onAdd: function (map) { onAdd: function (map) {
var zoomName = 'leaflet-control-zoom', var zoomName = 'leaflet-control-zoom',
container = L.DomUtil.create('div', zoomName + ' leaflet-bar'), container = DomUtil.create('div', zoomName + ' leaflet-bar'),
options = this.options; options = this.options;
this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle, 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) { _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.innerHTML = html;
link.href = '#'; link.href = '#';
link.title = title; link.title = title;
@ -85,11 +91,10 @@ L.Control.Zoom = L.Control.extend({
link.setAttribute('role', 'button'); link.setAttribute('role', 'button');
link.setAttribute('aria-label', title); link.setAttribute('aria-label', title);
L.DomEvent DomEvent.on(link, 'mousedown dblclick', DomEvent.stopPropagation);
.on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) DomEvent.on(link, 'click', DomEvent.stop);
.on(link, 'click', L.DomEvent.stop) DomEvent.on(link, 'click', fn, this);
.on(link, 'click', fn, this) DomEvent.on(link, 'click', this._refocusOnMap, this);
.on(link, 'click', this._refocusOnMap, this);
return link; return link;
}, },
@ -98,14 +103,14 @@ L.Control.Zoom = L.Control.extend({
var map = this._map, var map = this._map,
className = 'leaflet-disabled'; className = 'leaflet-disabled';
L.DomUtil.removeClass(this._zoomInButton, className); DomUtil.removeClass(this._zoomInButton, className);
L.DomUtil.removeClass(this._zoomOutButton, className); DomUtil.removeClass(this._zoomOutButton, className);
if (this._disabled || map._zoom === map.getMinZoom()) { 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()) { 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 // @section Control options
// @option zoomControl: Boolean = true // @option zoomControl: Boolean = true
// Whether a [zoom control](#control-zoom) is added to the map by default. // Whether a [zoom control](#control-zoom) is added to the map by default.
L.Map.mergeOptions({ Map.mergeOptions({
zoomControl: true zoomControl: true
}); });
L.Map.addInitHook(function () { Map.addInitHook(function () {
if (this.options.zoomControl) { if (this.options.zoomControl) {
this.zoomControl = new L.Control.Zoom(); this.zoomControl = new Zoom();
this.addControl(this.zoomControl); this.addControl(this.zoomControl);
} }
}); });
@ -128,6 +133,6 @@ L.Map.addInitHook(function () {
// @namespace Control.Zoom // @namespace Control.Zoom
// @factory L.control.zoom(options: Control.Zoom options) // @factory L.control.zoom(options: Control.Zoom options)
// Creates a zoom control // Creates a zoom control
L.control.zoom = function (options) { export var zoom = function (options) {
return new L.Control.Zoom(options); return new Zoom(options);
}; };

View File

@ -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 * @class Control
* @aka L.Control * @aka L.Control
@ -7,7 +13,7 @@
* All other controls extend from this class. * All other controls extend from this class.
*/ */
L.Control = L.Class.extend({ export var Control = Class.extend({
// @section // @section
// @aka Control options // @aka Control options
options: { options: {
@ -18,7 +24,7 @@ L.Control = L.Class.extend({
}, },
initialize: function (options) { initialize: function (options) {
L.setOptions(this, options); Util.setOptions(this, options);
}, },
/* @section /* @section
@ -65,7 +71,7 @@ L.Control = L.Class.extend({
pos = this.getPosition(), pos = this.getPosition(),
corner = map._controlCorners[pos]; corner = map._controlCorners[pos];
L.DomUtil.addClass(container, 'leaflet-control'); DomUtil.addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') !== -1) { if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild); corner.insertBefore(container, corner.firstChild);
@ -83,7 +89,7 @@ L.Control = L.Class.extend({
return this; return this;
} }
L.DomUtil.remove(this._container); DomUtil.remove(this._container);
if (this.onRemove) { if (this.onRemove) {
this.onRemove(this._map); this.onRemove(this._map);
@ -102,8 +108,8 @@ L.Control = L.Class.extend({
} }
}); });
L.control = function (options) { export var control = function (options) {
return new L.Control(options); return new Control(options);
}; };
/* @section Extension methods /* @section Extension methods
@ -121,7 +127,7 @@ L.control = function (options) {
/* @namespace Map /* @namespace Map
* @section Methods for Layers and Controls * @section Methods for Layers and Controls
*/ */
L.Map.include({ Map.include({
// @method addControl(control: Control): this // @method addControl(control: Control): this
// Adds the given control to the map // Adds the given control to the map
addControl: function (control) { addControl: function (control) {
@ -140,12 +146,12 @@ L.Map.include({
var corners = this._controlCorners = {}, var corners = this._controlCorners = {},
l = 'leaflet-', l = 'leaflet-',
container = this._controlContainer = container = this._controlContainer =
L.DomUtil.create('div', l + 'control-container', this._container); DomUtil.create('div', l + 'control-container', this._container);
function createCorner(vSide, hSide) { function createCorner(vSide, hSide) {
var className = l + vSide + ' ' + l + 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'); createCorner('top', 'left');
@ -155,6 +161,6 @@ L.Map.include({
}, },
_clearControlPos: function () { _clearControlPos: function () {
L.DomUtil.remove(this._controlContainer); DomUtil.remove(this._controlContainer);
} }
}); });

View File

@ -1,4 +0,0 @@
/*
Leaflet {VERSION}, a JS library for interactive maps. http://leafletjs.com
(c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/

View File

@ -1,3 +1,5 @@
import {svgCreate} from '../layer/vector/SVG.Util';
/* /*
* @namespace Browser * @namespace Browser
* @aka L.Browser * @aka L.Browser
@ -13,139 +15,128 @@
* ``` * ```
*/ */
(function () { var style = document.documentElement.style;
var ua = navigator.userAgent.toLowerCase(), // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
doc = document.documentElement, 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, // @property edge: Boolean; `true` for the Edge web browser.
phantomjs = ua.indexOf('phantom') !== -1, export var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
android23 = ua.search('android [23]') !== -1,
chrome = ua.indexOf('chrome') !== -1,
gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
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, // @property android: Boolean
msPointer = !window.PointerEvent && window.MSPointerEvent, // `true` for any browser running on an Android platform.
pointer = window.PointerEvent || msPointer, export var android = userAgentContains('android');
ie3d = ie && ('transition' in doc.style), // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, export var android23 = userAgentContains('android 2') || userAgentContains('android 3');
gecko3d = 'MozPerspective' in doc.style,
opera12 = 'OTransition' in doc.style; // @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 || // @property canvas: Boolean
(window.DocumentTouch && document instanceof window.DocumentTouch)); // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
export var canvas = (function () {
L.Browser = { return !!document.createElement('canvas').getContext;
// @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 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 = '<v:shape adj="1"/>';
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;
}

View File

@ -1,3 +1,4 @@
import * as Util from './Util';
// @class Class // @class Class
// @aka L.Class // @aka L.Class
@ -7,9 +8,9 @@
// Thanks to John Resig and Dean Edwards for inspiration! // 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 // @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included. // [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 parentProto = NewClass.__super__ = this.prototype;
var proto = L.Util.create(parentProto); var proto = Util.create(parentProto);
proto.constructor = NewClass; proto.constructor = NewClass;
NewClass.prototype = proto; NewClass.prototype = proto;
@ -41,23 +42,23 @@ L.Class.extend = function (props) {
// mix static properties into the class // mix static properties into the class
if (props.statics) { if (props.statics) {
L.extend(NewClass, props.statics); Util.extend(NewClass, props.statics);
delete props.statics; delete props.statics;
} }
// mix includes into the prototype // mix includes into the prototype
if (props.includes) { if (props.includes) {
L.Util.extend.apply(null, [proto].concat(props.includes)); Util.extend.apply(null, [proto].concat(props.includes));
delete props.includes; delete props.includes;
} }
// merge options // merge options
if (proto.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 // mix given properties into the prototype
L.extend(proto, props); Util.extend(proto, props);
proto._initHooks = []; proto._initHooks = [];
@ -83,21 +84,21 @@ L.Class.extend = function (props) {
// @function include(properties: Object): this // @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class. // [Includes a mixin](#class-includes) into the current class.
L.Class.include = function (props) { Class.include = function (props) {
L.extend(this.prototype, props); Util.extend(this.prototype, props);
return this; return this;
}; };
// @function mergeOptions(options: Object): this // @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class. // [Merges `options`](#class-options) into the defaults of the class.
L.Class.mergeOptions = function (options) { Class.mergeOptions = function (options) {
L.extend(this.prototype.options, options); Util.extend(this.prototype.options, options);
return this; return this;
}; };
// @function addInitHook(fn: Function): this // @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class. // 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 args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () { var init = typeof fn === 'function' ? fn : function () {

View File

@ -1,3 +1,6 @@
import {Class} from './Class';
import * as Util from './Util';
/* /*
* @class Evented * @class Evented
* @aka L.Evented * @aka L.Evented
@ -23,8 +26,7 @@
* ``` * ```
*/ */
export var Evented = Class.extend({
L.Evented = L.Class.extend({
/* @method on(type: String, fn: Function, context?: Object): this /* @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'`). * 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 { } else {
// types can be a string of space-separated words // 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++) { for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context); this._on(types[i], fn, context);
@ -78,7 +80,7 @@ L.Evented = L.Class.extend({
} }
} else { } else {
types = L.Util.splitWords(types); types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) { for (var i = 0, len = types.length; i < len; i++) {
this._off(types[i], fn, context); this._off(types[i], fn, context);
@ -132,7 +134,7 @@ L.Evented = L.Class.extend({
if (!fn) { if (!fn) {
// Set all removed listeners to noop so they are not called if remove happens in fire // 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++) { 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 // clear all listeners for a type if function isn't specified
delete this._events[type]; delete this._events[type];
@ -152,7 +154,7 @@ L.Evented = L.Class.extend({
if (l.fn === fn) { if (l.fn === fn) {
// set the removed listener to noop so that's not called if remove happens in fire // 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) { if (this._firingCount) {
/* copy array in case events are being fired */ /* copy array in case events are being fired */
@ -173,7 +175,7 @@ L.Evented = L.Class.extend({
fire: function (type, data, propagate) { fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; } 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) { if (this._events) {
var listeners = this._events[type]; var listeners = this._events[type];
@ -223,7 +225,7 @@ L.Evented = L.Class.extend({
return this; return this;
} }
var handler = L.bind(function () { var handler = Util.bind(function () {
this this
.off(types, fn, context) .off(types, fn, context)
.off(types, handler, 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 // Adds an event parent - an `Evented` that will receive propagated events
addEventParent: function (obj) { addEventParent: function (obj) {
this._eventParents = this._eventParents || {}; this._eventParents = this._eventParents || {};
this._eventParents[L.stamp(obj)] = obj; this._eventParents[Util.stamp(obj)] = obj;
return this; return this;
}, },
@ -247,19 +249,19 @@ L.Evented = L.Class.extend({
// Removes an event parent, so it will stop receiving propagated events // Removes an event parent, so it will stop receiving propagated events
removeEventParent: function (obj) { removeEventParent: function (obj) {
if (this._eventParents) { if (this._eventParents) {
delete this._eventParents[L.stamp(obj)]; delete this._eventParents[Util.stamp(obj)];
} }
return this; return this;
}, },
_propagateEvent: function (e) { _propagateEvent: function (e) {
for (var id in this._eventParents) { 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 // aliases; we should ditch those eventually
@ -285,5 +287,3 @@ proto.fireEvent = proto.fire;
// @method hasEventListeners(…): Boolean // @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens) // Alias to [`listens(…)`](#evented-listens)
proto.hasEventListeners = proto.listens; proto.hasEventListeners = proto.listens;
L.Mixin = {Events: proto};

View File

@ -1,3 +1,5 @@
import {Class} from './Class';
/* /*
L.Handler is a base class for handler classes that are used internally to inject 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. interaction features like dragging to classes like Map and Marker.
@ -7,7 +9,7 @@
// @aka L.Handler // @aka L.Handler
// Abstract class for map interaction handlers // Abstract class for map interaction handlers
L.Handler = L.Class.extend({ export var Handler = Class.extend({
initialize: function (map) { initialize: function (map) {
this._map = map; this._map = map;
}, },

View File

@ -4,247 +4,233 @@
* Various utility functions, used by Leaflet internally. * 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 for (j = 1, len = arguments.length; j < len; j++) {
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. src = arguments[j];
extend: function (dest) { for (i in src) {
var i, j, len, 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; // @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
// fallback for IE 7-8 export var create = Object.create || (function () {
function timeoutDefer(fn) { function F() {}
var time = +new Date(), return function (proto) {
timeToCall = Math.max(0, 16 - (time - lastTime)); F.prototype = proto;
return new F();
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);
}
}; };
})(); })();
// shortcuts for most used utility functions // @function bind(fn: Function, …): Function
L.extend = L.Util.extend; // 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).
L.bind = L.Util.bind; // Has a `L.bind()` shortcut.
L.stamp = L.Util.stamp; export function bind(fn, obj) {
L.setOptions = L.Util.setOptions; 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);
}
}

View File

@ -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. * 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', // inspired by Zepto touch code by Thomas Fuchs
_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', export function addDoubleTapListener(obj, handler, id) {
var last, touch,
doubleTap = false,
delay = 250;
// inspired by Zepto touch code by Thomas Fuchs function onTouchStart(e) {
addDoubleTapListener: function (obj, handler, id) { var count;
var last, touch,
doubleTap = false,
delay = 250;
function onTouchStart(e) { if (Browser.pointer) {
var count; if ((!Browser.edge) || e.pointerType === 'mouse') { return; }
count = _pointersCount;
if (L.Browser.pointer) { } else {
if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; } count = e.touches.length;
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;
} }
function onTouchEnd(e) { if (count > 1) { return; }
if (doubleTap && !touch.cancelBubble) {
if (L.Browser.pointer) {
if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
// work around .type being readonly with MSPointer* events var now = Date.now(),
var newTouch = {}, delta = now - (last || now);
prop, i;
for (i in touch) { touch = e.touches ? e.touches[0] : e;
prop = touch[i]; doubleTap = (delta > 0 && delta <= delay);
newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop; last = now;
}
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;
} }
});
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;
}

View File

@ -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. * 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', var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove',
POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup',
POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'], TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'],
_pointers: {}, _pointers = {},
_pointersCount: 0, _pointerDocListener = false;
// Provides a touch events wrapper for (ms)pointer events. // DomEvent.DoubleTap needs to know about this
// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 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') { export function addPointerListener(obj, type, handler, id) {
this._addPointerStart(obj, handler, id);
} else if (type === 'touchmove') { if (type === 'touchstart') {
this._addPointerMove(obj, handler, id); _addPointerStart(obj, handler, id);
} else if (type === 'touchend') { } else if (type === 'touchmove') {
this._addPointerEnd(obj, handler, id); _addPointerMove(obj, handler, id);
}
return this; } else if (type === 'touchend') {
}, _addPointerEnd(obj, handler, id);
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);
} }
});
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);
}

View File

@ -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 * @namespace DomEvent
* Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. * 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. // 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'; 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 if (obj[eventsKey] && obj[eventsKey][id]) { return 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 var handler = function (e) {
// @function on(el: HTMLElement, eventMap: Object, context?: Object): this return fn.call(context || obj, e || window.event);
// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` };
on: function (obj, types, fn, context) {
if (typeof types === 'object') { var originalHandler = handler;
for (var type in types) {
this._on(obj, type, types[type], fn);
}
} else {
types = L.Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) { if (Browser.pointer && type.indexOf('touch') === 0) {
this._on(obj, types[i], fn, context); // 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 } else if ('addEventListener' in obj) {
// 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 if (type === 'mousewheel') {
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
off: function (obj, types, fn, context) {
if (typeof types === 'object') { } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
for (var type in types) { handler = function (e) {
this._off(obj, type, types[type], fn); e = e || window.event;
} if (isExternalTarget(obj, e)) {
} else { originalHandler(e);
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);
};
} }
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 { } else {
e.cancelBubble = true; if (type === 'click' && Browser.android) {
} handler = function (e) {
L.DomEvent._skipped(e); filterClick(e, originalHandler);
};
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 `<form>` 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;
} }
} catch (err) { obj.addEventListener(type, handler, false);
return false;
} }
return (related !== el);
},
// this is a horrible workaround for a bug in Android where a single touch triggers two click events } else if ('attachEvent' in obj) {
_filterClick: function (e, handler) { obj.attachEvent('on' + type, 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);
} }
};
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 `<form>` 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 // @function addListener(…): this
// Alias to [`L.DomEvent.on`](#domevent-on) // Alias to [`L.DomEvent.on`](#domevent-on)
L.DomEvent.addListener = L.DomEvent.on; export {on as addListener};
// @function removeListener(…): this // @function removeListener(…): this
// Alias to [`L.DomEvent.off`](#domevent-off) // Alias to [`L.DomEvent.off`](#domevent-off)
L.DomEvent.removeListener = L.DomEvent.off; export {off as removeListener};

View File

@ -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 * @namespace DomUtil
* *
@ -9,312 +14,301 @@
* in HTML and SVG classes in SVG. * in HTML and SVG classes in SVG.
*/ */
L.DomUtil = {
// @function get(id: String|HTMLElement): HTMLElement // @property TRANSFORM: String
// Returns an element given its DOM id, or returns the element itself // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
// if it was passed directly. export var TRANSFORM = testProp(
get: function (id) { ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
return typeof id === 'string' ? document.getElementById(id) : id;
},
// @function getStyle(el: HTMLElement, styleAttrib: String): String // webkitTransition comes first because some browser versions that drop vendor prefix don't do
// Returns the value for a certain style attribute on an element, // the same for the transitionend event, in particular the Android 4.1 stock browser
// including computed values or values set through CSS.
getStyle: function (el, style) {
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) { export var TRANSITION_END =
var css = document.defaultView.getComputedStyle(el, null); TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
value = css ? css[style] : null;
}
return value === 'auto' ? null : value;
},
// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement // @function get(id: String|HTMLElement): HTMLElement
// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element. // Returns an element given its DOM id, or returns the element itself
create: function (tagName, className, container) { // if it was passed directly.
export function get(id) {
return typeof id === 'string' ? document.getElementById(id) : id;
}
var el = document.createElement(tagName); // @function getStyle(el: HTMLElement, styleAttrib: String): String
el.className = className || ''; // 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) { if ((!value || value === 'auto') && document.defaultView) {
container.appendChild(el); var css = document.defaultView.getComputedStyle(el, null);
} value = css ? css[style] : null;
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);
} }
}; 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 () { if (container) {
// prefix style property names container.appendChild(el);
}
return el;
}
// @property TRANSFORM: String // @function remove(el: HTMLElement)
// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit). // Removes `el` from its parent element
L.DomUtil.TRANSFORM = L.DomUtil.testProp( export function remove(el) {
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 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 // @function toFront(el: HTMLElement)
// the same for the transitionend event, in particular the Android 4.1 stock browser // 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 // @function toBack(el: HTMLElement)
// Vendor-prefixed transform style name. // Makes `el` the first children of its parent, so it renders back from the other children.
var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp( export function toBack(el) {
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); var parent = el.parentNode;
parent.insertBefore(el, parent.firstChild);
}
L.DomUtil.TRANSITION_END = // @function hasClass(el: HTMLElement, name: String): Boolean
transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend'; // 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() // @function addClass(el: HTMLElement, name: String)
// Prevents the user from generating `selectstart` DOM events, usually generated // Adds `name` to the element's class attribute.
// when the user drags the mouse through a page with text. Used internally export function addClass(el, name) {
// by Leaflet to override the behaviour of any click-and-drag interaction on if (el.classList !== undefined) {
// the map. Affects drag interactions on the whole document. var classes = Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
// @function enableTextSelection() el.classList.add(classes[i]);
// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection). }
if ('onselectstart' in document) { } else if (!hasClass(el, name)) {
L.DomUtil.disableTextSelection = function () { var className = getClass(el);
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); setClass(el, (className ? className + ' ' : '') + name);
}; }
L.DomUtil.enableTextSelection = function () { }
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
};
// @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 { } else {
var userSelectProperty = L.DomUtil.testProp( setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); }
}
L.DomUtil.disableTextSelection = function () { // @function setClass(el: HTMLElement, name: String)
if (userSelectProperty) { // Sets the element's class.
var style = document.documentElement.style; export function setClass(el, name) {
this._userSelect = style[userSelectProperty]; if (el.className.baseVal === undefined) {
style[userSelectProperty] = 'none'; el.className = name;
} } else {
}; // in case of SVG element
L.DomUtil.enableTextSelection = function () { el.className.baseVal = name;
if (userSelectProperty) { }
document.documentElement.style[userSelectProperty] = this._userSelect; }
delete this._userSelect;
} // @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() value = Math.round(value * 100);
// 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);
};
// @function enableImageDrag() if (filter) {
// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection). filter.Enabled = (value !== 100);
L.DomUtil.enableImageDrag = function () { filter.Opacity = value;
L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault); } else {
}; el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
// @function preventOutline(el: HTMLElement) // @function testProp(props: String[]): String|false
// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline) // Goes through the array of style names and returns the first name
// of the element `el` invisible. Used internally by Leaflet to prevent // that is a valid style name for an element. If no such name is found,
// focusable elements from displaying an outline when the user performs a // it returns false. Useful for vendor-prefixed styles like `transform`.
// drag interaction on them. export function testProp(props) {
L.DomUtil.preventOutline = function (element) { var style = document.documentElement.style;
while (element.tabIndex === -1) {
element = element.parentNode; for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
} }
if (!element || !element.style) { return; } }
L.DomUtil.restoreOutline(); return false;
this._outlineElement = element; }
this._outlineStyle = element.style.outline;
element.style.outline = 'none';
L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
};
// @function restoreOutline() // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
// Cancels the effects of a previous [`L.DomUtil.preventOutline`](). // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
L.DomUtil.restoreOutline = function () { // and optionally scaled by `scale`. Does not have an effect if the
if (!this._outlineElement) { return; } // browser doesn't support 3D CSS transforms.
this._outlineElement.style.outline = this._outlineStyle; export function setTransform(el, offset, scale) {
delete this._outlineElement; var pos = offset || new Point(0, 0);
delete this._outlineStyle;
L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this); 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);
}

View File

@ -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 * @class Draggable
* @aka L.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: { options: {
// @option clickTolerance: Number = 3 // @option clickTolerance: Number = 3
@ -23,22 +46,6 @@ L.Draggable = L.Evented.extend({
clickTolerance: 3 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) // @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). // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
initialize: function (element, dragStartTarget, preventOutline) { initialize: function (element, dragStartTarget, preventOutline) {
@ -52,7 +59,7 @@ L.Draggable = L.Evented.extend({
enable: function () { enable: function () {
if (this._enabled) { return; } 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; this._enabled = true;
}, },
@ -68,7 +75,7 @@ L.Draggable = L.Evented.extend({
this.finishDrag(); 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._enabled = false;
this._moved = false; this._moved = false;
@ -84,17 +91,17 @@ L.Draggable = L.Evented.extend({
this._moved = false; 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; } if (_dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
L.Draggable._dragging = this; // Prevent dragging multiple objects at once. _dragging = this; // Prevent dragging multiple objects at once.
if (this._preventOutline) { if (this._preventOutline) {
L.DomUtil.preventOutline(this._element); DomUtil.preventOutline(this._element);
} }
L.DomUtil.disableImageDrag(); DomUtil.disableImageDrag();
L.DomUtil.disableTextSelection(); DomUtil.disableTextSelection();
if (this._moving) { return; } if (this._moving) { return; }
@ -104,11 +111,10 @@ L.Draggable = L.Evented.extend({
var first = e.touches ? e.touches[0] : e; 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 DomEvent.on(document, MOVE[e.type], this._onMove, this);
.on(document, L.Draggable.MOVE[e.type], this._onMove, this) DomEvent.on(document, END[e.type], this._onUp, this);
.on(document, L.Draggable.END[e.type], this._onUp, this);
}, },
_onMove: function (e) { _onMove: function (e) {
@ -125,13 +131,13 @@ L.Draggable = L.Evented.extend({
} }
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), 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); offset = newPoint.subtract(this._startPoint);
if (!offset.x && !offset.y) { return; } if (!offset.x && !offset.y) { return; }
if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
L.DomEvent.preventDefault(e); DomEvent.preventDefault(e);
if (!this._moved) { if (!this._moved) {
// @event dragstart: Event // @event dragstart: Event
@ -139,9 +145,9 @@ L.Draggable = L.Evented.extend({
this.fire('dragstart'); this.fire('dragstart');
this._moved = true; 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; this._lastTarget = e.target || e.srcElement;
// IE and Edge do not give the <use> element, so fetch it // IE and Edge do not give the <use> element, so fetch it
@ -149,15 +155,15 @@ L.Draggable = L.Evented.extend({
if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
this._lastTarget = this._lastTarget.correspondingUseElement; 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._newPos = this._startPos.add(offset);
this._moving = true; this._moving = true;
L.Util.cancelAnimFrame(this._animRequest); Util.cancelAnimFrame(this._animRequest);
this._lastEvent = e; this._lastEvent = e;
this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true); this._animRequest = Util.requestAnimFrame(this._updatePosition, this, true);
}, },
_updatePosition: function () { _updatePosition: function () {
@ -167,7 +173,7 @@ L.Draggable = L.Evented.extend({
// Fired continuously during dragging *before* each corresponding // Fired continuously during dragging *before* each corresponding
// update of the element's position. // update of the element's position.
this.fire('predrag', e); this.fire('predrag', e);
L.DomUtil.setPosition(this._element, this._newPos); DomUtil.setPosition(this._element, this._newPos);
// @event drag: Event // @event drag: Event
// Fired continuously during dragging. // Fired continuously during dragging.
@ -185,25 +191,24 @@ L.Draggable = L.Evented.extend({
}, },
finishDrag: function () { finishDrag: function () {
L.DomUtil.removeClass(document.body, 'leaflet-dragging'); DomUtil.removeClass(document.body, 'leaflet-dragging');
if (this._lastTarget) { if (this._lastTarget) {
L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target'); DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
this._lastTarget = null; this._lastTarget = null;
} }
for (var i in L.Draggable.MOVE) { for (var i in MOVE) {
L.DomEvent DomEvent.off(document, MOVE[i], this._onMove, this);
.off(document, L.Draggable.MOVE[i], this._onMove, this) DomEvent.off(document, END[i], this._onUp, this);
.off(document, L.Draggable.END[i], this._onUp, this);
} }
L.DomUtil.enableImageDrag(); DomUtil.enableImageDrag();
L.DomUtil.enableTextSelection(); DomUtil.enableTextSelection();
if (this._moved && this._moving) { if (this._moved && this._moving) {
// ensure drag is not fired after dragend // ensure drag is not fired after dragend
L.Util.cancelAnimFrame(this._animRequest); Util.cancelAnimFrame(this._animRequest);
// @event dragend: DragEndEvent // @event dragend: DragEndEvent
// Fired when the drag ends. // Fired when the drag ends.
@ -213,7 +218,7 @@ L.Draggable = L.Evented.extend({
} }
this._moving = false; this._moving = false;
L.Draggable._dragging = false; _dragging = false;
} }
}); });

View File

@ -1,3 +1,8 @@
import * as Util from '../core/Util';
import {Evented} from '../core/Events';
import * as DomUtil from '../dom/DomUtil';
/* /*
* @class PosAnimation * @class PosAnimation
* @aka L.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) // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
// Run an animation of a given element to a new position, optionally setting // 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._duration = duration || 0.25;
this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 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._offset = newPos.subtract(this._startPos);
this._startTime = +new Date(); this._startTime = +new Date();
@ -52,7 +57,7 @@ L.PosAnimation = L.Evented.extend({
_animate: function () { _animate: function () {
// animation loop // animation loop
this._animId = L.Util.requestAnimFrame(this._animate, this); this._animId = Util.requestAnimFrame(this._animate, this);
this._step(); this._step();
}, },
@ -73,7 +78,7 @@ L.PosAnimation = L.Evented.extend({
if (round) { if (round) {
pos._round(); pos._round();
} }
L.DomUtil.setPosition(this._el, pos); DomUtil.setPosition(this._el, pos);
// @event step: Event // @event step: Event
// Fired continuously during the animation. // Fired continuously during the animation.
@ -81,7 +86,7 @@ L.PosAnimation = L.Evented.extend({
}, },
_complete: function () { _complete: function () {
L.Util.cancelAnimFrame(this._animId); Util.cancelAnimFrame(this._animId);
this._inProgress = false; this._inProgress = false;
// @event end: Event // @event end: Event

View File

@ -1,3 +1,7 @@
import * as Util from '../core/Util';
import {Earth} from './crs/CRS.Earth';
import {toLatLngBounds} from './LatLngBounds';
/* @class LatLng /* @class LatLng
* @aka L.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)) { if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
} }
@ -37,15 +41,15 @@ L.LatLng = function (lat, lng, alt) {
if (alt !== undefined) { if (alt !== undefined) {
this.alt = +alt; this.alt = +alt;
} }
}; }
L.LatLng.prototype = { LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean // @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. // 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) { equals: function (obj, maxMargin) {
if (!obj) { return false; } if (!obj) { return false; }
obj = L.latLng(obj); obj = toLatLng(obj);
var margin = Math.max( var margin = Math.max(
Math.abs(this.lat - obj.lat), Math.abs(this.lat - obj.lat),
@ -58,20 +62,20 @@ L.LatLng.prototype = {
// Returns a string representation of the point (for debugging purposes). // Returns a string representation of the point (for debugging purposes).
toString: function (precision) { toString: function (precision) {
return 'LatLng(' + return 'LatLng(' +
L.Util.formatNum(this.lat, precision) + ', ' + Util.formatNum(this.lat, precision) + ', ' +
L.Util.formatNum(this.lng, precision) + ')'; Util.formatNum(this.lng, precision) + ')';
}, },
// @method distanceTo(otherLatLng: LatLng): Number // @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). // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
distanceTo: function (other) { distanceTo: function (other) {
return L.CRS.Earth.distance(this, L.latLng(other)); return Earth.distance(this, toLatLng(other));
}, },
// @method wrap(): LatLng // @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap: function () { wrap: function () {
return L.CRS.Earth.wrapLatLng(this); return Earth.wrapLatLng(this);
}, },
// @method toBounds(sizeInMeters: Number): LatLngBounds // @method toBounds(sizeInMeters: Number): LatLngBounds
@ -80,13 +84,13 @@ L.LatLng.prototype = {
var latAccuracy = 180 * sizeInMeters / 40075017, var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 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],
[this.lat + latAccuracy, this.lng + lngAccuracy]); [this.lat + latAccuracy, this.lng + lngAccuracy]);
}, },
clone: function () { 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 // @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. // 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) { export function toLatLng(a, b, c) {
if (a instanceof L.LatLng) { if (a instanceof LatLng) {
return a; return a;
} }
if (L.Util.isArray(a) && typeof a[0] !== 'object') { if (Util.isArray(a) && typeof a[0] !== 'object') {
if (a.length === 3) { 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) { if (a.length === 2) {
return new L.LatLng(a[0], a[1]); return new LatLng(a[0], a[1]);
} }
return null; return null;
} }
@ -120,10 +124,10 @@ L.latLng = function (a, b, c) {
return a; return a;
} }
if (typeof a === 'object' && 'lat' in 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) { if (b === undefined) {
return null; return null;
} }
return new L.LatLng(a, b, c); return new LatLng(a, b, c);
}; }

View File

@ -1,3 +1,5 @@
import {LatLng, toLatLng} from './LatLng';
/* /*
* @class LatLngBounds * @class LatLngBounds
* @aka L.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. * 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; } if (!corner1) { return; }
var latlngs = corner2 ? [corner1, corner2] : corner1; 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++) { for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]); this.extend(latlngs[i]);
} }
}; }
L.LatLngBounds.prototype = { LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this // @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point // Extend the bounds to contain the given point
@ -47,23 +49,23 @@ L.LatLngBounds.prototype = {
ne = this._northEast, ne = this._northEast,
sw2, ne2; sw2, ne2;
if (obj instanceof L.LatLng) { if (obj instanceof LatLng) {
sw2 = obj; sw2 = obj;
ne2 = obj; ne2 = obj;
} else if (obj instanceof L.LatLngBounds) { } else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest; sw2 = obj._southWest;
ne2 = obj._northEast; ne2 = obj._northEast;
if (!sw2 || !ne2) { return this; } if (!sw2 || !ne2) { return this; }
} else { } else {
return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this; return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
} }
if (!sw && !ne) { if (!sw && !ne) {
this._southWest = new L.LatLng(sw2.lat, sw2.lng); this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new L.LatLng(ne2.lat, ne2.lng); this._northEast = new LatLng(ne2.lat, ne2.lng);
} else { } else {
sw.lat = Math.min(sw2.lat, sw.lat); sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng); sw.lng = Math.min(sw2.lng, sw.lng);
@ -82,15 +84,15 @@ L.LatLngBounds.prototype = {
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new L.LatLngBounds( return new LatLngBounds(
new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
}, },
// @method getCenter(): LatLng // @method getCenter(): LatLng
// Returns the center point of the bounds. // Returns the center point of the bounds.
getCenter: function () { getCenter: function () {
return new L.LatLng( return new LatLng(
(this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2); (this._southWest.lng + this._northEast.lng) / 2);
}, },
@ -110,13 +112,13 @@ L.LatLngBounds.prototype = {
// @method getNorthWest(): LatLng // @method getNorthWest(): LatLng
// Returns the north-west point of the bounds. // Returns the north-west point of the bounds.
getNorthWest: function () { getNorthWest: function () {
return new L.LatLng(this.getNorth(), this.getWest()); return new LatLng(this.getNorth(), this.getWest());
}, },
// @method getSouthEast(): LatLng // @method getSouthEast(): LatLng
// Returns the south-east point of the bounds. // Returns the south-east point of the bounds.
getSouthEast: function () { getSouthEast: function () {
return new L.LatLng(this.getSouth(), this.getEast()); return new LatLng(this.getSouth(), this.getEast());
}, },
// @method getWest(): Number // @method getWest(): Number
@ -150,17 +152,17 @@ L.LatLngBounds.prototype = {
// @method contains (latlng: LatLng): Boolean // @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point. // Returns `true` if the rectangle contains the given point.
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) { if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = L.latLng(obj); obj = toLatLng(obj);
} else { } else {
obj = L.latLngBounds(obj); obj = toLatLngBounds(obj);
} }
var sw = this._southWest, var sw = this._southWest,
ne = this._northEast, ne = this._northEast,
sw2, ne2; sw2, ne2;
if (obj instanceof L.LatLngBounds) { if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest(); sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast(); ne2 = obj.getNorthEast();
} else { } else {
@ -174,7 +176,7 @@ L.LatLngBounds.prototype = {
// @method intersects(otherBounds: LatLngBounds): Boolean // @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. // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects: function (bounds) { intersects: function (bounds) {
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
var sw = this._southWest, var sw = this._southWest,
ne = this._northEast, ne = this._northEast,
@ -190,7 +192,7 @@ L.LatLngBounds.prototype = {
// @method overlaps(otherBounds: Bounds): Boolean // @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps: function (bounds) { overlaps: function (bounds) {
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
var sw = this._southWest, var sw = this._southWest,
ne = this._northEast, ne = this._northEast,
@ -214,7 +216,7 @@ L.LatLngBounds.prototype = {
equals: function (bounds) { equals: function (bounds) {
if (!bounds) { return false; } if (!bounds) { return false; }
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest()) && return this._southWest.equals(bounds.getSouthWest()) &&
this._northEast.equals(bounds.getNorthEast()); this._northEast.equals(bounds.getNorthEast());
@ -235,9 +237,9 @@ L.LatLngBounds.prototype = {
// @alternative // @alternative
// @factory L.latLngBounds(latlngs: LatLng[]) // @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). // 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) { export function toLatLngBounds(a, b) {
if (a instanceof L.LatLngBounds) { if (a instanceof LatLngBounds) {
return a; return a;
} }
return new L.LatLngBounds(a, b); return new LatLngBounds(a, b);
}; }

View File

@ -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 * @namespace CRS
* @crs L.CRS.EPSG3395 * @crs L.CRS.EPSG3395
* *
* Rarely used by some commercial tile providers. Uses Elliptical Mercator projection. * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
*/ */
export var EPSG3395 = Util.extend({}, Earth, {
L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
code: 'EPSG:3395', code: 'EPSG:3395',
projection: L.Projection.Mercator, projection: Mercator,
transformation: (function () { transformation: (function () {
var scale = 0.5 / (Math.PI * L.Projection.Mercator.R); var scale = 0.5 / (Math.PI * Mercator.R);
return L.transformation(scale, 0.5, -scale, 0.5); return toTransformation(scale, 0.5, -scale, 0.5);
}()) }())
}); });

View File

@ -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 * @namespace CRS
* @crs L.CRS.EPSG3857 * @crs L.CRS.EPSG3857
@ -7,16 +12,16 @@
* Map's `crs` option. * Map's `crs` option.
*/ */
L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, { export var EPSG3857 = Util.extend({}, Earth, {
code: 'EPSG:3857', code: 'EPSG:3857',
projection: L.Projection.SphericalMercator, projection: SphericalMercator,
transformation: (function () { transformation: (function () {
var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R); var scale = 0.5 / (Math.PI * SphericalMercator.R);
return L.transformation(scale, 0.5, -scale, 0.5); 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' code: 'EPSG:900913'
}); });

View File

@ -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 * @namespace CRS
* @crs L.CRS.EPSG4326 * @crs L.CRS.EPSG4326
@ -11,8 +16,8 @@
* or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set. * 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', code: 'EPSG:4326',
projection: L.Projection.LonLat, projection: LonLat,
transformation: L.transformation(1 / 180, 1, -1 / 180, 0.5) transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
}); });

View File

@ -1,3 +1,6 @@
import {CRS} from './CRS';
import * as Util from '../../core/Util';
/* /*
* @namespace CRS * @namespace CRS
* @crs L.CRS.Earth * @crs L.CRS.Earth
@ -8,7 +11,7 @@
* meters. * meters.
*/ */
L.CRS.Earth = L.extend({}, L.CRS, { export var Earth = Util.extend({}, CRS, {
wrapLng: [-180, 180], wrapLng: [-180, 180],
// Mean Earth Radius, as recommended for use by // Mean Earth Radius, as recommended for use by

View File

@ -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 * @namespace CRS
* @crs L.CRS.Simple * @crs L.CRS.Simple
@ -8,9 +13,9 @@
* simple euclidean distance. * simple euclidean distance.
*/ */
L.CRS.Simple = L.extend({}, L.CRS, { export var Simple = Util.extend({}, CRS, {
projection: L.Projection.LonLat, projection: LonLat,
transformation: L.transformation(1, 0, -1, 0), transformation: toTransformation(1, 0, -1, 0),
scale: function (zoom) { scale: function (zoom) {
return Math.pow(2, zoom); return Math.pow(2, zoom);

View File

@ -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 * @namespace CRS
* @aka L.CRS * @crs L.CRS.Base
* Abstract class that defines coordinate reference systems for projecting * Object that defines coordinate reference systems for projecting
* geographical points into pixel (screen) coordinates and back (and to * 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 * 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). * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
@ -11,7 +17,7 @@
* [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
*/ */
L.CRS = { export var CRS = {
// @method latLngToPoint(latlng: LatLng, zoom: Number): Point // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
// Projects geographical coordinates into pixel coordinates for a given zoom. // Projects geographical coordinates into pixel coordinates for a given zoom.
latLngToPoint: function (latlng, zoom) { latLngToPoint: function (latlng, zoom) {
@ -70,7 +76,7 @@ L.CRS = {
min = this.transformation.transform(b.min, s), min = this.transformation.transform(b.min, s),
max = this.transformation.transform(b.max, 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 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
@ -97,13 +103,12 @@ L.CRS = {
// @method wrapLatLng(latlng: LatLng): LatLng // @method wrapLatLng(latlng: LatLng): LatLng
// Returns a `LatLng` where lat and lng has been wrapped according to the // 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. // 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) { wrapLatLng: function (latlng) {
var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, var lng = this.wrapLng ? Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, lat = this.wrapLat ? Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
alt = latlng.alt; alt = latlng.alt;
return L.latLng(lat, lng, alt); return new LatLng(lat, lng, alt);
}, },
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
@ -122,9 +127,9 @@ L.CRS = {
var sw = bounds.getSouthWest(), var sw = bounds.getSouthWest(),
ne = bounds.getNorthEast(), ne = bounds.getNorthEast(),
newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}), newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift}); newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
return new L.LatLngBounds(newSw, newNe); return new LatLngBounds(newSw, newNe);
} }
}; };

View File

@ -1,3 +1,7 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/* /*
* @namespace Projection * @namespace Projection
* @section * @section
@ -11,16 +15,14 @@
* `EPSG:3395` and `Simple` CRS. * `EPSG:3395` and `Simple` CRS.
*/ */
L.Projection = {}; export var LonLat = {
L.Projection.LonLat = {
project: function (latlng) { project: function (latlng) {
return new L.Point(latlng.lng, latlng.lat); return new Point(latlng.lng, latlng.lat);
}, },
unproject: function (point) { 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])
}; };

View File

@ -1,3 +1,7 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/* /*
* @namespace Projection * @namespace Projection
* @projection L.Projection.Mercator * @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. * 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: 6378137,
R_MINOR: 6356752.314245179, 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) { project: function (latlng) {
var d = Math.PI / 180, 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); 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)); 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) { unproject: function (point) {
@ -40,6 +44,6 @@ L.Projection.Mercator = {
phi += dphi; phi += dphi;
} }
return new L.LatLng(phi * d, point.x * d / r); return new LatLng(phi * d, point.x * d / r);
} }
}; };

View File

@ -1,3 +1,7 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/* /*
* @namespace Projection * @namespace Projection
* @projection L.Projection.SphericalMercator * @projection L.Projection.SphericalMercator
@ -7,7 +11,7 @@
* a sphere. Used by the `EPSG:3857` CRS. * a sphere. Used by the `EPSG:3857` CRS.
*/ */
L.Projection.SphericalMercator = { export var SphericalMercator = {
R: 6378137, R: 6378137,
MAX_LATITUDE: 85.0511287798, MAX_LATITUDE: 85.0511287798,
@ -18,7 +22,7 @@ L.Projection.SphericalMercator = {
lat = Math.max(Math.min(max, latlng.lat), -max), lat = Math.max(Math.min(max, latlng.lat), -max),
sin = Math.sin(lat * d); sin = Math.sin(lat * d);
return new L.Point( return new Point(
this.R * latlng.lng * d, this.R * latlng.lng * d,
this.R * Math.log((1 + sin) / (1 - sin)) / 2); this.R * Math.log((1 + sin) / (1 - sin)) / 2);
}, },
@ -26,13 +30,13 @@ L.Projection.SphericalMercator = {
unproject: function (point) { unproject: function (point) {
var d = 180 / Math.PI; 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, (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
point.x * d / this.R); point.x * d / this.R);
}, },
bounds: (function () { bounds: (function () {
var d = 6378137 * Math.PI; var d = 6378137 * Math.PI;
return L.bounds([-d, -d], [d, d]); return new Bounds([-d, -d], [d, d]);
})() })()
}; };

View File

@ -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';

View File

@ -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.

View File

@ -1,3 +1,5 @@
import {Point, toPoint} from './Point';
/* /*
* @class Bounds * @class Bounds
* @aka L.Bounds * @aka L.Bounds
@ -19,7 +21,7 @@
* ``` * ```
*/ */
L.Bounds = function (a, b) { export function Bounds(a, b) {
if (!a) { return; } if (!a) { return; }
var points = b ? [a, b] : a; 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++) { for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]); this.extend(points[i]);
} }
}; }
L.Bounds.prototype = { Bounds.prototype = {
// @method extend(point: Point): this // @method extend(point: Point): this
// Extends the bounds to contain the given point. // Extends the bounds to contain the given point.
extend: function (point) { // (Point) extend: function (point) { // (Point)
point = L.point(point); point = toPoint(point);
// @property min: Point // @property min: Point
// The top left corner of the rectangle. // The top left corner of the rectangle.
@ -54,7 +56,7 @@ L.Bounds.prototype = {
// @method getCenter(round?: Boolean): Point // @method getCenter(round?: Boolean): Point
// Returns the center point of the bounds. // Returns the center point of the bounds.
getCenter: function (round) { getCenter: function (round) {
return new L.Point( return new Point(
(this.min.x + this.max.x) / 2, (this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round); (this.min.y + this.max.y) / 2, round);
}, },
@ -62,13 +64,13 @@ L.Bounds.prototype = {
// @method getBottomLeft(): Point // @method getBottomLeft(): Point
// Returns the bottom-left point of the bounds. // Returns the bottom-left point of the bounds.
getBottomLeft: function () { getBottomLeft: function () {
return new L.Point(this.min.x, this.max.y); return new Point(this.min.x, this.max.y);
}, },
// @method getTopRight(): Point // @method getTopRight(): Point
// Returns the top-right point of the bounds. // Returns the top-right point of the bounds.
getTopRight: function () { // -> Point 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 // @method getSize(): Point
@ -85,13 +87,13 @@ L.Bounds.prototype = {
contains: function (obj) { contains: function (obj) {
var min, max; var min, max;
if (typeof obj[0] === 'number' || obj instanceof L.Point) { if (typeof obj[0] === 'number' || obj instanceof Point) {
obj = L.point(obj); obj = toPoint(obj);
} else { } else {
obj = L.bounds(obj); obj = toBounds(obj);
} }
if (obj instanceof L.Bounds) { if (obj instanceof Bounds) {
min = obj.min; min = obj.min;
max = obj.max; max = obj.max;
} else { } else {
@ -108,7 +110,7 @@ L.Bounds.prototype = {
// Returns `true` if the rectangle intersects the given bounds. Two bounds // Returns `true` if the rectangle intersects the given bounds. Two bounds
// intersect if they have at least one point in common. // intersect if they have at least one point in common.
intersects: function (bounds) { // (Bounds) -> Boolean intersects: function (bounds) { // (Bounds) -> Boolean
bounds = L.bounds(bounds); bounds = toBounds(bounds);
var min = this.min, var min = this.min,
max = this.max, max = this.max,
@ -124,7 +126,7 @@ L.Bounds.prototype = {
// Returns `true` if the rectangle overlaps the given bounds. Two bounds // Returns `true` if the rectangle overlaps the given bounds. Two bounds
// overlap if their intersection is an area. // overlap if their intersection is an area.
overlaps: function (bounds) { // (Bounds) -> Boolean overlaps: function (bounds) { // (Bounds) -> Boolean
bounds = L.bounds(bounds); bounds = toBounds(bounds);
var min = this.min, var min = this.min,
max = this.max, max = this.max,
@ -147,9 +149,9 @@ L.Bounds.prototype = {
// @alternative // @alternative
// @factory L.bounds(points: Point[]) // @factory L.bounds(points: Point[])
// Creates a Bounds object from the points it contains // Creates a Bounds object from the points it contains
L.bounds = function (a, b) { export function toBounds(a, b) {
if (!a || a instanceof L.Bounds) { if (!a || a instanceof Bounds) {
return a; return a;
} }
return new L.Bounds(a, b); return new Bounds(a, b);
}; }

View File

@ -1,228 +1,236 @@
import {Point} from './Point';
import * as Util from '../core/Util';
/* /*
* @namespace LineUtil * @namespace LineUtil
* *
* Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast. * 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. // @function simplify(points: Point[], tolerance: Number): Point[]
// Improves rendering performance dramatically by lessening the number of points to draw. // Dramatically reduces the number of points in a polyline while retaining
// its shape and returns a new array of simplified points, using the
// @function simplify(points: Point[], tolerance: Number): Point[] // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
// Dramatically reduces the number of points in a polyline while retaining // Used for a huge performance boost when processing/displaying Leaflet polylines for
// its shape and returns a new array of simplified points, using the // each zoom level and also reducing visual noise. tolerance affects the amount of
// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm). // simplification (lesser value means higher quality but slower and with more points).
// Used for a huge performance boost when processing/displaying Leaflet polylines for // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
// each zoom level and also reducing visual noise. tolerance affects the amount of export function simplify(points, tolerance) {
// simplification (lesser value means higher quality but slower and with more points). if (!tolerance || !points.length) {
// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/). return points.slice();
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);
} }
};
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');
}

View File

@ -1,3 +1,5 @@
import {isArray, formatNum} from '../core/Util';
/* /*
* @class Point * @class Point
* @aka L.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 // @property x: Number; The `x` coordinate of the point
this.x = (round ? Math.round(x) : x); this.x = (round ? Math.round(x) : x);
// @property y: Number; The `y` coordinate of the point // @property y: Number; The `y` coordinate of the point
this.y = (round ? Math.round(y) : y); this.y = (round ? Math.round(y) : y);
}; }
L.Point.prototype = { Point.prototype = {
// @method clone(): Point // @method clone(): Point
// Returns a copy of the current point. // Returns a copy of the current point.
clone: function () { clone: function () {
return new L.Point(this.x, this.y); return new Point(this.x, this.y);
}, },
// @method add(otherPoint: Point): Point // @method add(otherPoint: Point): Point
// Returns the result of addition of the current and the given points. // Returns the result of addition of the current and the given points.
add: function (point) { add: function (point) {
// non-destructive, returns a new point // non-destructive, returns a new point
return this.clone()._add(L.point(point)); return this.clone()._add(toPoint(point));
}, },
_add: function (point) { _add: function (point) {
@ -50,7 +52,7 @@ L.Point.prototype = {
// @method subtract(otherPoint: Point): Point // @method subtract(otherPoint: Point): Point
// Returns the result of subtraction of the given point from the current. // Returns the result of subtraction of the given point from the current.
subtract: function (point) { subtract: function (point) {
return this.clone()._subtract(L.point(point)); return this.clone()._subtract(toPoint(point));
}, },
_subtract: function (point) { _subtract: function (point) {
@ -89,14 +91,14 @@ L.Point.prototype = {
// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
// defined by `scale`. // defined by `scale`.
scaleBy: function (point) { 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 // @method unscaleBy(scale: Point): Point
// Inverse of `scaleBy`. Divide each coordinate of the current point by // Inverse of `scaleBy`. Divide each coordinate of the current point by
// each coordinate of `scale`. // each coordinate of `scale`.
unscaleBy: function (point) { 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 // @method round(): Point
@ -138,7 +140,7 @@ L.Point.prototype = {
// @method distanceTo(otherPoint: Point): Number // @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points. // Returns the cartesian distance between the current and the given points.
distanceTo: function (point) { distanceTo: function (point) {
point = L.point(point); point = toPoint(point);
var x = point.x - this.x, var x = point.x - this.x,
y = point.y - this.y; y = point.y - this.y;
@ -149,7 +151,7 @@ L.Point.prototype = {
// @method equals(otherPoint: Point): Boolean // @method equals(otherPoint: Point): Boolean
// Returns `true` if the given point has the same coordinates. // Returns `true` if the given point has the same coordinates.
equals: function (point) { equals: function (point) {
point = L.point(point); point = toPoint(point);
return point.x === this.x && return point.x === this.x &&
point.y === this.y; point.y === this.y;
@ -158,7 +160,7 @@ L.Point.prototype = {
// @method contains(otherPoint: Point): Boolean // @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). // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
contains: function (point) { contains: function (point) {
point = L.point(point); point = toPoint(point);
return Math.abs(point.x) <= Math.abs(this.x) && return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y); 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. // Returns a string representation of the point for debugging purposes.
toString: function () { toString: function () {
return 'Point(' + return 'Point(' +
L.Util.formatNum(this.x) + ', ' + formatNum(this.x) + ', ' +
L.Util.formatNum(this.y) + ')'; formatNum(this.y) + ')';
} }
}; };
@ -183,18 +185,18 @@ L.Point.prototype = {
// @alternative // @alternative
// @factory L.point(coords: Object) // @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead. // Expects a plain object of the form `{x: Number, y: Number}` instead.
L.point = function (x, y, round) { export function toPoint(x, y, round) {
if (x instanceof L.Point) { if (x instanceof Point) {
return x; return x;
} }
if (L.Util.isArray(x)) { if (isArray(x)) {
return new L.Point(x[0], x[1]); return new Point(x[0], x[1]);
} }
if (x === undefined || x === null) { if (x === undefined || x === null) {
return x; return x;
} }
if (typeof x === 'object' && 'x' in x && 'y' in 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);
}; }

View File

@ -1,26 +1,25 @@
import * as LineUtil from './LineUtil';
/* /*
* @namespace PolyUtil * @namespace PolyUtil
* Various utility functions for polygon geometries. * Various utility functions for polygon geometries.
*/ */
L.PolyUtil = {};
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[] /* @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)). * 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 * 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 * performance. Note that polygon points needs different algorithm for clipping
* than polyline, so there's a seperate method for it. * 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, var clippedPoints,
edges = [1, 4, 2, 8], edges = [1, 4, 2, 8],
i, j, k, i, j, k,
a, b, a, b,
len, edge, p, len, edge, p;
lu = L.LineUtil;
for (i = 0, len = points.length; i < len; i++) { 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) // for each edge (left, bottom, right, top)
@ -36,16 +35,16 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) {
if (!(a._code & edge)) { if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen) // if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) { if (b._code & edge) {
p = lu._getEdgeIntersection(b, a, edge, bounds, round); p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = lu._getBitCode(p, bounds); p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p); clippedPoints.push(p);
} }
clippedPoints.push(a); clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen) // else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) { } else if (!(b._code & edge)) {
p = lu._getEdgeIntersection(b, a, edge, bounds, round); p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = lu._getBitCode(p, bounds); p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p); clippedPoints.push(p);
} }
} }
@ -53,4 +52,4 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) {
} }
return points; return points;
}; }

View File

@ -1,3 +1,6 @@
import {Point} from './Point';
import * as Util from '../core/Util';
/* /*
* @class Transformation * @class Transformation
* @aka L.Transformation * @aka L.Transformation
@ -19,8 +22,8 @@
// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
// Creates a `Transformation` object with the given coefficients. // Creates a `Transformation` object with the given coefficients.
L.Transformation = function (a, b, c, d) { export function Transformation(a, b, c, d) {
if (L.Util.isArray(a)) { if (Util.isArray(a)) {
// use array properties // use array properties
this._a = a[0]; this._a = a[0];
this._b = a[1]; this._b = a[1];
@ -32,9 +35,9 @@ L.Transformation = function (a, b, c, d) {
this._b = b; this._b = b;
this._c = c; this._c = c;
this._d = d; this._d = d;
}; }
L.Transformation.prototype = { Transformation.prototype = {
// @method transform(point: Point, scale?: Number): Point // @method transform(point: Point, scale?: Number): Point
// Returns a transformed point, optionally multiplied by the given scale. // Returns a transformed point, optionally multiplied by the given scale.
// Only accepts actual `L.Point` instances, not arrays. // 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. // by the given scale. Only accepts actual `L.Point` instances, not arrays.
untransform: function (point, scale) { untransform: function (point, scale) {
scale = scale || 1; scale = scale || 1;
return new L.Point( return new Point(
(point.x / scale - this._b) / this._a, (point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c); (point.y / scale - this._d) / this._c);
} }
}; };
@ -71,6 +74,6 @@ L.Transformation.prototype = {
// Expects an coeficients array of the form // Expects an coeficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`. // `[a: Number, b: Number, c: Number, d: Number]`.
L.transformation = function (a, b, c, d) { export function toTransformation(a, b, c, d) {
return new L.Transformation(a, b, c, d); return new Transformation(a, b, c, d);
}; }

View File

@ -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 * @class DivOverlay
* @inherits Layer * @inherits Layer
@ -6,7 +12,7 @@
*/ */
// @namespace DivOverlay // @namespace DivOverlay
L.DivOverlay = L.Layer.extend({ export var DivOverlay = Layer.extend({
// @section // @section
// @aka DivOverlay options // @aka DivOverlay options
@ -26,7 +32,7 @@ L.DivOverlay = L.Layer.extend({
}, },
initialize: function (options, source) { initialize: function (options, source) {
L.setOptions(this, options); Util.setOptions(this, options);
this._source = source; this._source = source;
}, },
@ -39,7 +45,7 @@ L.DivOverlay = L.Layer.extend({
} }
if (map._fadeAnimated) { if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0); DomUtil.setOpacity(this._container, 0);
} }
clearTimeout(this._removeTimeout); clearTimeout(this._removeTimeout);
@ -47,7 +53,7 @@ L.DivOverlay = L.Layer.extend({
this.update(); this.update();
if (map._fadeAnimated) { if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 1); DomUtil.setOpacity(this._container, 1);
} }
this.bringToFront(); this.bringToFront();
@ -55,10 +61,10 @@ L.DivOverlay = L.Layer.extend({
onRemove: function (map) { onRemove: function (map) {
if (map._fadeAnimated) { if (map._fadeAnimated) {
L.DomUtil.setOpacity(this._container, 0); DomUtil.setOpacity(this._container, 0);
this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200); this._removeTimeout = setTimeout(Util.bind(DomUtil.remove, undefined, this._container), 200);
} else { } else {
L.DomUtil.remove(this._container); DomUtil.remove(this._container);
} }
}, },
@ -72,7 +78,7 @@ L.DivOverlay = L.Layer.extend({
// @method setLatLng(latlng: LatLng): this // @method setLatLng(latlng: LatLng): this
// Sets the geographical point where the popup will open. // Sets the geographical point where the popup will open.
setLatLng: function (latlng) { setLatLng: function (latlng) {
this._latlng = L.latLng(latlng); this._latlng = toLatLng(latlng);
if (this._map) { if (this._map) {
this._updatePosition(); this._updatePosition();
this._adjustPan(); this._adjustPan();
@ -138,7 +144,7 @@ L.DivOverlay = L.Layer.extend({
// Brings this popup in front of other popups (in the same map pane). // Brings this popup in front of other popups (in the same map pane).
bringToFront: function () { bringToFront: function () {
if (this._map) { if (this._map) {
L.DomUtil.toFront(this._container); DomUtil.toFront(this._container);
} }
return this; 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). // Brings this popup to the back of other popups (in the same map pane).
bringToBack: function () { bringToBack: function () {
if (this._map) { if (this._map) {
L.DomUtil.toBack(this._container); DomUtil.toBack(this._container);
} }
return this; return this;
}, },
@ -173,11 +179,11 @@ L.DivOverlay = L.Layer.extend({
if (!this._map) { return; } if (!this._map) { return; }
var pos = this._map.latLngToLayerPoint(this._latlng), var pos = this._map.latLngToLayerPoint(this._latlng),
offset = L.point(this.options.offset), offset = toPoint(this.options.offset),
anchor = this._getAnchor(); anchor = this._getAnchor();
if (this._zoomAnimated) { if (this._zoomAnimated) {
L.DomUtil.setPosition(this._container, pos.add(anchor)); DomUtil.setPosition(this._container, pos.add(anchor));
} else { } else {
offset = offset.add(pos).add(anchor); offset = offset.add(pos).add(anchor);
} }

View File

@ -1,3 +1,6 @@
import {LayerGroup} from './LayerGroup';
import {LatLngBounds} from '../geo/LatLngBounds';
/* /*
* @class FeatureGroup * @class FeatureGroup
* @aka L.FeatureGroup * @aka L.FeatureGroup
@ -20,7 +23,7 @@
* ``` * ```
*/ */
L.FeatureGroup = L.LayerGroup.extend({ export var FeatureGroup = LayerGroup.extend({
addLayer: function (layer) { addLayer: function (layer) {
if (this.hasLayer(layer)) { if (this.hasLayer(layer)) {
@ -29,7 +32,7 @@ L.FeatureGroup = L.LayerGroup.extend({
layer.addEventParent(this); layer.addEventParent(this);
L.LayerGroup.prototype.addLayer.call(this, layer); LayerGroup.prototype.addLayer.call(this, layer);
// @event layeradd: LayerEvent // @event layeradd: LayerEvent
// Fired when a layer is added to this `FeatureGroup` // Fired when a layer is added to this `FeatureGroup`
@ -46,7 +49,7 @@ L.FeatureGroup = L.LayerGroup.extend({
layer.removeEventParent(this); layer.removeEventParent(this);
L.LayerGroup.prototype.removeLayer.call(this, layer); LayerGroup.prototype.removeLayer.call(this, layer);
// @event layerremove: LayerEvent // @event layerremove: LayerEvent
// Fired when a layer is removed from this `FeatureGroup` // Fired when a layer is removed from this `FeatureGroup`
@ -74,7 +77,7 @@ L.FeatureGroup = L.LayerGroup.extend({
// @method getBounds(): LatLngBounds // @method getBounds(): LatLngBounds
// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
getBounds: function () { getBounds: function () {
var bounds = new L.LatLngBounds(); var bounds = new LatLngBounds();
for (var id in this._layers) { for (var id in this._layers) {
var layer = this._layers[id]; var layer = this._layers[id];
@ -86,6 +89,6 @@ L.FeatureGroup = L.LayerGroup.extend({
// @factory L.featureGroup(layers: Layer[]) // @factory L.featureGroup(layers: Layer[])
// Create a feature group, optionally given an initial set of layers. // Create a feature group, optionally given an initial set of layers.
L.featureGroup = function (layers) { export var featureGroup = function (layers) {
return new L.FeatureGroup(layers); return new FeatureGroup(layers);
}; };

View File

@ -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 * @class GeoJSON
* @aka L.GeoJSON * @aka L.GeoJSON
@ -19,7 +31,7 @@
* ``` * ```
*/ */
L.GeoJSON = L.FeatureGroup.extend({ export var GeoJSON = FeatureGroup.extend({
/* @section /* @section
* @aka GeoJSON options * @aka GeoJSON options
@ -69,7 +81,7 @@ L.GeoJSON = L.FeatureGroup.extend({
*/ */
initialize: function (geojson, options) { initialize: function (geojson, options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._layers = {}; this._layers = {};
@ -81,7 +93,7 @@ L.GeoJSON = L.FeatureGroup.extend({
// @method addData( <GeoJSON> data ): this // @method addData( <GeoJSON> data ): this
// Adds a GeoJSON object to the layer. // Adds a GeoJSON object to the layer.
addData: function (geojson) { addData: function (geojson) {
var features = L.Util.isArray(geojson) ? geojson : geojson.features, var features = Util.isArray(geojson) ? geojson : geojson.features,
i, len, feature; i, len, feature;
if (features) { if (features) {
@ -99,11 +111,11 @@ L.GeoJSON = L.FeatureGroup.extend({
if (options.filter && !options.filter(geojson)) { return this; } if (options.filter && !options.filter(geojson)) { return this; }
var layer = L.GeoJSON.geometryToLayer(geojson, options); var layer = geometryToLayer(geojson, options);
if (!layer) { if (!layer) {
return this; return this;
} }
layer.feature = L.GeoJSON.asFeature(geojson); layer.feature = asFeature(geojson);
layer.defaultOptions = layer.options; layer.defaultOptions = layer.options;
this.resetStyle(layer); 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. // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
resetStyle: function (layer) { resetStyle: function (layer) {
// reset any custom styles // reset any custom styles
layer.options = L.Util.extend({}, layer.defaultOptions); layer.options = Util.extend({}, layer.defaultOptions);
this._setLayerStyle(layer, this.options.style); this._setLayerStyle(layer, this.options.style);
return this; return this;
}, },
@ -144,143 +156,142 @@ L.GeoJSON = L.FeatureGroup.extend({
// @section // @section
// There are several static functions which can be called without instantiating L.GeoJSON: // 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, // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
coords = geometry ? geometry.coordinates : null, // Creates a `Layer` from a given GeoJSON feature. Can use a custom
layers = [], // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
pointToLayer = options && options.pointToLayer, // functions if provided as options.
coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng, export function geometryToLayer(geojson, options) {
latlng, latlngs, i, len;
if (!coords && !geometry) { var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
return null; coords = geometry ? geometry.coordinates : null,
} layers = [],
pointToLayer = options && options.pointToLayer,
_coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
latlng, latlngs, i, len;
switch (geometry.type) { if (!coords && !geometry) {
case 'Point': return null;
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
};
} }
});
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 = { var PointToGeoJSON = {
toGeoJSON: function () { toGeoJSON: function () {
return L.GeoJSON.getFeature(this, { return getFeature(this, {
type: 'Point', type: 'Point',
coordinates: L.GeoJSON.latLngToCoords(this.getLatLng()) coordinates: latLngToCoords(this.getLatLng())
}); });
} }
}; };
@ -288,51 +299,55 @@ var PointToGeoJSON = {
// @namespace Marker // @namespace Marker
// @method toGeoJSON(): Object // @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature). // 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 // @namespace CircleMarker
// @method toGeoJSON(): Object // @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature). // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
L.Circle.include(PointToGeoJSON); Circle.include(PointToGeoJSON);
L.CircleMarker.include(PointToGeoJSON); CircleMarker.include(PointToGeoJSON);
// @namespace Polyline // @namespace Polyline
// @method toGeoJSON(): Object // @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature). // 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 () { Polyline.include({
var multi = !L.Polyline._flat(this._latlngs); 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, { return getFeature(this, {
type: (multi ? 'Multi' : '') + 'LineString', type: (multi ? 'Multi' : '') + 'LineString',
coordinates: coords coordinates: coords
}); });
}; }
});
// @namespace Polygon // @namespace Polygon
// @method toGeoJSON(): Object // @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature). // 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 () { Polygon.include({
var holes = !L.Polyline._flat(this._latlngs), toGeoJSON: function () {
multi = holes && !L.Polyline._flat(this._latlngs[0]); 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) { if (!holes) {
coords = [coords]; coords = [coords];
}
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
} }
});
return L.GeoJSON.getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
};
// @namespace LayerGroup // @namespace LayerGroup
L.LayerGroup.include({ LayerGroup.include({
toMultiPoint: function () { toMultiPoint: function () {
var coords = []; var coords = [];
@ -340,7 +355,7 @@ L.LayerGroup.include({
coords.push(layer.toGeoJSON().geometry.coordinates); coords.push(layer.toGeoJSON().geometry.coordinates);
}); });
return L.GeoJSON.getFeature(this, { return getFeature(this, {
type: 'MultiPoint', type: 'MultiPoint',
coordinates: coords coordinates: coords
}); });
@ -362,12 +377,12 @@ L.LayerGroup.include({
this.eachLayer(function (layer) { this.eachLayer(function (layer) {
if (layer.toGeoJSON) { if (layer.toGeoJSON) {
var json = layer.toGeoJSON(); var json = layer.toGeoJSON();
jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); jsons.push(isGeometryCollection ? json.geometry : asFeature(json));
} }
}); });
if (isGeometryCollection) { if (isGeometryCollection) {
return L.GeoJSON.getFeature(this, { return getFeature(this, {
geometries: jsons, geometries: jsons,
type: 'GeometryCollection' type: 'GeometryCollection'
}); });
@ -385,8 +400,9 @@ L.LayerGroup.include({
// Creates a GeoJSON layer. Optionally accepts an object in // Creates a GeoJSON layer. Optionally accepts an object in
// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map // [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. // (you can alternatively add it later with `addData` method) and an `options` object.
L.geoJSON = function (geojson, options) { export function geoJSON(geojson, options) {
return new L.GeoJSON(geojson, options); return new GeoJSON(geojson, options);
}; }
// Backward compatibility. // Backward compatibility.
L.geoJson = L.geoJSON; export var geoJson = geoJSON;

View File

@ -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 * @class ImageOverlay
* @aka L.ImageOverlay * @aka L.ImageOverlay
@ -14,7 +20,7 @@
* ``` * ```
*/ */
L.ImageOverlay = L.Layer.extend({ export var ImageOverlay = Layer.extend({
// @section // @section
// @aka ImageOverlay options // @aka ImageOverlay options
@ -38,9 +44,9 @@ L.ImageOverlay = L.Layer.extend({
initialize: function (url, bounds, options) { // (String, LatLngBounds, Object) initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
this._url = url; this._url = url;
this._bounds = L.latLngBounds(bounds); this._bounds = toLatLngBounds(bounds);
L.setOptions(this, options); Util.setOptions(this, options);
}, },
onAdd: function () { onAdd: function () {
@ -53,7 +59,7 @@ L.ImageOverlay = L.Layer.extend({
} }
if (this.options.interactive) { if (this.options.interactive) {
L.DomUtil.addClass(this._image, 'leaflet-interactive'); DomUtil.addClass(this._image, 'leaflet-interactive');
this.addInteractiveTarget(this._image); this.addInteractiveTarget(this._image);
} }
@ -62,7 +68,7 @@ L.ImageOverlay = L.Layer.extend({
}, },
onRemove: function () { onRemove: function () {
L.DomUtil.remove(this._image); DomUtil.remove(this._image);
if (this.options.interactive) { if (this.options.interactive) {
this.removeInteractiveTarget(this._image); this.removeInteractiveTarget(this._image);
} }
@ -90,7 +96,7 @@ L.ImageOverlay = L.Layer.extend({
// Brings the layer to the top of all overlays. // Brings the layer to the top of all overlays.
bringToFront: function () { bringToFront: function () {
if (this._map) { if (this._map) {
L.DomUtil.toFront(this._image); DomUtil.toFront(this._image);
} }
return this; return this;
}, },
@ -99,7 +105,7 @@ L.ImageOverlay = L.Layer.extend({
// Brings the layer to the bottom of all overlays. // Brings the layer to the bottom of all overlays.
bringToBack: function () { bringToBack: function () {
if (this._map) { if (this._map) {
L.DomUtil.toBack(this._image); DomUtil.toBack(this._image);
} }
return this; return this;
}, },
@ -152,13 +158,13 @@ L.ImageOverlay = L.Layer.extend({
}, },
_initImage: function () { _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' : '')); 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
img.onselectstart = L.Util.falseFn; img.onselectstart = Util.falseFn;
img.onmousemove = L.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) { if (this.options.crossOrigin) {
img.crossOrigin = ''; img.crossOrigin = '';
@ -172,30 +178,30 @@ L.ImageOverlay = L.Layer.extend({
var scale = this._map.getZoomScale(e.zoom), var scale = this._map.getZoomScale(e.zoom),
offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 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 () { _reset: function () {
var image = this._image, var image = this._image,
bounds = new L.Bounds( bounds = new Bounds(
this._map.latLngToLayerPoint(this._bounds.getNorthWest()), this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
this._map.latLngToLayerPoint(this._bounds.getSouthEast())), this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
size = bounds.getSize(); size = bounds.getSize();
L.DomUtil.setPosition(image, bounds.min); DomUtil.setPosition(image, bounds.min);
image.style.width = size.x + 'px'; image.style.width = size.x + 'px';
image.style.height = size.y + 'px'; image.style.height = size.y + 'px';
}, },
_updateOpacity: function () { _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) // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
// Instantiates an image overlay object given the URL of the image and the // Instantiates an image overlay object given the URL of the image and the
// geographical bounds it is tied to. // geographical bounds it is tied to.
L.imageOverlay = function (url, bounds, options) { export var imageOverlay = function (url, bounds, options) {
return new L.ImageOverlay(url, bounds, options); return new ImageOverlay(url, bounds, options);
}; };

View File

@ -1,3 +1,6 @@
import {Evented} from '../core/Events';
import {Map} from '../map/Map';
import * as Util from '../core/Util';
/* /*
* @class Layer * @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: // Classes extending `L.Layer` will inherit the following options:
options: { options: {
@ -71,12 +74,12 @@ L.Layer = L.Evented.extend({
}, },
addInteractiveTarget: function (targetEl) { addInteractiveTarget: function (targetEl) {
this._map._targets[L.stamp(targetEl)] = this; this._map._targets[Util.stamp(targetEl)] = this;
return this; return this;
}, },
removeInteractiveTarget: function (targetEl) { removeInteractiveTarget: function (targetEl) {
delete this._map._targets[L.stamp(targetEl)]; delete this._map._targets[Util.stamp(targetEl)];
return this; return this;
}, },
@ -147,11 +150,11 @@ L.Layer = L.Evented.extend({
* *
* @section Methods for Layers and Controls * @section Methods for Layers and Controls
*/ */
L.Map.include({ Map.include({
// @method addLayer(layer: Layer): this // @method addLayer(layer: Layer): this
// Adds the given layer to the map // Adds the given layer to the map
addLayer: function (layer) { addLayer: function (layer) {
var id = L.stamp(layer); var id = Util.stamp(layer);
if (this._layers[id]) { return this; } if (this._layers[id]) { return this; }
this._layers[id] = layer; this._layers[id] = layer;
@ -169,7 +172,7 @@ L.Map.include({
// @method removeLayer(layer: Layer): this // @method removeLayer(layer: Layer): this
// Removes the given layer from the map. // Removes the given layer from the map.
removeLayer: function (layer) { removeLayer: function (layer) {
var id = L.stamp(layer); var id = Util.stamp(layer);
if (!this._layers[id]) { return this; } if (!this._layers[id]) { return this; }
@ -196,7 +199,7 @@ L.Map.include({
// @method hasLayer(layer: Layer): Boolean // @method hasLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the map // Returns `true` if the given layer is currently added to the map
hasLayer: function (layer) { 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 /* @method eachLayer(fn: Function, context?: Object): this
@ -215,7 +218,7 @@ L.Map.include({
}, },
_addLayers: function (layers) { _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++) { for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]); this.addLayer(layers[i]);
@ -224,13 +227,13 @@ L.Map.include({
_addZoomLimit: function (layer) { _addZoomLimit: function (layer) {
if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
this._zoomBoundLayers[L.stamp(layer)] = layer; this._zoomBoundLayers[Util.stamp(layer)] = layer;
this._updateZoomLevels(); this._updateZoomLevels();
} }
}, },
_removeZoomLimit: function (layer) { _removeZoomLimit: function (layer) {
var id = L.stamp(layer); var id = Util.stamp(layer);
if (this._zoomBoundLayers[id]) { if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id]; delete this._zoomBoundLayers[id];

View File

@ -1,3 +1,7 @@
import {Layer} from './Layer';
import * as Util from '../core/Util';
/* /*
* @class LayerGroup * @class LayerGroup
* @aka L.LayerGroup * @aka L.LayerGroup
@ -16,7 +20,7 @@
* ``` * ```
*/ */
L.LayerGroup = L.Layer.extend({ export var LayerGroup = Layer.extend({
initialize: function (layers) { initialize: function (layers) {
this._layers = {}; this._layers = {};
@ -147,13 +151,13 @@ L.LayerGroup = L.Layer.extend({
// @method getLayerId(layer: Layer): Number // @method getLayerId(layer: Layer): Number
// Returns the internal ID for a layer // Returns the internal ID for a layer
getLayerId: function (layer) { getLayerId: function (layer) {
return L.stamp(layer); return Util.stamp(layer);
} }
}); });
// @factory L.layerGroup(layers: Layer[]) // @factory L.layerGroup(layers: Layer[])
// Create a layer group, optionally given an initial set of layers. // Create a layer group, optionally given an initial set of layers.
L.layerGroup = function (layers) { export var layerGroup = function (layers) {
return new L.LayerGroup(layers); return new LayerGroup(layers);
}; };

View File

@ -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 * @class Popup
* @inherits DivOverlay * @inherits DivOverlay
@ -26,7 +36,7 @@
// @namespace Popup // @namespace Popup
L.Popup = L.DivOverlay.extend({ export var Popup = DivOverlay.extend({
// @section // @section
// @aka Popup options // @aka Popup options
@ -92,7 +102,7 @@ L.Popup = L.DivOverlay.extend({
}, },
onAdd: function (map) { onAdd: function (map) {
L.DivOverlay.prototype.onAdd.call(this, map); DivOverlay.prototype.onAdd.call(this, map);
// @namespace Map // @namespace Map
// @section Popup events // @section Popup events
@ -108,14 +118,14 @@ L.Popup = L.DivOverlay.extend({
this._source.fire('popupopen', {popup: this}, true); this._source.fire('popupopen', {popup: this}, true);
// For non-path layers, we toggle the popup when clicking // For non-path layers, we toggle the popup when clicking
// again the layer, so prevent the map to reopen it. // again the layer, so prevent the map to reopen it.
if (!(this._source instanceof L.Path)) { if (!(this._source instanceof Path)) {
this._source.on('preclick', L.DomEvent.stopPropagation); this._source.on('preclick', DomEvent.stopPropagation);
} }
} }
}, },
onRemove: function (map) { onRemove: function (map) {
L.DivOverlay.prototype.onRemove.call(this, map); DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map // @namespace Map
// @section Popup events // @section Popup events
@ -129,14 +139,14 @@ L.Popup = L.DivOverlay.extend({
// @event popupclose: PopupEvent // @event popupclose: PopupEvent
// Fired when a popup bound to this layer is closed // Fired when a popup bound to this layer is closed
this._source.fire('popupclose', {popup: this}, true); this._source.fire('popupclose', {popup: this}, true);
if (!(this._source instanceof L.Path)) { if (!(this._source instanceof Path)) {
this._source.off('preclick', L.DomEvent.stopPropagation); this._source.off('preclick', DomEvent.stopPropagation);
} }
} }
}, },
getEvents: function () { 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) { if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this._close; events.preclick = this._close;
@ -157,28 +167,27 @@ L.Popup = L.DivOverlay.extend({
_initLayout: function () { _initLayout: function () {
var prefix = 'leaflet-popup', var prefix = 'leaflet-popup',
container = this._container = L.DomUtil.create('div', container = this._container = DomUtil.create('div',
prefix + ' ' + (this.options.className || '') + prefix + ' ' + (this.options.className || '') +
' leaflet-zoom-animated'); ' leaflet-zoom-animated');
if (this.options.closeButton) { 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.href = '#close';
closeButton.innerHTML = '&#215;'; closeButton.innerHTML = '&#215;';
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); var wrapper = this._wrapper = DomUtil.create('div', prefix + '-content-wrapper', container);
this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); this._contentNode = DomUtil.create('div', prefix + '-content', wrapper);
L.DomEvent DomEvent.disableClickPropagation(wrapper);
.disableClickPropagation(wrapper) DomEvent.disableScrollPropagation(this._contentNode);
.disableScrollPropagation(this._contentNode) DomEvent.on(wrapper, 'contextmenu', DomEvent.stopPropagation);
.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); this._tipContainer = DomUtil.create('div', prefix + '-tip-container', container);
this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); this._tip = DomUtil.create('div', prefix + '-tip', this._tipContainer);
}, },
_updateLayout: function () { _updateLayout: function () {
@ -203,9 +212,9 @@ L.Popup = L.DivOverlay.extend({
if (maxHeight && height > maxHeight) { if (maxHeight && height > maxHeight) {
style.height = maxHeight + 'px'; style.height = maxHeight + 'px';
L.DomUtil.addClass(container, scrolledClass); DomUtil.addClass(container, scrolledClass);
} else { } else {
L.DomUtil.removeClass(container, scrolledClass); DomUtil.removeClass(container, scrolledClass);
} }
this._containerWidth = this._container.offsetWidth; this._containerWidth = this._container.offsetWidth;
@ -214,24 +223,24 @@ L.Popup = L.DivOverlay.extend({
_animateZoom: function (e) { _animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
anchor = this._getAnchor(); anchor = this._getAnchor();
L.DomUtil.setPosition(this._container, pos.add(anchor)); DomUtil.setPosition(this._container, pos.add(anchor));
}, },
_adjustPan: function () { _adjustPan: function () {
if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; } if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
var map = this._map, 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, containerHeight = this._container.offsetHeight + marginBottom,
containerWidth = this._containerWidth, 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), var containerPos = map.layerPointToContainerPoint(layerPos),
padding = L.point(this.options.autoPanPadding), padding = toPoint(this.options.autoPanPadding),
paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
size = map.getSize(), size = map.getSize(),
dx = 0, dx = 0,
dy = 0; dy = 0;
@ -262,12 +271,12 @@ L.Popup = L.DivOverlay.extend({
_onCloseButtonClick: function (e) { _onCloseButtonClick: function (e) {
this._close(); this._close();
L.DomEvent.stop(e); DomEvent.stop(e);
}, },
_getAnchor: function () { _getAnchor: function () {
// Where should we anchor the popup on the source layer? // 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 // @namespace Popup
// @factory L.popup(options?: Popup options, source?: Layer) // @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. // 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) { export var popup = function (options, source) {
return new L.Popup(options, source); return new Popup(options, source);
}; };
@ -285,22 +294,22 @@ L.popup = function (options, source) {
* @option closePopupOnClick: Boolean = true * @option closePopupOnClick: Boolean = true
* Set it to `false` if you don't want popups to close when user clicks the map. * Set it to `false` if you don't want popups to close when user clicks the map.
*/ */
L.Map.mergeOptions({ Map.mergeOptions({
closePopupOnClick: true closePopupOnClick: true
}); });
// @namespace Map // @namespace Map
// @section Methods for Layers and Controls // @section Methods for Layers and Controls
L.Map.include({ Map.include({
// @method openPopup(popup: Popup): this // @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). // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
// @alternative // @alternative
// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this // @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. // Creates a popup with the specified content and options and opens it in the given point on a map.
openPopup: function (popup, latlng, options) { openPopup: function (popup, latlng, options) {
if (!(popup instanceof L.Popup)) { if (!(popup instanceof Popup)) {
popup = new L.Popup(options).setContent(popup); popup = new Popup(options).setContent(popup);
} }
if (latlng) { if (latlng) {
@ -349,7 +358,7 @@ L.Map.include({
*/ */
// @section Popup methods // @section Popup methods
L.Layer.include({ Layer.include({
// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this // @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 // 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`. // the layer as the first argument and should return a `String` or `HTMLElement`.
bindPopup: function (content, options) { bindPopup: function (content, options) {
if (content instanceof L.Popup) { if (content instanceof Popup) {
L.setOptions(content, options); Util.setOptions(content, options);
this._popup = content; this._popup = content;
content._source = this; content._source = this;
} else { } else {
if (!this._popup || options) { if (!this._popup || options) {
this._popup = new L.Popup(options, this); this._popup = new Popup(options, this);
} }
this._popup.setContent(content); this._popup.setContent(content);
} }
@ -398,12 +407,12 @@ L.Layer.include({
// @method openPopup(latlng?: LatLng): this // @method openPopup(latlng?: LatLng): this
// Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed. // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
openPopup: function (layer, latlng) { openPopup: function (layer, latlng) {
if (!(layer instanceof L.Layer)) { if (!(layer instanceof Layer)) {
latlng = layer; latlng = layer;
layer = this; layer = this;
} }
if (layer instanceof L.FeatureGroup) { if (layer instanceof FeatureGroup) {
for (var id in this._layers) { for (var id in this._layers) {
layer = this._layers[id]; layer = this._layers[id];
break; break;
@ -483,11 +492,11 @@ L.Layer.include({
} }
// prevent map click // prevent map click
L.DomEvent.stop(e); DomEvent.stop(e);
// if this inherits from Path its a vector and we can just // if this inherits from Path its a vector and we can just
// open the popup at the new location // open the popup at the new location
if (layer instanceof L.Path) { if (layer instanceof Path) {
this.openPopup(e.layer || e.target, e.latlng); this.openPopup(e.layer || e.target, e.latlng);
return; return;
} }

View File

@ -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 * @class Tooltip
* @inherits DivOverlay * @inherits DivOverlay
@ -20,7 +30,7 @@
// @namespace Tooltip // @namespace Tooltip
L.Tooltip = L.DivOverlay.extend({ export var Tooltip = DivOverlay.extend({
// @section // @section
// @aka Tooltip options // @aka Tooltip options
@ -58,7 +68,7 @@ L.Tooltip = L.DivOverlay.extend({
}, },
onAdd: function (map) { onAdd: function (map) {
L.DivOverlay.prototype.onAdd.call(this, map); DivOverlay.prototype.onAdd.call(this, map);
this.setOpacity(this.options.opacity); this.setOpacity(this.options.opacity);
// @namespace Map // @namespace Map
@ -77,7 +87,7 @@ L.Tooltip = L.DivOverlay.extend({
}, },
onRemove: function (map) { onRemove: function (map) {
L.DivOverlay.prototype.onRemove.call(this, map); DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map // @namespace Map
// @section Tooltip events // @section Tooltip events
@ -95,9 +105,9 @@ L.Tooltip = L.DivOverlay.extend({
}, },
getEvents: function () { 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; events.preclick = this._close;
} }
@ -114,7 +124,7 @@ L.Tooltip = L.DivOverlay.extend({
var prefix = 'leaflet-tooltip', var prefix = 'leaflet-tooltip',
className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); 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 () {}, _updateLayout: function () {},
@ -129,29 +139,29 @@ L.Tooltip = L.DivOverlay.extend({
direction = this.options.direction, direction = this.options.direction,
tooltipWidth = container.offsetWidth, tooltipWidth = container.offsetWidth,
tooltipHeight = container.offsetHeight, tooltipHeight = container.offsetHeight,
offset = L.point(this.options.offset), offset = toPoint(this.options.offset),
anchor = this._getAnchor(); anchor = this._getAnchor();
if (direction === 'top') { 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') { } 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') { } 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) { } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
direction = 'right'; 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 { } else {
direction = 'left'; 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'); DomUtil.removeClass(container, 'leaflet-tooltip-right');
L.DomUtil.removeClass(container, 'leaflet-tooltip-left'); DomUtil.removeClass(container, 'leaflet-tooltip-left');
L.DomUtil.removeClass(container, 'leaflet-tooltip-top'); DomUtil.removeClass(container, 'leaflet-tooltip-top');
L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom'); DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction); DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
L.DomUtil.setPosition(container, pos); DomUtil.setPosition(container, pos);
}, },
_updatePosition: function () { _updatePosition: function () {
@ -163,7 +173,7 @@ L.Tooltip = L.DivOverlay.extend({
this.options.opacity = opacity; this.options.opacity = opacity;
if (this._container) { 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 () { _getAnchor: function () {
// Where should we anchor the tooltip on the source layer? // 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 // @namespace Tooltip
// @factory L.tooltip(options?: Tooltip options, source?: Layer) // @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. // 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) { export var tooltip = function (options, source) {
return new L.Tooltip(options, source); return new Tooltip(options, source);
}; };
// @namespace Map // @namespace Map
// @section Methods for Layers and Controls // @section Methods for Layers and Controls
L.Map.include({ Map.include({
// @method openTooltip(tooltip: Tooltip): this // @method openTooltip(tooltip: Tooltip): this
// Opens the specified tooltip. // Opens the specified tooltip.
@ -196,8 +206,8 @@ L.Map.include({
// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
// Creates a tooltip with the specified content and options and open it. // Creates a tooltip with the specified content and options and open it.
openTooltip: function (tooltip, latlng, options) { openTooltip: function (tooltip, latlng, options) {
if (!(tooltip instanceof L.Tooltip)) { if (!(tooltip instanceof Tooltip)) {
tooltip = new L.Tooltip(options).setContent(tooltip); tooltip = new Tooltip(options).setContent(tooltip);
} }
if (latlng) { if (latlng) {
@ -236,7 +246,7 @@ L.Map.include({
*/ */
// @section Tooltip methods // @section Tooltip methods
L.Layer.include({ Layer.include({
// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this // @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 // 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`. // the layer as the first argument and should return a `String` or `HTMLElement`.
bindTooltip: function (content, options) { bindTooltip: function (content, options) {
if (content instanceof L.Tooltip) { if (content instanceof Tooltip) {
L.setOptions(content, options); Util.setOptions(content, options);
this._tooltip = content; this._tooltip = content;
content._source = this; content._source = this;
} else { } else {
if (!this._tooltip || options) { if (!this._tooltip || options) {
this._tooltip = L.tooltip(options, this); this._tooltip = new Tooltip(options, this);
} }
this._tooltip.setContent(content); this._tooltip.setContent(content);
@ -289,7 +299,7 @@ L.Layer.include({
if (this._tooltip.options.sticky) { if (this._tooltip.options.sticky) {
events.mousemove = this._moveTooltip; events.mousemove = this._moveTooltip;
} }
if (L.Browser.touch) { if (Browser.touch) {
events.click = this._openTooltip; events.click = this._openTooltip;
} }
} else { } else {
@ -302,12 +312,12 @@ L.Layer.include({
// @method openTooltip(latlng?: LatLng): this // @method openTooltip(latlng?: LatLng): this
// Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed. // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
openTooltip: function (layer, latlng) { openTooltip: function (layer, latlng) {
if (!(layer instanceof L.Layer)) { if (!(layer instanceof Layer)) {
latlng = layer; latlng = layer;
layer = this; layer = this;
} }
if (layer instanceof L.FeatureGroup) { if (layer instanceof FeatureGroup) {
for (var id in this._layers) { for (var id in this._layers) {
layer = this._layers[id]; layer = this._layers[id];
break; break;
@ -332,7 +342,7 @@ L.Layer.include({
// Tooltip container may not be defined if not permanent and never // Tooltip container may not be defined if not permanent and never
// opened. // opened.
if (this._tooltip.options.interactive && this._tooltip._container) { 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); this.addInteractiveTarget(this._tooltip._container);
} }
} }
@ -346,7 +356,7 @@ L.Layer.include({
if (this._tooltip) { if (this._tooltip) {
this._tooltip._close(); this._tooltip._close();
if (this._tooltip.options.interactive && this._tooltip._container) { 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); this.removeInteractiveTarget(this._tooltip._container);
} }
} }

View File

@ -1,3 +1,6 @@
import {Icon} from './Icon';
import {toPoint as point} from '../../geometry/Point';
/* /*
* @class DivIcon * @class DivIcon
* @aka L.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. * 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: { options: {
// @section // @section
// @aka DivIcon options // @aka DivIcon options
@ -44,7 +47,7 @@ L.DivIcon = L.Icon.extend({
div.innerHTML = options.html !== false ? options.html : ''; div.innerHTML = options.html !== false ? options.html : '';
if (options.bgPos) { if (options.bgPos) {
var bgPos = L.point(options.bgPos); var bgPos = point(options.bgPos);
div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px'; div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
} }
this._setIconStyles(div, 'icon'); this._setIconStyles(div, 'icon');
@ -59,6 +62,6 @@ L.DivIcon = L.Icon.extend({
// @factory L.divIcon(options: DivIcon options) // @factory L.divIcon(options: DivIcon options)
// Creates a `DivIcon` instance with the given options. // Creates a `DivIcon` instance with the given options.
L.divIcon = function (options) { export function divIcon(options) {
return new L.DivIcon(options); return new DivIcon(options);
}; }

View File

@ -1,3 +1,6 @@
import {Icon} from './Icon';
import * as DomUtil from '../../dom/DomUtil';
/* /*
* @miniclass Icon.Default (Icon) * @miniclass Icon.Default (Icon)
* @aka L.Icon.Default * @aka L.Icon.Default
@ -14,7 +17,7 @@
* `L.Marker.prototype.options.icon` with your own icon instead. * `L.Marker.prototype.options.icon` with your own icon instead.
*/ */
L.Icon.Default = L.Icon.extend({ export var IconDefault = Icon.extend({
options: { options: {
iconUrl: 'marker-icon.png', iconUrl: 'marker-icon.png',
@ -28,21 +31,21 @@ L.Icon.Default = L.Icon.extend({
}, },
_getIconUrl: function (name) { _getIconUrl: function (name) {
if (!L.Icon.Default.imagePath) { // Deprecated, backwards-compatibility only if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
L.Icon.Default.imagePath = this._detectIconPath(); IconDefault.imagePath = this._detectIconPath();
} }
// @option imagePath: String // @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 // blue icon images. If you are placing these images in a non-standard
// way, set this option to point to the right absolute path. // 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 () { _detectIconPath: function () {
var el = L.DomUtil.create('div', 'leaflet-default-icon-path', document.body); var el = DomUtil.create('div', 'leaflet-default-icon-path', document.body);
var path = L.DomUtil.getStyle(el, 'background-image') || var path = DomUtil.getStyle(el, 'background-image') ||
L.DomUtil.getStyle(el, 'backgroundImage'); // IE8 DomUtil.getStyle(el, 'backgroundImage'); // IE8
document.body.removeChild(el); document.body.removeChild(el);

View File

@ -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 * @class Icon
* @aka L.Icon * @aka L.Icon
@ -27,7 +32,7 @@
* *
*/ */
L.Icon = L.Class.extend({ export var Icon = Class.extend({
/* @section /* @section
* @aka Icon options * @aka Icon options
@ -67,7 +72,7 @@ L.Icon = L.Class.extend({
*/ */
initialize: function (options) { initialize: function (options) {
L.setOptions(this, options); setOptions(this, options);
}, },
// @method createIcon(oldIcon?: HTMLElement): HTMLElement // @method createIcon(oldIcon?: HTMLElement): HTMLElement
@ -107,8 +112,8 @@ L.Icon = L.Class.extend({
sizeOption = [sizeOption, sizeOption]; sizeOption = [sizeOption, sizeOption];
} }
var size = L.point(sizeOption), var size = point(sizeOption),
anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor || anchor = point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
size && size.divideBy(2, true)); size && size.divideBy(2, true));
img.className = 'leaflet-marker-' + name + ' ' + (options.className || ''); img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
@ -131,13 +136,13 @@ L.Icon = L.Class.extend({
}, },
_getIconUrl: function (name) { _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) // @factory L.icon(options: Icon options)
// Creates an icon instance with the given options. // Creates an icon instance with the given options.
L.icon = function (options) { export function icon(options) {
return new L.Icon(options); return new Icon(options);
}; }

View File

@ -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. * 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). * Marker dragging handler (by both mouse and touch).
*/ */
L.Handler.MarkerDrag = L.Handler.extend({ export var MarkerDrag = Handler.extend({
initialize: function (marker) { initialize: function (marker) {
this._marker = marker; this._marker = marker;
}, },
@ -25,7 +29,7 @@ L.Handler.MarkerDrag = L.Handler.extend({
var icon = this._marker._icon; var icon = this._marker._icon;
if (!this._draggable) { if (!this._draggable) {
this._draggable = new L.Draggable(icon, icon, true); this._draggable = new Draggable(icon, icon, true);
} }
this._draggable.on({ this._draggable.on({
@ -34,7 +38,7 @@ L.Handler.MarkerDrag = L.Handler.extend({
dragend: this._onDragEnd dragend: this._onDragEnd
}, this).enable(); }, this).enable();
L.DomUtil.addClass(icon, 'leaflet-marker-draggable'); DomUtil.addClass(icon, 'leaflet-marker-draggable');
}, },
removeHooks: function () { removeHooks: function () {
@ -45,7 +49,7 @@ L.Handler.MarkerDrag = L.Handler.extend({
}, this).disable(); }, this).disable();
if (this._marker._icon) { 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) { _onDrag: function (e) {
var marker = this._marker, var marker = this._marker,
shadow = marker._shadow, shadow = marker._shadow,
iconPos = L.DomUtil.getPosition(marker._icon), iconPos = DomUtil.getPosition(marker._icon),
latlng = marker._map.layerPointToLatLng(iconPos); latlng = marker._map.layerPointToLatLng(iconPos);
// update shadow position // update shadow position
if (shadow) { if (shadow) {
L.DomUtil.setPosition(shadow, iconPos); DomUtil.setPosition(shadow, iconPos);
} }
marker._latlng = latlng; marker._latlng = latlng;

View File

@ -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 * @class Marker
* @inherits Interactive layer * @inherits Interactive layer
@ -11,14 +18,14 @@
* ``` * ```
*/ */
L.Marker = L.Layer.extend({ export var Marker = Layer.extend({
// @section // @section
// @aka Marker options // @aka Marker options
options: { options: {
// @option icon: Icon = * // @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 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 // Option inherited from "Interactive layer" abstract class
interactive: true, interactive: true,
@ -69,8 +76,8 @@ L.Marker = L.Layer.extend({
*/ */
initialize: function (latlng, options) { initialize: function (latlng, options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._latlng = L.latLng(latlng); this._latlng = latLng(latlng);
}, },
onAdd: function (map) { onAdd: function (map) {
@ -115,7 +122,7 @@ L.Marker = L.Layer.extend({
// Changes the marker position to the given point. // Changes the marker position to the given point.
setLatLng: function (latlng) { setLatLng: function (latlng) {
var oldLatLng = this._latlng; var oldLatLng = this._latlng;
this._latlng = L.latLng(latlng); this._latlng = latLng(latlng);
this.update(); this.update();
// @event move: Event // @event move: Event
@ -184,7 +191,7 @@ L.Marker = L.Layer.extend({
} }
} }
L.DomUtil.addClass(icon, classToAdd); DomUtil.addClass(icon, classToAdd);
if (options.keyboard) { if (options.keyboard) {
icon.tabIndex = '0'; icon.tabIndex = '0';
@ -208,7 +215,7 @@ L.Marker = L.Layer.extend({
} }
if (newShadow) { if (newShadow) {
L.DomUtil.addClass(newShadow, classToAdd); DomUtil.addClass(newShadow, classToAdd);
newShadow.alt = ''; newShadow.alt = '';
} }
this._shadow = newShadow; 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.removeInteractiveTarget(this._icon);
this._icon = null; this._icon = null;
@ -244,16 +251,16 @@ L.Marker = L.Layer.extend({
_removeShadow: function () { _removeShadow: function () {
if (this._shadow) { if (this._shadow) {
L.DomUtil.remove(this._shadow); DomUtil.remove(this._shadow);
} }
this._shadow = null; this._shadow = null;
}, },
_setPos: function (pos) { _setPos: function (pos) {
L.DomUtil.setPosition(this._icon, pos); DomUtil.setPosition(this._icon, pos);
if (this._shadow) { if (this._shadow) {
L.DomUtil.setPosition(this._shadow, pos); DomUtil.setPosition(this._shadow, pos);
} }
this._zIndex = pos.y + this.options.zIndexOffset; this._zIndex = pos.y + this.options.zIndexOffset;
@ -275,18 +282,18 @@ L.Marker = L.Layer.extend({
if (!this.options.interactive) { return; } if (!this.options.interactive) { return; }
L.DomUtil.addClass(this._icon, 'leaflet-interactive'); DomUtil.addClass(this._icon, 'leaflet-interactive');
this.addInteractiveTarget(this._icon); this.addInteractiveTarget(this._icon);
if (L.Handler.MarkerDrag) { if (MarkerDrag) {
var draggable = this.options.draggable; var draggable = this.options.draggable;
if (this.dragging) { if (this.dragging) {
draggable = this.dragging.enabled(); draggable = this.dragging.enabled();
this.dragging.disable(); this.dragging.disable();
} }
this.dragging = new L.Handler.MarkerDrag(this); this.dragging = new MarkerDrag(this);
if (draggable) { if (draggable) {
this.dragging.enable(); this.dragging.enable();
@ -308,10 +315,10 @@ L.Marker = L.Layer.extend({
_updateOpacity: function () { _updateOpacity: function () {
var opacity = this.options.opacity; var opacity = this.options.opacity;
L.DomUtil.setOpacity(this._icon, opacity); DomUtil.setOpacity(this._icon, opacity);
if (this._shadow) { 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) // @factory L.marker(latlng: LatLng, options? : Marker options)
// Instantiates a Marker object given a geographical point and optionally an options object. // Instantiates a Marker object given a geographical point and optionally an options object.
L.marker = function (latlng, options) { export function marker(latlng, options) {
return new L.Marker(latlng, options); return new Marker(latlng, options);
}; }

View File

@ -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 * @class GridLayer
* @inherits Layer * @inherits Layer
@ -64,7 +72,7 @@
*/ */
L.GridLayer = L.Layer.extend({ export var GridLayer = Layer.extend({
// @section // @section
// @aka GridLayer options // @aka GridLayer options
@ -79,7 +87,7 @@ L.GridLayer = L.Layer.extend({
// @option updateWhenIdle: Boolean = depends // @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`. // 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 // @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. // 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) { initialize: function (options) {
L.setOptions(this, options); Util.setOptions(this, options);
}, },
onAdd: function () { onAdd: function () {
@ -146,7 +154,7 @@ L.GridLayer = L.Layer.extend({
onRemove: function (map) { onRemove: function (map) {
this._removeAllTiles(); this._removeAllTiles();
L.DomUtil.remove(this._container); DomUtil.remove(this._container);
map._removeZoomLimit(this); map._removeZoomLimit(this);
this._container = null; this._container = null;
this._tileZoom = null; this._tileZoom = null;
@ -156,7 +164,7 @@ L.GridLayer = L.Layer.extend({
// Brings the tile layer to the top of all tile layers. // Brings the tile layer to the top of all tile layers.
bringToFront: function () { bringToFront: function () {
if (this._map) { if (this._map) {
L.DomUtil.toFront(this._container); DomUtil.toFront(this._container);
this._setAutoZIndex(Math.max); this._setAutoZIndex(Math.max);
} }
return this; return this;
@ -166,7 +174,7 @@ L.GridLayer = L.Layer.extend({
// Brings the tile layer to the bottom of all tile layers. // Brings the tile layer to the bottom of all tile layers.
bringToBack: function () { bringToBack: function () {
if (this._map) { if (this._map) {
L.DomUtil.toBack(this._container); DomUtil.toBack(this._container);
this._setAutoZIndex(Math.min); this._setAutoZIndex(Math.min);
} }
return this; return this;
@ -222,7 +230,7 @@ L.GridLayer = L.Layer.extend({
if (!this.options.updateWhenIdle) { if (!this.options.updateWhenIdle) {
// update tiles on move, but not more often than once per given interval // update tiles on move, but not more often than once per given interval
if (!this._onMove) { 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; 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. // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
getTileSize: function () { getTileSize: function () {
var s = this.options.tileSize; 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 () { _updateZIndex: function () {
@ -284,9 +292,9 @@ L.GridLayer = L.Layer.extend({
if (!this._map) { return; } if (!this._map) { return; }
// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles // 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(), var now = +new Date(),
nextFrame = false, nextFrame = false,
@ -298,7 +306,7 @@ L.GridLayer = L.Layer.extend({
var fade = Math.min(1, (now - tile.loaded) / 200); var fade = Math.min(1, (now - tile.loaded) / 200);
L.DomUtil.setOpacity(tile.el, fade); DomUtil.setOpacity(tile.el, fade);
if (fade < 1) { if (fade < 1) {
nextFrame = true; nextFrame = true;
} else { } else {
@ -310,15 +318,15 @@ L.GridLayer = L.Layer.extend({
if (willPrune && !this._noPrune) { this._pruneTiles(); } if (willPrune && !this._noPrune) { this._pruneTiles(); }
if (nextFrame) { if (nextFrame) {
L.Util.cancelAnimFrame(this._fadeFrame); Util.cancelAnimFrame(this._fadeFrame);
this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this);
} }
}, },
_initContainer: function () { _initContainer: function () {
if (this._container) { return; } 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(); this._updateZIndex();
if (this.options.opacity < 1) { if (this.options.opacity < 1) {
@ -339,7 +347,7 @@ L.GridLayer = L.Layer.extend({
if (this._levels[z].el.children.length || z === zoom) { if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z); this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
} else { } else {
L.DomUtil.remove(this._levels[z].el); DomUtil.remove(this._levels[z].el);
this._removeTilesAtZoom(z); this._removeTilesAtZoom(z);
delete this._levels[z]; delete this._levels[z];
} }
@ -351,7 +359,7 @@ L.GridLayer = L.Layer.extend({
if (!level) { if (!level) {
level = this._levels[zoom] = {}; 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.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round(); 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()); this._setZoomTransform(level, map.getCenter(), map.getZoom());
// force the browser to consider the newly added element for transition // 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; this._level = level;
@ -421,7 +429,7 @@ L.GridLayer = L.Layer.extend({
_invalidateAll: function () { _invalidateAll: function () {
for (var z in this._levels) { for (var z in this._levels) {
L.DomUtil.remove(this._levels[z].el); DomUtil.remove(this._levels[z].el);
delete this._levels[z]; delete this._levels[z];
} }
this._removeAllTiles(); this._removeAllTiles();
@ -433,7 +441,7 @@ L.GridLayer = L.Layer.extend({
var x2 = Math.floor(x / 2), var x2 = Math.floor(x / 2),
y2 = Math.floor(y / 2), y2 = Math.floor(y / 2),
z2 = z - 1, z2 = z - 1,
coords2 = new L.Point(+x2, +y2); coords2 = new Point(+x2, +y2);
coords2.z = +z2; coords2.z = +z2;
var key = this._tileCoordsToKey(coords2), 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 i = 2 * x; i < 2 * x + 2; i++) {
for (var j = 2 * y; j < 2 * y + 2; j++) { 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; coords.z = z + 1;
var key = this._tileCoordsToKey(coords), var key = this._tileCoordsToKey(coords),
@ -536,10 +544,10 @@ L.GridLayer = L.Layer.extend({
translate = level.origin.multiplyBy(scale) translate = level.origin.multiplyBy(scale)
.subtract(this._map._getNewPixelOrigin(center, zoom)).round(); .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
if (L.Browser.any3d) { if (Browser.any3d) {
L.DomUtil.setTransform(level.el, translate, scale); DomUtil.setTransform(level.el, translate, scale);
} else { } 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(), pixelCenter = map.project(center, this._tileZoom).floor(),
halfSize = map.getSize().divideBy(scale * 2); 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 // 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(), tileCenter = tileRange.getCenter(),
queue = [], queue = [],
margin = this.options.keepBuffer, 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])); tileRange.getTopRight().add([margin, -margin]));
for (var key in this._tiles) { for (var key in this._tiles) {
var c = this._tiles[key].coords; 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; this._tiles[key].current = false;
} }
} }
@ -611,7 +619,7 @@ L.GridLayer = L.Layer.extend({
// create a queue of coordinates to load tiles from // create a queue of coordinates to load tiles from
for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 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; coords.z = this._tileZoom;
if (!this._isValidTile(coords)) { continue; } 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 // don't load tile if it doesn't intersect the bounds in options
var tileBounds = this._tileCoordsToBounds(coords); var tileBounds = this._tileCoordsToBounds(coords);
return L.latLngBounds(this.options.bounds).overlaps(tileBounds); return latLngBounds(this.options.bounds).overlaps(tileBounds);
}, },
_keyToBounds: function (key) { _keyToBounds: function (key) {
@ -682,7 +690,7 @@ L.GridLayer = L.Layer.extend({
nw = map.unproject(nwPoint, coords.z), nw = map.unproject(nwPoint, coords.z),
se = map.unproject(sePoint, coords.z), se = map.unproject(sePoint, coords.z),
bounds = new L.LatLngBounds(nw, se); bounds = new LatLngBounds(nw, se);
if (!this.options.noWrap) { if (!this.options.noWrap) {
map.wrapLatLngBounds(bounds); map.wrapLatLngBounds(bounds);
@ -699,7 +707,7 @@ L.GridLayer = L.Layer.extend({
// converts tile cache key to coordinates // converts tile cache key to coordinates
_keyToTileCoords: function (key) { _keyToTileCoords: function (key) {
var k = key.split(':'), var k = key.split(':'),
coords = new L.Point(+k[0], +k[1]); coords = new Point(+k[0], +k[1]);
coords.z = +k[2]; coords.z = +k[2];
return coords; return coords;
}, },
@ -708,7 +716,7 @@ L.GridLayer = L.Layer.extend({
var tile = this._tiles[key]; var tile = this._tiles[key];
if (!tile) { return; } if (!tile) { return; }
L.DomUtil.remove(tile.el); DomUtil.remove(tile.el);
delete this._tiles[key]; delete this._tiles[key];
@ -721,23 +729,23 @@ L.GridLayer = L.Layer.extend({
}, },
_initTile: function (tile) { _initTile: function (tile) {
L.DomUtil.addClass(tile, 'leaflet-tile'); DomUtil.addClass(tile, 'leaflet-tile');
var tileSize = this.getTileSize(); var tileSize = this.getTileSize();
tile.style.width = tileSize.x + 'px'; tile.style.width = tileSize.x + 'px';
tile.style.height = tileSize.y + 'px'; tile.style.height = tileSize.y + 'px';
tile.onselectstart = L.Util.falseFn; tile.onselectstart = Util.falseFn;
tile.onmousemove = L.Util.falseFn; tile.onmousemove = Util.falseFn;
// update opacity on tiles in IE7-8 because of filter inheritance problems // update opacity on tiles in IE7-8 because of filter inheritance problems
if (L.Browser.ielt9 && this.options.opacity < 1) { if (Browser.ielt9 && this.options.opacity < 1) {
L.DomUtil.setOpacity(tile, this.options.opacity); DomUtil.setOpacity(tile, this.options.opacity);
} }
// without this hack, tiles disappear after zoom on Chrome for Android // without this hack, tiles disappear after zoom on Chrome for Android
// https://github.com/Leaflet/Leaflet/issues/2078 // https://github.com/Leaflet/Leaflet/issues/2078
if (L.Browser.android && !L.Browser.android23) { if (Browser.android && !Browser.android23) {
tile.style.WebkitBackfaceVisibility = 'hidden'; tile.style.WebkitBackfaceVisibility = 'hidden';
} }
}, },
@ -746,7 +754,7 @@ L.GridLayer = L.Layer.extend({
var tilePos = this._getTilePos(coords), var tilePos = this._getTilePos(coords),
key = this._tileCoordsToKey(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); this._initTile(tile);
@ -754,10 +762,10 @@ L.GridLayer = L.Layer.extend({
// we know that tile is async and will be ready later; otherwise // we know that tile is async and will be ready later; otherwise
if (this.createTile.length < 2) { if (this.createTile.length < 2) {
// mark tile as ready, but delay one frame for opacity animation to happen // 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 // save tile in cache
this._tiles[key] = { this._tiles[key] = {
@ -795,16 +803,16 @@ L.GridLayer = L.Layer.extend({
tile.loaded = +new Date(); tile.loaded = +new Date();
if (this._map._fadeAnimated) { if (this._map._fadeAnimated) {
L.DomUtil.setOpacity(tile.el, 0); DomUtil.setOpacity(tile.el, 0);
L.Util.cancelAnimFrame(this._fadeFrame); Util.cancelAnimFrame(this._fadeFrame);
this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this); this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this);
} else { } else {
tile.active = true; tile.active = true;
this._pruneTiles(); this._pruneTiles();
} }
if (!err) { if (!err) {
L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded'); DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
// @event tileload: TileEvent // @event tileload: TileEvent
// Fired when a tile loads. // Fired when a tile loads.
@ -820,12 +828,12 @@ L.GridLayer = L.Layer.extend({
// Fired when the grid layer loaded all visible tiles. // Fired when the grid layer loaded all visible tiles.
this.fire('load'); this.fire('load');
if (L.Browser.ielt9 || !this._map._fadeAnimated) { if (Browser.ielt9 || !this._map._fadeAnimated) {
L.Util.requestAnimFrame(this._pruneTiles, this); Util.requestAnimFrame(this._pruneTiles, this);
} else { } else {
// Wait a bit more than 0.2 secs (the duration of the tile fade-in) // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
// to trigger a pruning. // 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) { _wrapCoords: function (coords) {
var newCoords = new L.Point( var newCoords = new Point(
this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x, this._wrapX ? Util.wrapNum(coords.x, this._wrapX) : coords.x,
this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y); this._wrapY ? Util.wrapNum(coords.y, this._wrapY) : coords.y);
newCoords.z = coords.z; newCoords.z = coords.z;
return newCoords; return newCoords;
}, },
_pxBoundsToTileRange: function (bounds) { _pxBoundsToTileRange: function (bounds) {
var tileSize = this.getTileSize(); var tileSize = this.getTileSize();
return new L.Bounds( return new Bounds(
bounds.min.unscaleBy(tileSize).floor(), bounds.min.unscaleBy(tileSize).floor(),
bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1])); bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
}, },
@ -859,6 +867,6 @@ L.GridLayer = L.Layer.extend({
// @factory L.gridLayer(options?: GridLayer options) // @factory L.gridLayer(options?: GridLayer options)
// Creates a new instance of GridLayer with the supplied options. // Creates a new instance of GridLayer with the supplied options.
L.gridLayer = function (options) { export function gridLayer(options) {
return new L.GridLayer(options); return new GridLayer(options);
}; }

View File

@ -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 * @class TileLayer.WMS
* @inherits TileLayer * @inherits TileLayer
@ -16,7 +21,7 @@
* ``` * ```
*/ */
L.TileLayer.WMS = L.TileLayer.extend({ export var TileLayerWMS = TileLayer.extend({
// @section // @section
// @aka TileLayer.WMS options // @aka TileLayer.WMS options
@ -63,7 +68,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
this._url = url; 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 // all keys that are not TileLayer options go to WMS params
for (var i in options) { 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; this.wmsParams = wmsParams;
}, },
@ -87,7 +92,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = this._crs.code; this.wmsParams[projectionKey] = this._crs.code;
L.TileLayer.prototype.onAdd.call(this, map); TileLayer.prototype.onAdd.call(this, map);
}, },
getTileUrl: function (coords) { getTileUrl: function (coords) {
@ -96,14 +101,14 @@ L.TileLayer.WMS = L.TileLayer.extend({
nw = this._crs.project(tileBounds.getNorthWest()), nw = this._crs.project(tileBounds.getNorthWest()),
se = this._crs.project(tileBounds.getSouthEast()), 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] : [se.y, nw.x, nw.y, se.x] :
[nw.x, se.y, se.x, nw.y]).join(','), [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 + return url +
L.Util.getParamString(this.wmsParams, url, this.options.uppercase) + getParamString(this.wmsParams, url, this.options.uppercase) +
(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox; (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). // 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) { setParams: function (params, noRedraw) {
L.extend(this.wmsParams, params); extend(this.wmsParams, params);
if (!noRedraw) { if (!noRedraw) {
this.redraw(); this.redraw();
@ -124,6 +129,6 @@ L.TileLayer.WMS = L.TileLayer.extend({
// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options) // @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. // 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) { export function tileLayerWMS(url, options) {
return new L.TileLayer.WMS(url, options); return new TileLayerWMS(url, options);
}; }

View File

@ -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 * @class TileLayer
* @inherits GridLayer * @inherits GridLayer
@ -29,7 +36,7 @@
*/ */
L.TileLayer = L.GridLayer.extend({ export var TileLayer = GridLayer.extend({
// @section // @section
// @aka TileLayer options // @aka TileLayer options
@ -87,10 +94,10 @@ L.TileLayer = L.GridLayer.extend({
this._url = url; this._url = url;
options = L.setOptions(this, options); options = Util.setOptions(this, options);
// detecting retina displays, adjusting tileSize and zoom levels // 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); options.tileSize = Math.floor(options.tileSize / 2);
@ -110,7 +117,7 @@ L.TileLayer = L.GridLayer.extend({
} }
// for https://github.com/Leaflet/Leaflet/issues/137 // for https://github.com/Leaflet/Leaflet/issues/137
if (!L.Browser.android) { if (!Browser.android) {
this.on('tileunload', this._onTileRemove); this.on('tileunload', this._onTileRemove);
} }
}, },
@ -133,8 +140,8 @@ L.TileLayer = L.GridLayer.extend({
createTile: function (coords, done) { createTile: function (coords, done) {
var tile = document.createElement('img'); var tile = document.createElement('img');
L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile)); DomEvent.on(tile, 'load', Util.bind(this._tileOnLoad, this, done, tile));
L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile)); DomEvent.on(tile, 'error', Util.bind(this._tileOnError, this, done, tile));
if (this.options.crossOrigin) { if (this.options.crossOrigin) {
tile.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. // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
getTileUrl: function (coords) { getTileUrl: function (coords) {
var data = { var data = {
r: L.Browser.retina ? '@2x' : '', r: Browser.retina ? '@2x' : '',
s: this._getSubdomain(coords), s: this._getSubdomain(coords),
x: coords.x, x: coords.x,
y: coords.y, y: coords.y,
@ -179,13 +186,13 @@ L.TileLayer = L.GridLayer.extend({
data['-y'] = invertedY; 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) { _tileOnLoad: function (done, tile) {
// For https://github.com/Leaflet/Leaflet/issues/3332 // For https://github.com/Leaflet/Leaflet/issues/3332
if (L.Browser.ielt9) { if (Browser.ielt9) {
setTimeout(L.bind(done, this, null, tile), 0); setTimeout(Util.bind(done, this, null, tile), 0);
} else { } else {
done(null, tile); done(null, tile);
} }
@ -201,10 +208,10 @@ L.TileLayer = L.GridLayer.extend({
getTileSize: function () { getTileSize: function () {
var map = this._map, var map = this._map,
tileSize = L.GridLayer.prototype.getTileSize.call(this), tileSize = GridLayer.prototype.getTileSize.call(this),
zoom = this._tileZoom + this.options.zoomOffset, zoom = this._tileZoom + this.options.zoomOffset,
minNativeZoom = this.options.minNativeZoom, minNativeZoom = this.options.minNativeZoom,
maxNativeZoom = this.options.maxNativeZoom; maxNativeZoom = this.options.maxNativeZoom;
// decrease tile size when scaling below minNativeZoom // decrease tile size when scaling below minNativeZoom
if (minNativeZoom !== null && zoom < minNativeZoom) { if (minNativeZoom !== null && zoom < minNativeZoom) {
@ -260,12 +267,12 @@ L.TileLayer = L.GridLayer.extend({
if (this._tiles[i].coords.z !== this._tileZoom) { if (this._tiles[i].coords.z !== this._tileZoom) {
tile = this._tiles[i].el; tile = this._tiles[i].el;
tile.onload = L.Util.falseFn; tile.onload = Util.falseFn;
tile.onerror = L.Util.falseFn; tile.onerror = Util.falseFn;
if (!tile.complete) { if (!tile.complete) {
tile.src = L.Util.emptyImageUrl; tile.src = Util.emptyImageUrl;
L.DomUtil.remove(tile); DomUtil.remove(tile);
} }
} }
} }
@ -276,6 +283,6 @@ L.TileLayer = L.GridLayer.extend({
// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
// Instantiates a tile layer object given a `URL template` and optionally an options object. // Instantiates a tile layer object given a `URL template` and optionally an options object.
L.tileLayer = function (url, options) { export function tileLayer(url, options) {
return new L.TileLayer(url, options); return new TileLayer(url, options);
}; }

View File

@ -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 * @class Canvas
* @inherits Renderer * @inherits Renderer
@ -30,7 +37,7 @@
* ``` * ```
*/ */
L.Canvas = L.Renderer.extend({ export var Canvas = Renderer.extend({
getEvents: function () { getEvents: function () {
var events = L.Renderer.prototype.getEvents.call(this); var events = L.Renderer.prototype.getEvents.call(this);
events.viewprereset = this._onViewPreReset; events.viewprereset = this._onViewPreReset;
@ -43,7 +50,7 @@ L.Canvas = L.Renderer.extend({
}, },
onAdd: function () { onAdd: function () {
L.Renderer.prototype.onAdd.call(this); Renderer.prototype.onAdd.call(this);
// Redraw vectors since canvas is cleared upon removal, // Redraw vectors since canvas is cleared upon removal,
// in case of removing the renderer itself from the map. // in case of removing the renderer itself from the map.
@ -53,10 +60,9 @@ L.Canvas = L.Renderer.extend({
_initContainer: function () { _initContainer: function () {
var container = this._container = document.createElement('canvas'); var container = this._container = document.createElement('canvas');
L.DomEvent DomEvent.on(container, 'mousemove', Util.throttle(this._onMouseMove, 32, this), this);
.on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this) DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this) DomEvent.on(container, 'mouseout', this._handleMouseOut, this);
.on(container, 'mouseout', this._handleMouseOut, this);
this._ctx = container.getContext('2d'); this._ctx = container.getContext('2d');
}, },
@ -78,14 +84,14 @@ L.Canvas = L.Renderer.extend({
this._drawnLayers = {}; this._drawnLayers = {};
L.Renderer.prototype._update.call(this); Renderer.prototype._update.call(this);
var b = this._bounds, var b = this._bounds,
container = this._container, container = this._container,
size = b.getSize(), 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 // set canvas size (also clearing it); use double size on retina
container.width = m * size.x; container.width = m * size.x;
@ -93,7 +99,7 @@ L.Canvas = L.Renderer.extend({
container.style.width = size.x + 'px'; container.style.width = size.x + 'px';
container.style.height = size.y + 'px'; container.style.height = size.y + 'px';
if (L.Browser.retina) { if (Browser.retina) {
this._ctx.scale(2, 2); this._ctx.scale(2, 2);
} }
@ -115,7 +121,7 @@ L.Canvas = L.Renderer.extend({
_initPath: function (layer) { _initPath: function (layer) {
this._updateDashArray(layer); this._updateDashArray(layer);
this._layers[L.stamp(layer)] = layer; this._layers[Util.stamp(layer)] = layer;
var order = layer._order = { var order = layer._order = {
layer: layer, layer: layer,
@ -186,12 +192,12 @@ L.Canvas = L.Renderer.extend({
if (!this._map) { return; } if (!this._map) { return; }
this._extendRedrawBounds(layer); 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) { _extendRedrawBounds: function (layer) {
var padding = (layer.options.weight || 0) + 1; 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.min.subtract([padding, padding]));
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
}, },
@ -335,7 +341,7 @@ L.Canvas = L.Renderer.extend({
} }
} }
if (clickedLayer) { if (clickedLayer) {
L.DomEvent._fakeStop(e); DomEvent.fakeStop(e);
this._fireEvent([clickedLayer], e); this._fireEvent([clickedLayer], e);
} }
}, },
@ -352,7 +358,7 @@ L.Canvas = L.Renderer.extend({
var layer = this._hoveredLayer; var layer = this._hoveredLayer;
if (layer) { if (layer) {
// if we're leaving the layer, fire mouseout // 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._fireEvent([layer], e, 'mouseout');
this._hoveredLayer = null; this._hoveredLayer = null;
} }
@ -372,7 +378,7 @@ L.Canvas = L.Renderer.extend({
this._handleMouseOut(e); this._handleMouseOut(e);
if (candidateHoveredLayer) { 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._fireEvent([candidateHoveredLayer], e, 'mouseover');
this._hoveredLayer = candidateHoveredLayer; this._hoveredLayer = candidateHoveredLayer;
} }
@ -444,64 +450,8 @@ L.Canvas = L.Renderer.extend({
} }
}); });
// @namespace Browser; @property canvas: Boolean
// `true` when the browser supports [`<canvas>`](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) // @factory L.canvas(options?: Renderer options)
// Creates a Canvas renderer with the given options. // Creates a Canvas renderer with the given options.
L.canvas = function (options) { export function canvas(options) {
return L.Browser.canvas ? new L.Canvas(options) : null; return Browser.canvas ? new 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();
};

View File

@ -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 * @class Circle
* @aka L.Circle * @aka L.Circle
@ -14,15 +22,15 @@
* ``` * ```
*/ */
L.Circle = L.CircleMarker.extend({ export var Circle = CircleMarker.extend({
initialize: function (latlng, options, legacyOptions) { initialize: function (latlng, options, legacyOptions) {
if (typeof options === 'number') { if (typeof options === 'number') {
// Backwards compatibility with 0.7.x factory (latlng, radius, options?) // 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); Util.setOptions(this, options);
this._latlng = L.latLng(latlng); this._latlng = toLatLng(latlng);
if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
@ -50,12 +58,12 @@ L.Circle = L.CircleMarker.extend({
getBounds: function () { getBounds: function () {
var half = [this._radius, this._radiusY || this._radius]; 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.subtract(half)),
this._map.layerPointToLatLng(this._point.add(half))); this._map.layerPointToLatLng(this._point.add(half)));
}, },
setStyle: L.Path.prototype.setStyle, setStyle: Path.prototype.setStyle,
_project: function () { _project: function () {
@ -64,9 +72,9 @@ L.Circle = L.CircleMarker.extend({
map = this._map, map = this._map,
crs = map.options.crs; crs = map.options.crs;
if (crs.distance === L.CRS.Earth.distance) { if (crs.distance === Earth.distance) {
var d = Math.PI / 180, 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]), top = map.project([lat + latR, lng]),
bottom = map.project([lat - latR, lng]), bottom = map.project([lat - latR, lng]),
p = top.add(bottom).divideBy(2), 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) // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
// Obsolete way of instantiating a circle, for compatibility with 0.7.x code. // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
// Do not use in new applications or plugins. // Do not use in new applications or plugins.
L.circle = function (latlng, options, legacyOptions) { export function circle(latlng, options, legacyOptions) {
return new L.Circle(latlng, options, legacyOptions); return new Circle(latlng, options, legacyOptions);
}; }

View File

@ -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 * @class CircleMarker
* @aka L.CircleMarker * @aka L.CircleMarker
@ -6,7 +12,7 @@
* A circle of a fixed size with radius specified in pixels. Extends `Path`. * A circle of a fixed size with radius specified in pixels. Extends `Path`.
*/ */
L.CircleMarker = L.Path.extend({ export var CircleMarker = Path.extend({
// @section // @section
// @aka CircleMarker options // @aka CircleMarker options
@ -19,15 +25,15 @@ L.CircleMarker = L.Path.extend({
}, },
initialize: function (latlng, options) { initialize: function (latlng, options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._latlng = L.latLng(latlng); this._latlng = toLatLng(latlng);
this._radius = this.options.radius; this._radius = this.options.radius;
}, },
// @method setLatLng(latLng: LatLng): this // @method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location. // Sets the position of a circle marker to a new location.
setLatLng: function (latlng) { setLatLng: function (latlng) {
this._latlng = L.latLng(latlng); this._latlng = toLatLng(latlng);
this.redraw(); this.redraw();
return this.fire('move', {latlng: this._latlng}); return this.fire('move', {latlng: this._latlng});
}, },
@ -53,7 +59,7 @@ L.CircleMarker = L.Path.extend({
setStyle : function (options) { setStyle : function (options) {
var radius = options && options.radius || this._radius; var radius = options && options.radius || this._radius;
L.Path.prototype.setStyle.call(this, options); Path.prototype.setStyle.call(this, options);
this.setRadius(radius); this.setRadius(radius);
return this; return this;
}, },
@ -68,7 +74,7 @@ L.CircleMarker = L.Path.extend({
r2 = this._radiusY || r, r2 = this._radiusY || r,
w = this._clickTolerance(), w = this._clickTolerance(),
p = [r + w, r2 + w]; 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 () { _update: function () {
@ -83,12 +89,17 @@ L.CircleMarker = L.Path.extend({
_empty: function () { _empty: function () {
return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 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) // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
// Instantiates a circle marker object given a geographical point, and an optional options object. // Instantiates a circle marker object given a geographical point, and an optional options object.
L.circleMarker = function (latlng, options) { export function circleMarker(latlng, options) {
return new L.CircleMarker(latlng, options); return new CircleMarker(latlng, options);
}; }

View File

@ -1,3 +1,7 @@
import {Layer} from '../Layer';
import * as Util from '../../core/Util';
import {touch} from '../../core/Browser';
/* /*
* @class Path * @class Path
* @aka L.Path * @aka L.Path
@ -7,7 +11,7 @@
* overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`. * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
*/ */
L.Path = L.Layer.extend({ export var Path = Layer.extend({
// @section // @section
// @aka Path options // @aka Path options
@ -94,7 +98,7 @@ L.Path = L.Layer.extend({
// @method setStyle(style: Path options): this // @method setStyle(style: Path options): this
// Changes the appearance of a Path based on the options in the `Path options` object. // Changes the appearance of a Path based on the options in the `Path options` object.
setStyle: function (style) { setStyle: function (style) {
L.setOptions(this, style); Util.setOptions(this, style);
if (this._renderer) { if (this._renderer) {
this._renderer._updateStyle(this); this._renderer._updateStyle(this);
} }
@ -131,6 +135,6 @@ L.Path = L.Layer.extend({
_clickTolerance: function () { _clickTolerance: function () {
// used when doing hit detection for Canvas layers // 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);
} }
}); });

View File

@ -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 * @class Polygon
* @aka L.Polygon * @aka L.Polygon
@ -44,7 +51,7 @@
* ``` * ```
*/ */
L.Polygon = L.Polyline.extend({ export var Polygon = Polyline.extend({
options: { options: {
fill: true fill: true
@ -90,25 +97,25 @@ L.Polygon = L.Polyline.extend({
}, },
_convertLatLngs: function (latlngs) { _convertLatLngs: function (latlngs) {
var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs), var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
len = result.length; len = result.length;
// remove last point if it equals first one // 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(); result.pop();
} }
return result; return result;
}, },
_setLatLngs: function (latlngs) { _setLatLngs: function (latlngs) {
L.Polyline.prototype._setLatLngs.call(this, latlngs); Polyline.prototype._setLatLngs.call(this, latlngs);
if (L.Polyline._flat(this._latlngs)) { if (LineUtil._flat(this._latlngs)) {
this._latlngs = [this._latlngs]; this._latlngs = [this._latlngs];
} }
}, },
_defaultShape: function () { _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 () { _clipPoints: function () {
@ -116,10 +123,10 @@ L.Polygon = L.Polyline.extend({
var bounds = this._renderer._bounds, var bounds = this._renderer._bounds,
w = this.options.weight, 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 // 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 = []; this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 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++) { 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) { if (clipped.length) {
this._parts.push(clipped); this._parts.push(clipped);
} }
@ -141,11 +148,37 @@ L.Polygon = L.Polyline.extend({
_updatePath: function () { _updatePath: function () {
this._renderer._updatePoly(this, true); 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) // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
L.polygon = function (latlngs, options) { export function polygon(latlngs, options) {
return new L.Polygon(latlngs, options); return new Polygon(latlngs, options);
}; }

View File

@ -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 * @class Polyline
* @aka L.Polyline * @aka L.Polyline
@ -36,7 +44,8 @@
* ``` * ```
*/ */
L.Polyline = L.Path.extend({
export var Polyline = Path.extend({
// @section // @section
// @aka Polyline options // @aka Polyline options
@ -52,7 +61,7 @@ L.Polyline = L.Path.extend({
}, },
initialize: function (latlngs, options) { initialize: function (latlngs, options) {
L.setOptions(this, options); Util.setOptions(this, options);
this._setLatLngs(latlngs); this._setLatLngs(latlngs);
}, },
@ -78,7 +87,7 @@ L.Polyline = L.Path.extend({
closestLayerPoint: function (p) { closestLayerPoint: function (p) {
var minDistance = Infinity, var minDistance = Infinity,
minPoint = null, minPoint = null,
closest = L.LineUtil._sqClosestPointOnSegment, closest = LineUtil._sqClosestPointOnSegment,
p1, p2; p1, p2;
for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 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)). // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
addLatLng: function (latlng, latlngs) { addLatLng: function (latlng, latlngs) {
latlngs = latlngs || this._defaultShape(); latlngs = latlngs || this._defaultShape();
latlng = L.latLng(latlng); latlng = toLatLng(latlng);
latlngs.push(latlng); latlngs.push(latlng);
this._bounds.extend(latlng); this._bounds.extend(latlng);
return this.redraw(); return this.redraw();
}, },
_setLatLngs: function (latlngs) { _setLatLngs: function (latlngs) {
this._bounds = new L.LatLngBounds(); this._bounds = new LatLngBounds();
this._latlngs = this._convertLatLngs(latlngs); this._latlngs = this._convertLatLngs(latlngs);
}, },
_defaultShape: function () { _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 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
_convertLatLngs: function (latlngs) { _convertLatLngs: function (latlngs) {
var result = [], var result = [],
flat = L.Polyline._flat(latlngs); flat = LineUtil._flat(latlngs);
for (var i = 0, len = latlngs.length; i < len; i++) { for (var i = 0, len = latlngs.length; i < len; i++) {
if (flat) { if (flat) {
result[i] = L.latLng(latlngs[i]); result[i] = toLatLng(latlngs[i]);
this._bounds.extend(result[i]); this._bounds.extend(result[i]);
} else { } else {
result[i] = this._convertLatLngs(latlngs[i]); result[i] = this._convertLatLngs(latlngs[i]);
@ -188,12 +197,12 @@ L.Polyline = L.Path.extend({
}, },
_project: function () { _project: function () {
var pxBounds = new L.Bounds(); var pxBounds = new Bounds();
this._rings = []; this._rings = [];
this._projectLatlngs(this._latlngs, this._rings, pxBounds); this._projectLatlngs(this._latlngs, this._rings, pxBounds);
var w = this._clickTolerance(), var w = this._clickTolerance(),
p = new L.Point(w, w); p = new Point(w, w);
if (this._bounds.isValid() && pxBounds.isValid()) { if (this._bounds.isValid() && pxBounds.isValid()) {
pxBounds.min._subtract(p); pxBounds.min._subtract(p);
@ -204,7 +213,7 @@ L.Polyline = L.Path.extend({
// recursively turns latlngs into a set of rings with projected coordinates // recursively turns latlngs into a set of rings with projected coordinates
_projectLatlngs: function (latlngs, result, projectedBounds) { _projectLatlngs: function (latlngs, result, projectedBounds) {
var flat = latlngs[0] instanceof L.LatLng, var flat = latlngs[0] instanceof LatLng,
len = latlngs.length, len = latlngs.length,
i, ring; i, ring;
@ -243,7 +252,7 @@ L.Polyline = L.Path.extend({
points = this._rings[i]; points = this._rings[i];
for (j = 0, len2 = points.length; j < len2 - 1; j++) { 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; } if (!segment) { continue; }
@ -265,7 +274,7 @@ L.Polyline = L.Path.extend({
tolerance = this.options.smoothFactor; tolerance = this.options.smoothFactor;
for (var i = 0, len = parts.length; i < len; i++) { 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 () { _updatePath: function () {
this._renderer._updatePoly(this); 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 // optionally an options object. You can create a `Polyline` object with
// multiple separate lines (`MultiPolyline`) by passing an array of arrays // multiple separate lines (`MultiPolyline`) by passing an array of arrays
// of geographic points. // of geographic points.
L.polyline = function (latlngs, options) { export function polyline(latlngs, options) {
return new L.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');
};

View File

@ -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. * 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) { 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 // @method setBounds(latLngBounds: LatLngBounds): this
@ -37,7 +40,7 @@ L.Rectangle = L.Polygon.extend({
}, },
_boundsToLatLngs: function (latLngBounds) { _boundsToLatLngs: function (latLngBounds) {
latLngBounds = L.latLngBounds(latLngBounds); latLngBounds = toLatLngBounds(latLngBounds);
return [ return [
latLngBounds.getSouthWest(), latLngBounds.getSouthWest(),
latLngBounds.getNorthWest(), latLngBounds.getNorthWest(),
@ -49,6 +52,6 @@ L.Rectangle = L.Polygon.extend({
// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options) // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
L.rectangle = function (latLngBounds, options) { export function rectangle(latLngBounds, options) {
return new L.Rectangle(latLngBounds, options); return new Rectangle(latLngBounds, options);
}; }

View File

@ -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;
}
});

View File

@ -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 * @class Renderer
* @inherits Layer * @inherits Layer
@ -18,7 +26,7 @@
* its map has moved * its map has moved
*/ */
L.Renderer = L.Layer.extend({ export var Renderer = Layer.extend({
// @section // @section
// @aka Renderer options // @aka Renderer options
@ -30,8 +38,8 @@ L.Renderer = L.Layer.extend({
}, },
initialize: function (options) { initialize: function (options) {
L.setOptions(this, options); Util.setOptions(this, options);
L.stamp(this); Util.stamp(this);
this._layers = this._layers || {}; this._layers = this._layers || {};
}, },
@ -40,7 +48,7 @@ L.Renderer = L.Layer.extend({
this._initContainer(); // defined by renderer implementations this._initContainer(); // defined by renderer implementations
if (this._zoomAnimated) { 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 () { onRemove: function () {
L.DomUtil.remove(this._container); DomUtil.remove(this._container);
this.off('update', this._updatePaths, this); this.off('update', this._updatePaths, this);
}, },
@ -77,7 +85,7 @@ L.Renderer = L.Layer.extend({
_updateTransform: function (center, zoom) { _updateTransform: function (center, zoom) {
var scale = this._map.getZoomScale(zoom, this._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), viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
currentCenterPoint = this._map.project(this._center, zoom), currentCenterPoint = this._map.project(this._center, zoom),
destCenterPoint = this._map.project(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); topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
if (L.Browser.any3d) { if (Browser.any3d) {
L.DomUtil.setTransform(this._container, topLeftOffset, scale); DomUtil.setTransform(this._container, topLeftOffset, scale);
} else { } 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(), size = this._map.getSize(),
min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 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._center = this._map.getCenter();
this._zoom = this._map.getZoom(); 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;
}
});

View File

@ -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';
}

View File

@ -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! * 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('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
return function (name) {
return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
}
})();
/* /*
* @class SVG * @class SVG
* *
@ -11,48 +30,31 @@
* with old versions of Internet Explorer. * with old versions of Internet Explorer.
*/ */
// @namespace Browser; @property vml: Boolean // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). export var vmlMixin = {
L.Browser.vml = !L.Browser.svg && (function () {
try {
var div = document.createElement('div');
div.innerHTML = '<v:shape adj="1"/>';
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 ? {} : {
_initContainer: function () { _initContainer: function () {
this._container = L.DomUtil.create('div', 'leaflet-vml-container'); this._container = DomUtil.create('div', 'leaflet-vml-container');
}, },
_update: function () { _update: function () {
if (this._map._animatingZoom) { return; } if (this._map._animatingZoom) { return; }
L.Renderer.prototype._update.call(this); Renderer.prototype._update.call(this);
this.fire('update'); this.fire('update');
}, },
_initPath: function (layer) { _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'; container.coordsize = '1 1';
layer._path = L.SVG.create('path'); layer._path = vmlCreate('path');
container.appendChild(layer._path); container.appendChild(layer._path);
this._updateStyle(layer); this._updateStyle(layer);
this._layers[L.stamp(layer)] = layer; this._layers[Util.stamp(layer)] = layer;
}, },
_addPath: function (layer) { _addPath: function (layer) {
@ -66,9 +68,9 @@ L.SVG.include(!L.Browser.vml ? {} : {
_removePath: function (layer) { _removePath: function (layer) {
var container = layer._container; var container = layer._container;
L.DomUtil.remove(container); DomUtil.remove(container);
layer.removeInteractiveTarget(container); layer.removeInteractiveTarget(container);
delete this._layers[L.stamp(layer)]; delete this._layers[Util.stamp(layer)];
}, },
_updateStyle: function (layer) { _updateStyle: function (layer) {
@ -82,7 +84,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
if (options.stroke) { if (options.stroke) {
if (!stroke) { if (!stroke) {
stroke = layer._stroke = L.SVG.create('stroke'); stroke = layer._stroke = vmlCreate('stroke');
} }
container.appendChild(stroke); container.appendChild(stroke);
stroke.weight = options.weight + 'px'; stroke.weight = options.weight + 'px';
@ -90,7 +92,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
stroke.opacity = options.opacity; stroke.opacity = options.opacity;
if (options.dashArray) { if (options.dashArray) {
stroke.dashStyle = L.Util.isArray(options.dashArray) ? stroke.dashStyle = Util.isArray(options.dashArray) ?
options.dashArray.join(' ') : options.dashArray.join(' ') :
options.dashArray.replace(/( *, *)/g, ' '); options.dashArray.replace(/( *, *)/g, ' ');
} else { } else {
@ -106,7 +108,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
if (options.fill) { if (options.fill) {
if (!fill) { if (!fill) {
fill = layer._fill = L.SVG.create('fill'); fill = layer._fill = vmlCreate('fill');
} }
container.appendChild(fill); container.appendChild(fill);
fill.color = options.fillColor || options.color; fill.color = options.fillColor || options.color;
@ -132,25 +134,10 @@ L.SVG.include(!L.Browser.vml ? {} : {
}, },
_bringToFront: function (layer) { _bringToFront: function (layer) {
L.DomUtil.toFront(layer._container); DomUtil.toFront(layer._container);
}, },
_bringToBack: function (layer) { _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('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
return function (name) {
return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
}
})();
}

View File

@ -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 * @class SVG
* @inherits Renderer * @inherits Renderer
@ -34,21 +43,21 @@
* ``` * ```
*/ */
L.SVG = L.Renderer.extend({ export var SVG = Renderer.extend({
getEvents: function () { getEvents: function () {
var events = L.Renderer.prototype.getEvents.call(this); var events = Renderer.prototype.getEvents.call(this);
events.zoomstart = this._onZoomStart; events.zoomstart = this._onZoomStart;
return events; return events;
}, },
_initContainer: function () { _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 // makes it possible to click through svg root; we'll reset it back in individual paths
this._container.setAttribute('pointer-events', 'none'); this._container.setAttribute('pointer-events', 'none');
this._rootGroup = L.SVG.create('g'); this._rootGroup = create('g');
this._container.appendChild(this._rootGroup); this._container.appendChild(this._rootGroup);
}, },
@ -62,7 +71,7 @@ L.SVG = L.Renderer.extend({
_update: function () { _update: function () {
if (this._map._animatingZoom && this._bounds) { return; } if (this._map._animatingZoom && this._bounds) { return; }
L.Renderer.prototype._update.call(this); Renderer.prototype._update.call(this);
var b = this._bounds, var b = this._bounds,
size = b.getSize(), 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 // 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(' ')); container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
this.fire('update'); this.fire('update');
@ -85,17 +94,17 @@ L.SVG = L.Renderer.extend({
// methods below are called by vector layers implementations // methods below are called by vector layers implementations
_initPath: function (layer) { _initPath: function (layer) {
var path = layer._path = L.SVG.create('path'); var path = layer._path = create('path');
// @namespace Path // @namespace Path
// @option className: String = null // @option className: String = null
// Custom class name set on an element. Only for SVG renderer. // Custom class name set on an element. Only for SVG renderer.
if (layer.options.className) { if (layer.options.className) {
L.DomUtil.addClass(path, layer.options.className); DomUtil.addClass(path, layer.options.className);
} }
if (layer.options.interactive) { if (layer.options.interactive) {
L.DomUtil.addClass(path, 'leaflet-interactive'); DomUtil.addClass(path, 'leaflet-interactive');
} }
this._updateStyle(layer); this._updateStyle(layer);
@ -108,7 +117,7 @@ L.SVG = L.Renderer.extend({
}, },
_removePath: function (layer) { _removePath: function (layer) {
L.DomUtil.remove(layer._path); DomUtil.remove(layer._path);
layer.removeInteractiveTarget(layer._path); layer.removeInteractiveTarget(layer._path);
delete this._layers[L.stamp(layer)]; delete this._layers[L.stamp(layer)];
}, },
@ -156,7 +165,7 @@ L.SVG = L.Renderer.extend({
}, },
_updatePoly: function (layer, closed) { _updatePoly: function (layer, closed) {
this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed)); this._setPath(layer, pointsToPath(layer._parts, closed));
}, },
_updateCircle: function (layer) { _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 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
_bringToFront: function (layer) { _bringToFront: function (layer) {
L.DomUtil.toFront(layer._path); DomUtil.toFront(layer._path);
}, },
_bringToBack: function (layer) { _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) // @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options. // Creates a SVG renderer with the given options.
L.svg = function (options) { export function svg(options) {
return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null; return Browser.svg || Browser.vml ? new SVG(options) : null;
}; }

View File

@ -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 * @class Map
* @aka L.Map * @aka L.Map
@ -17,14 +29,14 @@
* *
*/ */
L.Map = L.Evented.extend({ export var Map = Evented.extend({
options: { options: {
// @section Map State Options // @section Map State Options
// @option crs: CRS = L.CRS.EPSG3857 // @option crs: CRS = L.CRS.EPSG3857
// The [Coordinate Reference System](#crs) to use. Don't change this if you're not // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
// sure what it means. // sure what it means.
crs: L.CRS.EPSG3857, crs: EPSG3857,
// @option center: LatLng = undefined // @option center: LatLng = undefined
// Initial geographic center of the map // Initial geographic center of the map
@ -108,13 +120,13 @@ L.Map = L.Evented.extend({
}, },
initialize: function (id, options) { // (HTMLElement or String, Object) initialize: function (id, options) { // (HTMLElement or String, Object)
options = L.setOptions(this, options); options = Util.setOptions(this, options);
this._initContainer(id); this._initContainer(id);
this._initLayout(); this._initLayout();
// hack for https://github.com/Leaflet/Leaflet/issues/1980 // 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(); this._initEvents();
@ -127,7 +139,7 @@ L.Map = L.Evented.extend({
} }
if (options.center && options.zoom !== undefined) { 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 = []; this._handlers = [];
@ -138,14 +150,14 @@ L.Map = L.Evented.extend({
this.callInitHooks(); this.callInitHooks();
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera // 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; this.options.zoomAnimation;
// zoom transitions run with the same duration for all layers, so if one of transitionend events // 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 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
if (this._zoomAnimated) { if (this._zoomAnimated) {
this._createAnimProxy(); 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); this._addLayers(this.options.layers);
@ -160,7 +172,7 @@ L.Map = L.Evented.extend({
setView: function (center, zoom, options) { setView: function (center, zoom, options) {
zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); 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 || {}; options = options || {};
this._stop(); this._stop();
@ -168,8 +180,8 @@ L.Map = L.Evented.extend({
if (this._loaded && !options.reset && options !== true) { if (this._loaded && !options.reset && options !== true) {
if (options.animate !== undefined) { if (options.animate !== undefined) {
options.zoom = L.extend({animate: options.animate}, options.zoom); options.zoom = Util.extend({animate: options.animate}, options.zoom);
options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan); options.pan = Util.extend({animate: options.animate, duration: options.duration}, options.pan);
} }
// try animating pan or zoom // try animating pan or zoom
@ -203,14 +215,14 @@ L.Map = L.Evented.extend({
// @method zoomIn(delta?: Number, options?: Zoom options): this // @method zoomIn(delta?: Number, options?: Zoom options): this
// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
zoomIn: function (delta, options) { 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); return this.setZoom(this._zoom + delta, options);
}, },
// @method zoomOut(delta?: Number, options?: Zoom options): this // @method zoomOut(delta?: Number, options?: Zoom options): this
// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
zoomOut: function (delta, options) { 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); return this.setZoom(this._zoom - delta, options);
}, },
@ -223,7 +235,7 @@ L.Map = L.Evented.extend({
setZoomAround: function (latlng, zoom, options) { setZoomAround: function (latlng, zoom, options) {
var scale = this.getZoomScale(zoom), var scale = this.getZoomScale(zoom),
viewHalf = this.getSize().divideBy(2), 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), centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
@ -234,10 +246,10 @@ L.Map = L.Evented.extend({
_getBoundsCenterZoom: function (bounds, options) { _getBoundsCenterZoom: function (bounds, options) {
options = 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]), var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]), paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
@ -260,7 +272,7 @@ L.Map = L.Evented.extend({
// maximum zoom level possible. // maximum zoom level possible.
fitBounds: function (bounds, options) { fitBounds: function (bounds, options) {
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
if (!bounds.isValid()) { if (!bounds.isValid()) {
throw new Error('Bounds are not valid.'); throw new Error('Bounds are not valid.');
@ -286,7 +298,7 @@ L.Map = L.Evented.extend({
// @method panBy(offset: Point): this // @method panBy(offset: Point): this
// Pans the map by a given number of pixels (animated). // Pans the map by a given number of pixels (animated).
panBy: function (offset, options) { panBy: function (offset, options) {
offset = L.point(offset).round(); offset = toPoint(offset).round();
options = options || {}; options = options || {};
if (!offset.x && !offset.y) { if (!offset.x && !offset.y) {
@ -300,7 +312,7 @@ L.Map = L.Evented.extend({
} }
if (!this._panAnim) { if (!this._panAnim) {
this._panAnim = new L.PosAnimation(); this._panAnim = new PosAnimation();
this._panAnim.on({ this._panAnim.on({
'step': this._onPanTransitionStep, 'step': this._onPanTransitionStep,
@ -315,7 +327,7 @@ L.Map = L.Evented.extend({
// animate pan unless animate: false specified // animate pan unless animate: false specified
if (options.animate !== false) { 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(); var newPos = this._getMapPanePos().subtract(offset).round();
this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 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) { flyTo: function (targetCenter, targetZoom, options) {
options = options || {}; options = options || {};
if (options.animate === false || !L.Browser.any3d) { if (options.animate === false || !Browser.any3d) {
return this.setView(targetCenter, targetZoom, options); return this.setView(targetCenter, targetZoom, options);
} }
@ -344,7 +356,7 @@ L.Map = L.Evented.extend({
size = this.getSize(), size = this.getSize(),
startZoom = this._zoom; startZoom = this._zoom;
targetCenter = L.latLng(targetCenter); targetCenter = toLatLng(targetCenter);
targetZoom = targetZoom === undefined ? startZoom : targetZoom; targetZoom = targetZoom === undefined ? startZoom : targetZoom;
var w0 = Math.max(size.x, size.y), var w0 = Math.max(size.x, size.y),
@ -388,7 +400,7 @@ L.Map = L.Evented.extend({
s = easeOut(t) * S; s = easeOut(t) * S;
if (t <= 1) { if (t <= 1) {
this._flyToFrame = L.Util.requestAnimFrame(frame, this); this._flyToFrame = Util.requestAnimFrame(frame, this);
this._move( this._move(
this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 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 // @method setMaxBounds(bounds: Bounds): this
// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
setMaxBounds: function (bounds) { setMaxBounds: function (bounds) {
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
if (!bounds.isValid()) { if (!bounds.isValid()) {
this.options.maxBounds = null; this.options.maxBounds = null;
@ -466,7 +478,7 @@ L.Map = L.Evented.extend({
panInsideBounds: function (bounds, options) { panInsideBounds: function (bounds, options) {
this._enforcingBounds = true; this._enforcingBounds = true;
var center = this.getCenter(), 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)) { if (!center.equals(newCenter)) {
this.panTo(newCenter, options); this.panTo(newCenter, options);
@ -492,7 +504,7 @@ L.Map = L.Evented.extend({
invalidateSize: function (options) { invalidateSize: function (options) {
if (!this._loaded) { return this; } if (!this._loaded) { return this; }
options = L.extend({ options = Util.extend({
animate: false, animate: false,
pan: true pan: true
}, options === true ? {animate: true} : options); }, options === true ? {animate: true} : options);
@ -520,7 +532,7 @@ L.Map = L.Evented.extend({
if (options.debounceMoveend) { if (options.debounceMoveend) {
clearTimeout(this._sizeTimer); clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); this._sizeTimer = setTimeout(Util.bind(this.fire, this, 'moveend'), 200);
} else { } else {
this.fire('moveend'); this.fire('moveend');
} }
@ -557,7 +569,7 @@ L.Map = L.Evented.extend({
// See `Locate options` for more details. // See `Locate options` for more details.
locate: function (options) { locate: function (options) {
options = this._locateOptions = L.extend({ options = this._locateOptions = Util.extend({
timeout: 10000, timeout: 10000,
watch: false watch: false
// setView: false // setView: false
@ -574,8 +586,8 @@ L.Map = L.Evented.extend({
return this; return this;
} }
var onResponse = L.bind(this._handleGeolocationResponse, this), var onResponse = Util.bind(this._handleGeolocationResponse, this),
onError = L.bind(this._handleGeolocationError, this); onError = Util.bind(this._handleGeolocationError, this);
if (options.watch) { if (options.watch) {
this._locationWatchId = this._locationWatchId =
@ -622,7 +634,7 @@ L.Map = L.Evented.extend({
_handleGeolocationResponse: function (pos) { _handleGeolocationResponse: function (pos) {
var lat = pos.coords.latitude, var lat = pos.coords.latitude,
lng = pos.coords.longitude, lng = pos.coords.longitude,
latlng = new L.LatLng(lat, lng), latlng = new LatLng(lat, lng),
bounds = latlng.toBounds(pos.coords.accuracy), bounds = latlng.toBounds(pos.coords.accuracy),
options = this._locateOptions; options = this._locateOptions;
@ -689,7 +701,7 @@ L.Map = L.Evented.extend({
this._containerId = undefined; this._containerId = undefined;
} }
L.DomUtil.remove(this._mapPane); DomUtil.remove(this._mapPane);
if (this._clearControlPos) { if (this._clearControlPos) {
this._clearControlPos(); this._clearControlPos();
@ -718,7 +730,7 @@ L.Map = L.Evented.extend({
// as a children of the main map pane if not set. // as a children of the main map pane if not set.
createPane: function (name, container) { createPane: function (name, container) {
var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''), 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) { if (name) {
this._panes[name] = pane; this._panes[name] = pane;
@ -752,7 +764,7 @@ L.Map = L.Evented.extend({
sw = this.unproject(bounds.getBottomLeft()), sw = this.unproject(bounds.getBottomLeft()),
ne = this.unproject(bounds.getTopRight()); ne = this.unproject(bounds.getTopRight());
return new L.LatLngBounds(sw, ne); return new LatLngBounds(sw, ne);
}, },
// @method getMinZoom(): Number // @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 // instead returns the minimum zoom level on which the map view fits into
// the given bounds in its entirety. // the given bounds in its entirety.
getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
bounds = L.latLngBounds(bounds); bounds = toLatLngBounds(bounds);
padding = L.point(padding || [0, 0]); padding = toPoint(padding || [0, 0]);
var zoom = this.getZoom() || 0, var zoom = this.getZoom() || 0,
min = this.getMinZoom(), min = this.getMinZoom(),
@ -784,8 +796,8 @@ L.Map = L.Evented.extend({
nw = bounds.getNorthWest(), nw = bounds.getNorthWest(),
se = bounds.getSouthEast(), se = bounds.getSouthEast(),
size = this.getSize().subtract(padding), size = this.getSize().subtract(padding),
boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(), boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
snap = L.Browser.any3d ? this.options.zoomSnap : 1; snap = Browser.any3d ? this.options.zoomSnap : 1;
var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y); var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
zoom = this.getScaleZoom(scale, zoom); zoom = this.getScaleZoom(scale, zoom);
@ -802,7 +814,7 @@ L.Map = L.Evented.extend({
// Returns the current size of the map container (in pixels). // Returns the current size of the map container (in pixels).
getSize: function () { getSize: function () {
if (!this._size || this._sizeChanged) { if (!this._size || this._sizeChanged) {
this._size = new L.Point( this._size = new Point(
this._container.clientWidth || 0, this._container.clientWidth || 0,
this._container.clientHeight || 0); this._container.clientHeight || 0);
@ -816,7 +828,7 @@ L.Map = L.Evented.extend({
// coordinates (sometimes useful in layer and overlay implementations). // coordinates (sometimes useful in layer and overlay implementations).
getPixelBounds: function (center, zoom) { getPixelBounds: function (center, zoom) {
var topLeftPoint = this._getTopLeftPoint(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 // 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. // the CRS origin.
project: function (latlng, zoom) { project: function (latlng, zoom) {
zoom = zoom === undefined ? this._zoom : 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 // @method unproject(point: Point, zoom: Number): LatLng
// Inverse of [`project`](#map-project). // Inverse of [`project`](#map-project).
unproject: function (point, zoom) { unproject: function (point, zoom) {
zoom = zoom === undefined ? this._zoom : 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 // @method layerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding geographical coordinate (for the current zoom level). // returns the corresponding geographical coordinate (for the current zoom level).
layerPointToLatLng: function (point) { layerPointToLatLng: function (point) {
var projectedPoint = L.point(point).add(this.getPixelOrigin()); var projectedPoint = toPoint(point).add(this.getPixelOrigin());
return this.unproject(projectedPoint); return this.unproject(projectedPoint);
}, },
@ -911,7 +923,7 @@ L.Map = L.Evented.extend({
// Given a geographical coordinate, returns the corresponding pixel coordinate // Given a geographical coordinate, returns the corresponding pixel coordinate
// relative to the [origin pixel](#map-getpixelorigin). // relative to the [origin pixel](#map-getpixelorigin).
latLngToLayerPoint: function (latlng) { latLngToLayerPoint: function (latlng) {
var projectedPoint = this.project(L.latLng(latlng))._round(); var projectedPoint = this.project(toLatLng(latlng))._round();
return projectedPoint._subtract(this.getPixelOrigin()); 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 // By default this means longitude is wrapped around the dateline so its
// value is between -180 and +180 degrees. // value is between -180 and +180 degrees.
wrapLatLng: function (latlng) { wrapLatLng: function (latlng) {
return this.options.crs.wrapLatLng(L.latLng(latlng)); return this.options.crs.wrapLatLng(toLatLng(latlng));
}, },
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
@ -939,28 +951,28 @@ L.Map = L.Evented.extend({
// Returns the distance between two geographical coordinates according to // Returns the distance between two geographical coordinates according to
// the map's CRS. By default this measures distance in meters. // the map's CRS. By default this measures distance in meters.
distance: function (latlng1, latlng2) { 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 // @method containerPointToLayerPoint(point: Point): Point
// Given a pixel coordinate relative to the map container, returns the corresponding // Given a pixel coordinate relative to the map container, returns the corresponding
// pixel coordinate relative to the [origin pixel](#map-getpixelorigin). // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
containerPointToLayerPoint: function (point) { // (Point) containerPointToLayerPoint: function (point) { // (Point)
return L.point(point).subtract(this._getMapPanePos()); return toPoint(point).subtract(this._getMapPanePos());
}, },
// @method layerPointToContainerPoint(point: Point): Point // @method layerPointToContainerPoint(point: Point): Point
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding pixel coordinate relative to the map container. // returns the corresponding pixel coordinate relative to the map container.
layerPointToContainerPoint: function (point) { // (Point) layerPointToContainerPoint: function (point) { // (Point)
return L.point(point).add(this._getMapPanePos()); return toPoint(point).add(this._getMapPanePos());
}, },
// @method containerPointToLatLng(point: Point): LatLng // @method containerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the map container, returns // Given a pixel coordinate relative to the map container, returns
// the corresponding geographical coordinate (for the current zoom level). // the corresponding geographical coordinate (for the current zoom level).
containerPointToLatLng: function (point) { containerPointToLatLng: function (point) {
var layerPoint = this.containerPointToLayerPoint(L.point(point)); var layerPoint = this.containerPointToLayerPoint(toPoint(point));
return this.layerPointToLatLng(layerPoint); return this.layerPointToLatLng(layerPoint);
}, },
@ -968,14 +980,14 @@ L.Map = L.Evented.extend({
// Given a geographical coordinate, returns the corresponding pixel coordinate // Given a geographical coordinate, returns the corresponding pixel coordinate
// relative to the map container. // relative to the map container.
latLngToContainerPoint: function (latlng) { latLngToContainerPoint: function (latlng) {
return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng))); return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
}, },
// @method mouseEventToContainerPoint(ev: MouseEvent): Point // @method mouseEventToContainerPoint(ev: MouseEvent): Point
// Given a MouseEvent object, returns the pixel coordinate relative to the // Given a MouseEvent object, returns the pixel coordinate relative to the
// map container where the event took place. // map container where the event took place.
mouseEventToContainerPoint: function (e) { mouseEventToContainerPoint: function (e) {
return L.DomEvent.getMousePosition(e, this._container); return DomEvent.getMousePosition(e, this._container);
}, },
// @method mouseEventToLayerPoint(ev: MouseEvent): Point // @method mouseEventToLayerPoint(ev: MouseEvent): Point
@ -996,7 +1008,7 @@ L.Map = L.Evented.extend({
// map initialization methods // map initialization methods
_initContainer: function (id) { _initContainer: function (id) {
var container = this._container = L.DomUtil.get(id); var container = this._container = DomUtil.get(id);
if (!container) { if (!container) {
throw new Error('Map container not found.'); throw new Error('Map container not found.');
@ -1004,23 +1016,23 @@ L.Map = L.Evented.extend({
throw new Error('Map container is already initialized.'); throw new Error('Map container is already initialized.');
} }
L.DomEvent.addListener(container, 'scroll', this._onScroll, this); DomEvent.on(container, 'scroll', this._onScroll, this);
this._containerId = L.Util.stamp(container); this._containerId = Util.stamp(container);
}, },
_initLayout: function () { _initLayout: function () {
var container = this._container; 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' + DomUtil.addClass(container, 'leaflet-container' +
(L.Browser.touch ? ' leaflet-touch' : '') + (Browser.touch ? ' leaflet-touch' : '') +
(L.Browser.retina ? ' leaflet-retina' : '') + (Browser.retina ? ' leaflet-retina' : '') +
(L.Browser.ielt9 ? ' leaflet-oldie' : '') + (Browser.ielt9 ? ' leaflet-oldie' : '') +
(L.Browser.safari ? ' leaflet-safari' : '') + (Browser.safari ? ' leaflet-safari' : '') +
(this._fadeAnimated ? ' leaflet-fade-anim' : '')); (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') { if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
container.style.position = 'relative'; container.style.position = 'relative';
@ -1050,7 +1062,7 @@ L.Map = L.Evented.extend({
// Pane that contains all other map panes // Pane that contains all other map panes
this._mapPane = this.createPane('mapPane', this._container); 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 tilePane: HTMLElement = 200
// Pane for `GridLayer`s and `TileLayer`s // Pane for `GridLayer`s and `TileLayer`s
@ -1072,8 +1084,8 @@ L.Map = L.Evented.extend({
this.createPane('popupPane'); this.createPane('popupPane');
if (!this.options.markerZoomAnimation) { if (!this.options.markerZoomAnimation) {
L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
} }
}, },
@ -1082,7 +1094,7 @@ L.Map = L.Evented.extend({
// @section Map state change events // @section Map state change events
_resetView: function (center, zoom) { _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; var loading = !this._loaded;
this._loaded = true; this._loaded = true;
@ -1157,7 +1169,7 @@ L.Map = L.Evented.extend({
}, },
_stop: function () { _stop: function () {
L.Util.cancelAnimFrame(this._flyToFrame); Util.cancelAnimFrame(this._flyToFrame);
if (this._panAnim) { if (this._panAnim) {
this._panAnim.stop(); this._panAnim.stop();
} }
@ -1165,7 +1177,7 @@ L.Map = L.Evented.extend({
}, },
_rawPanBy: function (offset) { _rawPanBy: function (offset) {
L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
}, },
_getZoomSpan: function () { _getZoomSpan: function () {
@ -1188,12 +1200,10 @@ L.Map = L.Evented.extend({
// @section Interaction events // @section Interaction events
_initEvents: function (remove) { _initEvents: function (remove) {
if (!L.DomEvent) { return; }
this._targets = {}; 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 // @event click: MouseEvent
// Fired when the user clicks (or taps) the map. // 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). // for a second (also called long press).
// @event keypress: KeyboardEvent // @event keypress: KeyboardEvent
// Fired when the user presses a key from the keyboard while the map is focused. // 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); 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
if (this.options.trackResize) { 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) { if (Browser.any3d && this.options.transform3DLimit) {
this[onOff]('moveend', this._onMoveEnd); (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
} }
}, },
_onResize: function () { _onResize: function () {
L.Util.cancelAnimFrame(this._resizeRequest); Util.cancelAnimFrame(this._resizeRequest);
this._resizeRequest = L.Util.requestAnimFrame( this._resizeRequest = Util.requestAnimFrame(
function () { this.invalidateSize({debounceMoveend: true}); }, this); function () { this.invalidateSize({debounceMoveend: true}); }, this);
}, },
@ -1256,34 +1266,34 @@ L.Map = L.Evented.extend({
dragging = false; dragging = false;
while (src) { 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)) { if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
// Prevent firing click after you just dragged an object. // Prevent firing click after you just dragged an object.
dragging = true; dragging = true;
break; break;
} }
if (target && target.listens(type, true)) { if (target && target.listens(type, true)) {
if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; } if (isHover && !DomEvent.isExternalTarget(src, e)) { break; }
targets.push(target); targets.push(target);
if (isHover) { break; } if (isHover) { break; }
} }
if (src === this._container) { break; } if (src === this._container) { break; }
src = src.parentNode; src = src.parentNode;
} }
if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) { if (!targets.length && !dragging && !isHover && DomEvent.isExternalTarget(src, e)) {
targets = [this]; targets = [this];
} }
return targets; return targets;
}, },
_handleDOMEvent: function (e) { _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; var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
if (type === 'mousedown') { if (type === 'mousedown') {
// prevents outline when clicking on keyboard-focusable element // 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); this._fireDOMEvent(e, type);
@ -1297,7 +1307,7 @@ L.Map = L.Evented.extend({
// Fired before mouse click on the map (sometimes useful when you // Fired before mouse click on the map (sometimes useful when you
// want something to happen on click before any existing click // want something to happen on click before any existing click
// handlers start running). // handlers start running).
var synth = L.Util.extend({}, e); var synth = Util.extend({}, e);
synth.type = 'preclick'; synth.type = 'preclick';
this._fireDOMEvent(synth, synth.type, targets); this._fireDOMEvent(synth, synth.type, targets);
} }
@ -1311,7 +1321,7 @@ L.Map = L.Evented.extend({
var target = targets[0]; var target = targets[0];
if (type === 'contextmenu' && target.listens(type, true)) { if (type === 'contextmenu' && target.listens(type, true)) {
L.DomEvent.preventDefault(e); DomEvent.preventDefault(e);
} }
var data = { var data = {
@ -1319,7 +1329,7 @@ L.Map = L.Evented.extend({
}; };
if (e.type !== 'keypress') { if (e.type !== 'keypress') {
var isMarker = target instanceof L.Marker; var isMarker = (target.options && 'icon' in target.options);
data.containerPoint = isMarker ? data.containerPoint = isMarker ?
this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
@ -1329,7 +1339,7 @@ L.Map = L.Evented.extend({
for (var i = 0; i < targets.length; i++) { for (var i = 0; i < targets.length; i++) {
targets[i].fire(type, data, true); targets[i].fire(type, data, true);
if (data.originalEvent._stopped || 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 // private methods for getting map state
_getMapPanePos: function () { _getMapPanePos: function () {
return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); return DomUtil.getPosition(this._mapPane) || new Point(0, 0);
}, },
_moved: function () { _moved: function () {
@ -1390,7 +1400,7 @@ L.Map = L.Evented.extend({
_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) { _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
var topLeft = this._getNewPixelOrigin(center, zoom); var topLeft = this._getNewPixelOrigin(center, zoom);
return L.bounds([ return toBounds([
this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft), this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getSouthEast(), 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), var centerPoint = this.project(center, zoom),
viewHalf = this.getSize().divideBy(2), 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); offset = this._getBoundsOffset(viewBounds, bounds, zoom);
// If offset is less than a pixel, ignore. // If offset is less than a pixel, ignore.
@ -1433,14 +1443,14 @@ L.Map = L.Evented.extend({
if (!bounds) { return offset; } if (!bounds) { return offset; }
var viewBounds = this.getPixelBounds(), 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)); return offset.add(this._getBoundsOffset(newBounds, bounds));
}, },
// returns offset needed for pxBounds to get inside maxBounds at a specified zoom // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
_getBoundsOffset: function (pxBounds, maxBounds, zoom) { _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
var projectedMaxBounds = L.bounds( var projectedMaxBounds = toBounds(
this.project(maxBounds.getNorthEast(), zoom), this.project(maxBounds.getNorthEast(), zoom),
this.project(maxBounds.getSouthWest(), zoom) this.project(maxBounds.getSouthWest(), zoom)
), ),
@ -1450,7 +1460,7 @@ L.Map = L.Evented.extend({
dx = this._rebound(minOffset.x, -maxOffset.x), dx = this._rebound(minOffset.x, -maxOffset.x),
dy = this._rebound(minOffset.y, -maxOffset.y); dy = this._rebound(minOffset.y, -maxOffset.y);
return new L.Point(dx, dy); return new Point(dx, dy);
}, },
_rebound: function (left, right) { _rebound: function (left, right) {
@ -1462,7 +1472,7 @@ L.Map = L.Evented.extend({
_limitZoom: function (zoom) { _limitZoom: function (zoom) {
var min = this.getMinZoom(), var min = this.getMinZoom(),
max = this.getMaxZoom(), max = this.getMaxZoom(),
snap = L.Browser.any3d ? this.options.zoomSnap : 1; snap = Browser.any3d ? this.options.zoomSnap : 1;
if (snap) { if (snap) {
zoom = Math.round(zoom / snap) * snap; zoom = Math.round(zoom / snap) * snap;
} }
@ -1474,7 +1484,7 @@ L.Map = L.Evented.extend({
}, },
_onPanTransitionEnd: function () { _onPanTransitionEnd: function () {
L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim'); DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
this.fire('moveend'); this.fire('moveend');
}, },
@ -1492,14 +1502,14 @@ L.Map = L.Evented.extend({
_createAnimProxy: function () { _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._panes.mapPane.appendChild(proxy);
this.on('zoomanim', function (e) { this.on('zoomanim', function (e) {
var prop = L.DomUtil.TRANSFORM, var prop = DomUtil.TRANSFORM,
transform = proxy.style[prop]; 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 // workaround for case when transform is the same and so transitionend event is not fired
if (transform === proxy.style[prop] && this._animatingZoom) { if (transform === proxy.style[prop] && this._animatingZoom) {
@ -1510,7 +1520,7 @@ L.Map = L.Evented.extend({
this.on('load moveend', function () { this.on('load moveend', function () {
var c = this.getCenter(), var c = this.getCenter(),
z = this.getZoom(); 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); }, 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 // 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; } if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
L.Util.requestAnimFrame(function () { Util.requestAnimFrame(function () {
this this
._moveStart(true) ._moveStart(true)
._animateZoom(center, zoom, true); ._animateZoom(center, zoom, true);
@ -1558,7 +1568,7 @@ L.Map = L.Evented.extend({
this._animateToCenter = center; this._animateToCenter = center;
this._animateToZoom = zoom; this._animateToZoom = zoom;
L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
} }
// @event zoomanim: ZoomAnimEvent // @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 // 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 () { _onZoomTransitionEnd: function () {
if (!this._animatingZoom) { return; } if (!this._animatingZoom) { return; }
L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
this._animatingZoom = false; this._animatingZoom = false;
this._move(this._animateToCenter, this._animateToZoom); this._move(this._animateToCenter, this._animateToZoom);
// This anim frame should prevent an obscure iOS webkit tile loading race condition. // This anim frame should prevent an obscure iOS webkit tile loading race condition.
L.Util.requestAnimFrame(function () { Util.requestAnimFrame(function () {
this._moveEnd(true); this._moveEnd(true);
}, this); }, this);
} }
@ -1599,6 +1609,6 @@ L.Map = L.Evented.extend({
// @factory L.map(el: HTMLElement, options?: Map options) // @factory L.map(el: HTMLElement, options?: Map options)
// Instantiates a map object given an instance of a `<div>` HTML element // Instantiates a map object given an instance of a `<div>` HTML element
// and optionally an object literal with `Map options`. // and optionally an object literal with `Map options`.
L.map = function (id, options) { export function createMap(id, options) {
return new L.Map(id, options); return new Map(id, options);
}; }

View File

@ -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 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
* (zoom to a selected bounding box), enabled by default. * (zoom to a selected bounding box), enabled by default.
@ -5,14 +13,14 @@
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @option boxZoom: Boolean = true // @option boxZoom: Boolean = true
// Whether the map can be zoomed to a rectangular area specified by // Whether the map can be zoomed to a rectangular area specified by
// dragging the mouse while pressing the shift key. // dragging the mouse while pressing the shift key.
boxZoom: true boxZoom: true
}); });
L.Map.BoxZoom = L.Handler.extend({ export var BoxZoom = Handler.extend({
initialize: function (map) { initialize: function (map) {
this._map = map; this._map = map;
this._container = map._container; this._container = map._container;
@ -20,11 +28,11 @@ L.Map.BoxZoom = L.Handler.extend({
}, },
addHooks: function () { addHooks: function () {
L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this); DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
}, },
removeHooks: function () { removeHooks: function () {
L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this); DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
}, },
moved: function () { moved: function () {
@ -40,13 +48,13 @@ L.Map.BoxZoom = L.Handler.extend({
this._resetState(); this._resetState();
L.DomUtil.disableTextSelection(); DomUtil.disableTextSelection();
L.DomUtil.disableImageDrag(); DomUtil.disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e); this._startPoint = this._map.mouseEventToContainerPoint(e);
L.DomEvent.on(document, { DomEvent.on(document, {
contextmenu: L.DomEvent.stop, contextmenu: DomEvent.stop,
mousemove: this._onMouseMove, mousemove: this._onMouseMove,
mouseup: this._onMouseUp, mouseup: this._onMouseUp,
keydown: this._onKeyDown keydown: this._onKeyDown
@ -57,18 +65,18 @@ L.Map.BoxZoom = L.Handler.extend({
if (!this._moved) { if (!this._moved) {
this._moved = true; this._moved = true;
this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container); this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
L.DomUtil.addClass(this._container, 'leaflet-crosshair'); DomUtil.addClass(this._container, 'leaflet-crosshair');
this._map.fire('boxzoomstart'); this._map.fire('boxzoomstart');
} }
this._point = this._map.mouseEventToContainerPoint(e); 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(); 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.width = size.x + 'px';
this._box.style.height = size.y + 'px'; this._box.style.height = size.y + 'px';
@ -76,15 +84,15 @@ L.Map.BoxZoom = L.Handler.extend({
_finish: function () { _finish: function () {
if (this._moved) { if (this._moved) {
L.DomUtil.remove(this._box); DomUtil.remove(this._box);
L.DomUtil.removeClass(this._container, 'leaflet-crosshair'); DomUtil.removeClass(this._container, 'leaflet-crosshair');
} }
L.DomUtil.enableTextSelection(); DomUtil.enableTextSelection();
L.DomUtil.enableImageDrag(); DomUtil.enableImageDrag();
L.DomEvent.off(document, { DomEvent.off(document, {
contextmenu: L.DomEvent.stop, contextmenu: DomEvent.stop,
mousemove: this._onMouseMove, mousemove: this._onMouseMove,
mouseup: this._onMouseUp, mouseup: this._onMouseUp,
keydown: this._onKeyDown keydown: this._onKeyDown
@ -99,9 +107,9 @@ L.Map.BoxZoom = L.Handler.extend({
if (!this._moved) { return; } if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling // Postpone to next JS tick so internal click event handling
// still see it as "moved". // 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._startPoint),
this._map.containerPointToLatLng(this._point)); this._map.containerPointToLatLng(this._point));
@ -120,4 +128,4 @@ L.Map.BoxZoom = L.Handler.extend({
// @section Handlers // @section Handlers
// @property boxZoom: Handler // @property boxZoom: Handler
// Box (shift-drag with mouse) zoom handler. // Box (shift-drag with mouse) zoom handler.
L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); Map.addInitHook('addHandler', 'boxZoom', BoxZoom);

View File

@ -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. * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
*/ */
@ -5,7 +8,7 @@
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @option doubleClickZoom: Boolean|String = true // @option doubleClickZoom: Boolean|String = true
// Whether the map can be zoomed in by double clicking on it and // Whether the map can be zoomed in by double clicking on it and
// zoomed out by double clicking while holding shift. If passed // zoomed out by double clicking while holding shift. If passed
@ -14,7 +17,7 @@ L.Map.mergeOptions({
doubleClickZoom: true doubleClickZoom: true
}); });
L.Map.DoubleClickZoom = L.Handler.extend({ export var DoubleClickZoom = Handler.extend({
addHooks: function () { addHooks: function () {
this._map.on('dblclick', this._onDoubleClick, this); this._map.on('dblclick', this._onDoubleClick, this);
}, },
@ -49,4 +52,4 @@ L.Map.DoubleClickZoom = L.Handler.extend({
// //
// @property doubleClickZoom: Handler // @property doubleClickZoom: Handler
// Double click zoom handler. // Double click zoom handler.
L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);

View File

@ -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. * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
*/ */
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @option dragging: Boolean = true // @option dragging: Boolean = true
// Whether the map be draggable with mouse/touch or not. // Whether the map be draggable with mouse/touch or not.
dragging: true, dragging: true,
@ -15,7 +24,7 @@ L.Map.mergeOptions({
// the map builds momentum while dragging and continues moving in // the map builds momentum while dragging and continues moving in
// the same direction for some time. Feels especially nice on touch // the same direction for some time. Feels especially nice on touch
// devices. Enabled by default unless running on old Android devices. // devices. Enabled by default unless running on old Android devices.
inertia: !L.Browser.android23, inertia: !Browser.android23,
// @option inertiaDeceleration: Number = 3000 // @option inertiaDeceleration: Number = 3000
// The rate with which the inertial movement slows down, in pixels/second². // The rate with which the inertial movement slows down, in pixels/second².
@ -44,12 +53,12 @@ L.Map.mergeOptions({
maxBoundsViscosity: 0.0 maxBoundsViscosity: 0.0
}); });
L.Map.Drag = L.Handler.extend({ export var Drag = Handler.extend({
addHooks: function () { addHooks: function () {
if (!this._draggable) { if (!this._draggable) {
var map = this._map; var map = this._map;
this._draggable = new L.Draggable(map._mapPane, map._container); this._draggable = new Draggable(map._mapPane, map._container);
this._draggable.on({ this._draggable.on({
down: this._onDown, down: this._onDown,
@ -66,15 +75,15 @@ L.Map.Drag = L.Handler.extend({
map.whenReady(this._onZoomEnd, this); 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._draggable.enable();
this._positions = []; this._positions = [];
this._times = []; this._times = [];
}, },
removeHooks: function () { removeHooks: function () {
L.DomUtil.removeClass(this._map._container, 'leaflet-grab'); DomUtil.removeClass(this._map._container, 'leaflet-grab');
L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag'); DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
this._draggable.disable(); this._draggable.disable();
}, },
@ -94,9 +103,9 @@ L.Map.Drag = L.Handler.extend({
var map = this._map; var map = this._map;
if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 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.getNorthWest()).multiplyBy(-1),
this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1) this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
.add(this._map.getSize())); .add(this._map.getSize()));
@ -207,7 +216,7 @@ L.Map.Drag = L.Handler.extend({
} else { } else {
offset = map._limitOffset(offset, map.options.maxBounds); offset = map._limitOffset(offset, map.options.maxBounds);
L.Util.requestAnimFrame(function () { Util.requestAnimFrame(function () {
map.panBy(offset, { map.panBy(offset, {
duration: decelerationDuration, duration: decelerationDuration,
easeLinearity: ease, easeLinearity: ease,
@ -223,4 +232,4 @@ L.Map.Drag = L.Handler.extend({
// @section Handlers // @section Handlers
// @property dragging: Handler // @property dragging: Handler
// Map dragging handler (by both mouse and touch). // Map dragging handler (by both mouse and touch).
L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); Map.addInitHook('addHandler', 'dragging', Drag);

View File

@ -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. * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
*/ */
// @namespace Map // @namespace Map
// @section Keyboard Navigation Options // @section Keyboard Navigation Options
L.Map.mergeOptions({ Map.mergeOptions({
// @option keyboard: Boolean = true // @option keyboard: Boolean = true
// Makes the map focusable and allows users to navigate the map with keyboard // Makes the map focusable and allows users to navigate the map with keyboard
// arrows and `+`/`-` keys. // arrows and `+`/`-` keys.
@ -15,7 +21,7 @@ L.Map.mergeOptions({
keyboardPanDelta: 80 keyboardPanDelta: 80
}); });
L.Map.Keyboard = L.Handler.extend({ export var Keyboard = Handler.extend({
keyCodes: { keyCodes: {
left: [37], left: [37],
@ -41,7 +47,7 @@ L.Map.Keyboard = L.Handler.extend({
container.tabIndex = '0'; container.tabIndex = '0';
} }
L.DomEvent.on(container, { on(container, {
focus: this._onFocus, focus: this._onFocus,
blur: this._onBlur, blur: this._onBlur,
mousedown: this._onMouseDown mousedown: this._onMouseDown
@ -56,7 +62,7 @@ L.Map.Keyboard = L.Handler.extend({
removeHooks: function () { removeHooks: function () {
this._removeHooks(); this._removeHooks();
L.DomEvent.off(this._map._container, { off(this._map._container, {
focus: this._onFocus, focus: this._onFocus,
blur: this._onBlur, blur: this._onBlur,
mousedown: this._onMouseDown mousedown: this._onMouseDown
@ -124,11 +130,11 @@ L.Map.Keyboard = L.Handler.extend({
}, },
_addHooks: function () { _addHooks: function () {
L.DomEvent.on(document, 'keydown', this._onKeyDown, this); on(document, 'keydown', this._onKeyDown, this);
}, },
_removeHooks: function () { _removeHooks: function () {
L.DomEvent.off(document, 'keydown', this._onKeyDown, this); off(document, 'keydown', this._onKeyDown, this);
}, },
_onKeyDown: function (e) { _onKeyDown: function (e) {
@ -144,7 +150,7 @@ L.Map.Keyboard = L.Handler.extend({
offset = this._panKeys[key]; offset = this._panKeys[key];
if (e.shiftKey) { if (e.shiftKey) {
offset = L.point(offset).multiplyBy(3); offset = toPoint(offset).multiplyBy(3);
} }
map.panBy(offset); map.panBy(offset);
@ -163,7 +169,7 @@ L.Map.Keyboard = L.Handler.extend({
return; return;
} }
L.DomEvent.stop(e); stop(e);
} }
}); });
@ -171,4 +177,4 @@ L.Map.Keyboard = L.Handler.extend({
// @section Handlers // @section Handlers
// @property keyboard: Handler // @property keyboard: Handler
// Keyboard navigation handler. // Keyboard navigation handler.
L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard); Map.addInitHook('addHandler', 'keyboard', Keyboard);

View File

@ -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. * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
*/ */
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @section Mousewheel options // @section Mousewheel options
// @option scrollWheelZoom: Boolean|String = true // @option scrollWheelZoom: Boolean|String = true
// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
@ -23,19 +28,19 @@ L.Map.mergeOptions({
wheelPxPerZoomLevel: 60 wheelPxPerZoomLevel: 60
}); });
L.Map.ScrollWheelZoom = L.Handler.extend({ export var ScrollWheelZoom = Handler.extend({
addHooks: function () { addHooks: function () {
L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this); DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
this._delta = 0; this._delta = 0;
}, },
removeHooks: function () { removeHooks: function () {
L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this); DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
}, },
_onWheelScroll: function (e) { _onWheelScroll: function (e) {
var delta = L.DomEvent.getWheelDelta(e); var delta = DomEvent.getWheelDelta(e);
var debounce = this._map.options.wheelDebounceTime; 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); var left = Math.max(debounce - (+new Date() - this._startTime), 0);
clearTimeout(this._timer); 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 () { _performZoom: function () {
@ -83,4 +88,4 @@ L.Map.ScrollWheelZoom = L.Handler.extend({
// @section Handlers // @section Handlers
// @property scrollWheelZoom: Handler // @property scrollWheelZoom: Handler
// Scroll wheel zoom handler. // Scroll wheel zoom handler.
L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);

View File

@ -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. * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
*/ */
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @section Touch interaction options // @section Touch interaction options
// @option tap: Boolean = true // @option tap: Boolean = true
// Enables mobile hacks for supporting instant taps (fixing 200ms click // Enables mobile hacks for supporting instant taps (fixing 200ms click
@ -17,19 +26,19 @@ L.Map.mergeOptions({
tapTolerance: 15 tapTolerance: 15
}); });
L.Map.Tap = L.Handler.extend({ export var Tap = Handler.extend({
addHooks: function () { addHooks: function () {
L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
}, },
removeHooks: function () { removeHooks: function () {
L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
}, },
_onDown: function (e) { _onDown: function (e) {
if (!e.touches) { return; } if (!e.touches) { return; }
L.DomEvent.preventDefault(e); DomEvent.preventDefault(e);
this._fireClick = true; this._fireClick = true;
@ -43,15 +52,15 @@ L.Map.Tap = L.Handler.extend({
var first = e.touches[0], var first = e.touches[0],
el = first.target; 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 touching a link, highlight it
if (el.tagName && el.tagName.toLowerCase() === 'a') { 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 // simulate long hold but setting a timeout
this._holdTimeout = setTimeout(L.bind(function () { this._holdTimeout = setTimeout(Util.bind(function () {
if (this._isTapValid()) { if (this._isTapValid()) {
this._fireClick = false; this._fireClick = false;
this._onUp(); this._onUp();
@ -61,7 +70,7 @@ L.Map.Tap = L.Handler.extend({
this._simulateEvent('mousedown', first); this._simulateEvent('mousedown', first);
L.DomEvent.on(document, { DomEvent.on(document, {
touchmove: this._onMove, touchmove: this._onMove,
touchend: this._onUp touchend: this._onUp
}, this); }, this);
@ -70,7 +79,7 @@ L.Map.Tap = L.Handler.extend({
_onUp: function (e) { _onUp: function (e) {
clearTimeout(this._holdTimeout); clearTimeout(this._holdTimeout);
L.DomEvent.off(document, { DomEvent.off(document, {
touchmove: this._onMove, touchmove: this._onMove,
touchend: this._onUp touchend: this._onUp
}, this); }, this);
@ -81,7 +90,7 @@ L.Map.Tap = L.Handler.extend({
el = first.target; el = first.target;
if (el && el.tagName && el.tagName.toLowerCase() === 'a') { if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
L.DomUtil.removeClass(el, 'leaflet-active'); DomUtil.removeClass(el, 'leaflet-active');
} }
this._simulateEvent('mouseup', first); this._simulateEvent('mouseup', first);
@ -99,7 +108,7 @@ L.Map.Tap = L.Handler.extend({
_onMove: function (e) { _onMove: function (e) {
var first = e.touches[0]; 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); this._simulateEvent('mousemove', first);
}, },
@ -122,6 +131,6 @@ L.Map.Tap = L.Handler.extend({
// @section Handlers // @section Handlers
// @property tap: Handler // @property tap: Handler
// Mobile touch hacks (quick tap and touch hold) handler. // Mobile touch hacks (quick tap and touch hold) handler.
if (L.Browser.touch && !L.Browser.pointer) { if (Browser.touch && !Browser.pointer) {
L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); Map.addInitHook('addHandler', 'tap', Tap);
} }

View File

@ -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. * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
*/ */
// @namespace Map // @namespace Map
// @section Interaction Options // @section Interaction Options
L.Map.mergeOptions({ Map.mergeOptions({
// @section Touch interaction options // @section Touch interaction options
// @option touchZoom: Boolean|String = * // @option touchZoom: Boolean|String = *
// Whether the map can be zoomed by touch-dragging with two fingers. If // 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 // passed `'center'`, it will zoom to the center of the view regardless of
// where the touch events (fingers) were. Enabled for touch-capable web // where the touch events (fingers) were. Enabled for touch-capable web
// browsers except for old Androids. // browsers except for old Androids.
touchZoom: L.Browser.touch && !L.Browser.android23, touchZoom: Browser.touch && !Browser.android23,
// @option bounceAtZoomLimits: Boolean = true // @option bounceAtZoomLimits: Boolean = true
// Set it to false if you don't want the map to zoom beyond min/max zoom // 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 bounceAtZoomLimits: true
}); });
L.Map.TouchZoom = L.Handler.extend({ export var TouchZoom = Handler.extend({
addHooks: function () { addHooks: function () {
L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom'); DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this); DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
}, },
removeHooks: function () { removeHooks: function () {
L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom'); DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this); DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
}, },
_onTouchStart: function (e) { _onTouchStart: function (e) {
@ -51,11 +58,10 @@ L.Map.TouchZoom = L.Handler.extend({
map._stop(); map._stop();
L.DomEvent DomEvent.on(document, 'touchmove', this._onTouchMove, this);
.on(document, 'touchmove', this._onTouchMove, this) DomEvent.on(document, 'touchend', this._onTouchEnd, this);
.on(document, 'touchend', this._onTouchEnd, this);
L.DomEvent.preventDefault(e); DomEvent.preventDefault(e);
}, },
_onTouchMove: function (e) { _onTouchMove: function (e) {
@ -66,7 +72,6 @@ L.Map.TouchZoom = L.Handler.extend({
p2 = map.mouseEventToContainerPoint(e.touches[1]), p2 = map.mouseEventToContainerPoint(e.touches[1]),
scale = p1.distanceTo(p2) / this._startDist; scale = p1.distanceTo(p2) / this._startDist;
this._zoom = map.getScaleZoom(scale, this._startZoom); this._zoom = map.getScaleZoom(scale, this._startZoom);
if (!map.options.bounceAtZoomLimits && ( if (!map.options.bounceAtZoomLimits && (
@ -90,12 +95,12 @@ L.Map.TouchZoom = L.Handler.extend({
this._moved = true; 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}); var moveFn = Util.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
this._animRequest = L.Util.requestAnimFrame(moveFn, this, true); this._animRequest = Util.requestAnimFrame(moveFn, this, true);
L.DomEvent.preventDefault(e); DomEvent.preventDefault(e);
}, },
_onTouchEnd: function () { _onTouchEnd: function () {
@ -105,11 +110,10 @@ L.Map.TouchZoom = L.Handler.extend({
} }
this._zooming = false; this._zooming = false;
L.Util.cancelAnimFrame(this._animRequest); Util.cancelAnimFrame(this._animRequest);
L.DomEvent DomEvent.off(document, 'touchmove', this._onTouchMove);
.off(document, 'touchmove', this._onTouchMove) DomEvent.off(document, 'touchend', this._onTouchEnd);
.off(document, 'touchend', this._onTouchEnd);
// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate. // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
if (this._map.options.zoomAnimation) { if (this._map.options.zoomAnimation) {
@ -123,4 +127,4 @@ L.Map.TouchZoom = L.Handler.extend({
// @section Handlers // @section Handlers
// @property touchZoom: Handler // @property touchZoom: Handler
// Touch zoom handler. // Touch zoom handler.
L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); Map.addInitHook('addHandler', 'touchZoom', TouchZoom);