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
```
You can build minified Leaflet by running `jake` (it will be built from source in the `dist` folder).
For a custom build with selected components, open `build/build.html` in the browser and follow the instructions from there.
or, if you prefer [`yarn`](https://yarnpkg.com/) over `npm`:
```
yarn install -g jake
yarn install
```
### Making Changes to Leaflet Source
@ -96,6 +98,25 @@ Also, please make sure that you have [line endings configured properly](https://
Happy coding!
### Using RollupJS
The source javascript code for Leaflet is a few dozen files, in the `src/` directory.
But normally, Leaflet is loaded in a web browser as just one javascript file.
In order to create this file, run `npm run-script rollup` or `yarn run rollup`.
You'll find `dist/leaflet-src.js` and `dist/leaflet.js`. The difference is that
`dist/leaflet-src.js` has sourcemaps and it's not uglified, so it's better for
development. `dist/leaflet.js` is uglified and thus is smaller, so it's better
for deployment.
When developing (or bugfixing) core Leaflet functionalities, it's common to use
the webpages in the `debug/` directory, and run the unit tests (`spec/index.html`)
in a graphical browser. This requires regenerating the bundled files quickly.
In order to do so, run `npm run-script watch` or `yarn run rollup`. This will keep
on rebuilding the bundles whenever any source file changes.
## Running the Tests
To run the tests from the command line,
@ -158,7 +179,8 @@ code for every method, option or property there is a special code comment docume
that feature. In order to edit the API documentation, just edit these comments in the
source code.
In order to generate the documentation, just run
In order to generate the documentation, make sure that the development dependencies
are installed (run either `npm install` or `yarn install`), then just run
```
jake docs

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.
*/
var build = require('./build/build.js'),
buildDocs = require('./build/docs'),
git = require('git-rev');
var buildDocs = require('./build/docs'),
git = require('git-rev-sync'),
path = require('path');
function hint(msg, args) {
return function () {
@ -29,16 +29,14 @@ function hint(msg, args) {
// Returns the version string in package.json, plus a semver build metadata if
// this is not an official release
function calculateVersion(officialRelease, callback) {
function calculateVersion(officialRelease) {
var version = require('./package.json').version;
if (officialRelease) {
callback(version);
return version;
} else {
git.short(function(str) {
callback (version + '+' + str);
});
return version + '+' + git.short();
}
}
@ -48,16 +46,53 @@ task('lint', {async: true}, hint('Checking for JS errors...', 'src'));
desc('Check Leaflet specs source for errors with ESLint');
task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/suites'));
desc('Combine and compress Leaflet source files');
task('build', {async: true}, function (compsBase32, buildName, officialRelease) {
calculateVersion(officialRelease, function(v){
build.build(complete, v, compsBase32, buildName);
});
});
desc('Run PhantomJS tests');
task('test', ['lint', 'lintspec'], {async: true}, function () {
build.test(complete);
var karma = require('karma'),
testConfig = {configFile : path.join(__dirname, './spec/karma.conf.js')};
testConfig.browsers = ['PhantomJSCustom'];
function isArgv(optName) {
return process.argv.indexOf(optName) !== -1;
}
if (isArgv('--chrome')) {
testConfig.browsers.push('Chrome');
}
if (isArgv('--safari')) {
testConfig.browsers.push('Safari');
}
if (isArgv('--ff')) {
testConfig.browsers.push('Firefox');
}
if (isArgv('--ie')) {
testConfig.browsers.push('IE');
}
if (isArgv('--cov')) {
testConfig.preprocessors = {
'src/**/*.js': 'coverage'
};
testConfig.coverageReporter = {
type : 'html',
dir : 'coverage/'
};
testConfig.reporters = ['coverage'];
}
console.log('Running tests...');
var server = new karma.Server(testConfig, function(exitCode) {
if (!exitCode) {
console.log('\tTests ran successfully.\n');
complete();
} else {
process.exit(exitCode);
}
});
server.start();
});
desc('Build documentation');

View File

@ -6,7 +6,7 @@
"dist/leaflet.css",
"dist/leaflet-src.js"
],
"ignore": [
"ignore": [
".*",
"CHANGELOG.json",
"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
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
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 commit -m "v$VERSION"
@ -16,7 +25,11 @@ git commit -m "v$VERSION"
git tag v$VERSION -f
git push --tags -f
echo "Uploading to NPM..."
npm publish
git checkout master
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 f2 = hand.growFinger('touch');

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>
<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>
<p>Leaflet is designed with <em>simplicity</em>, <em>performance</em> and <em>usability</em> in mind.

View File

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

View File

@ -19,7 +19,7 @@
// Trick Leaflet into believing we have a touchscreen (for Chrome)
if (window.Touch) { window.ontouchstart = function(){} };
</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>

View File

@ -1,12 +1,17 @@
// var es3 = require('rollup-plugin-es3');
var json = require('rollup-plugin-json');
// Karma configuration
module.exports = function (config) {
var libSources = require(__dirname + '/../build/build.js').getFiles();
// var libSources = require(__dirname + '/../build/build.js').getFiles();
var files = [
"spec/sinon.js",
"spec/expect.js"
].concat(libSources, [
"spec/expect.js",
"src/Leaflet.js",
"spec/after.js",
"node_modules/happen/happen.js",
"node_modules/prosthetic-hand/dist/prosthetic-hand.js",
@ -14,13 +19,14 @@ module.exports = function (config) {
"spec/suites/**/*.js",
"dist/*.css",
{pattern: "dist/images/*.png", included: false, serve: true}
]);
];
config.set({
// base path, that will be used to resolve files and exclude
basePath: '../',
plugins: [
'karma-rollup-preprocessor',
'karma-mocha',
'karma-coverage',
'karma-phantomjs-launcher',
@ -38,6 +44,24 @@ module.exports = function (config) {
},
exclude: [],
// Rollup the ES6 Leaflet sources into just one file, before tests
preprocessors: {
'src/Leaflet.js': ['rollup']
},
rollupPreprocessor: {
rollup: {
plugins: [
// es3(),
json()
]
},
bundle: {
format: 'umd',
moduleName: 'L'
// sourceMap: 'inline'
}
},
// test results reporter to use
// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
reporters: ['dots'],

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
it.skipInPhantom = L.Browser.any3d ? it : it.skip;
// Viceversa: some tests we want only to run in browsers without CSS animations.
it.skipInNonPhantom = L.Browser.any3d ? it.skip : it;
// A couple of tests need the browser to be touch-capable
it.skipIfNotTouch = window.TouchEvent ? it : it.skip;

View File

@ -1,14 +1,17 @@
describe('Path', function () {
// The following two tests are skipped, as the ES6-ifycation of Leaflet
// means that L.Path is no longer visible.
describe('#bringToBack', function () {
it('is a no-op for layers not on a map', function () {
var path = new L.Path();
var path = new L.Polyline([[1, 2], [3, 4], [5, 6]]);
expect(path.bringToBack()).to.equal(path);
});
});
describe('#bringToFront', function () {
it('is a no-op for layers not on a map', function () {
var path = new L.Path();
var path = new L.Polyline([[1, 2], [3, 4], [5, 6]]);
expect(path.bringToFront()).to.equal(path);
});
});

View File

@ -12,7 +12,7 @@ describe('Polygon', function () {
var polygon = new L.Polygon(latLngs);
expect(L.Polyline._flat(polygon._latlngs)).to.be(false);
expect(L.LineUtil._flat(polygon._latlngs)).to.be(false);
expect(polygon.getLatLngs()).to.eql(polygon._latlngs);
});

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 () {

View File

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

View File

@ -1,29 +1,170 @@
var L = {
version: '1.0.3'
};
import {version} from '../package.json';
export {version};
function expose() {
var oldL = window.L;
// control
L.noConflict = function () {
window.L = oldL;
return this;
};
import {Control, control} from './control/Control';
import {Layers, layers} from './control/Control.Layers';
import {Zoom, zoom} from './control/Control.Zoom';
import {Scale, scale} from './control/Control.Scale';
import {Attribution, attribution} from './control/Control.Attribution.js';
window.L = L;
}
// define Leaflet for Node module pattern loaders, including Browserify
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = L;
// define Leaflet as an AMD module
} else if (typeof define === 'function' && define.amd) {
define(L);
}
// define Leaflet as a global L variable, saving the original L to restore later if needed
if (typeof window !== 'undefined') {
expose();
Control.Layers = Layers;
Control.Zoom = Zoom;
Control.Scale = Scale;
Control.Attribution = Attribution;
control.layers = layers;
control.zoom = zoom;
control.scale = scale;
control.attribution = attribution;
export {Control, control};
// core
import * as Browser from './core/Browser';
export {Browser};
export {Class} from './core/Class';
import {Evented} from './core/Events';
export {Evented};
// export var Mixin = {Events: Evented.prototype};
export {Handler} from './core/Handler';
import * as Util from './core/Util';
export {Util};
export {extend, bind, stamp, setOptions} from './core/Util';
// dom
export {PosAnimation} from './dom/PosAnimation';
import * as DomEvent from './dom/DomEvent';
export {DomEvent};
import * as DomUtil from './dom/DomUtil';
export {DomUtil};
export {Draggable} from './dom/Draggable';
// geometry
export {Point, toPoint as point} from './geometry/Point';
export {Bounds, toBounds as bounds} from './geometry/Bounds';
export {Transformation, toTransformation as transformation} from './geometry/Transformation';
// geo
export {LatLng, toLatLng as latLng} from './geo/LatLng';
export {LatLngBounds, toLatLngBounds as latLngBounds} from './geo/LatLngBounds';
// geo/projection
import * as Projection from './geo/projection/Projection';
export {Projection};
// geo/crs
import {CRS} from './geo/crs/CRS';
import {Earth} from './geo/crs/CRS.Earth';
import {EPSG3395} from './geo/crs/CRS.EPSG3395';
import {EPSG3857, EPSG900913} from './geo/crs/CRS.EPSG3857';
import {EPSG4326} from './geo/crs/CRS.EPSG4326';
import {Simple} from './geo/crs/CRS.Simple';
CRS.Earth = Earth;
CRS.EPSG3395 = EPSG3395;
CRS.EPSG3857 = EPSG3857;
CRS.EPSG900913 = EPSG900913;
CRS.EPSG4326 = EPSG4326;
CRS.Simple = Simple;
export {CRS};
// layer
export {Layer} from './layer/Layer';
export {LayerGroup, layerGroup} from './layer/LayerGroup';
export {FeatureGroup, featureGroup} from './layer/FeatureGroup';
import {GeoJSON, geoJSON, geoJson, geometryToLayer, coordsToLatLng, coordsToLatLngs, latLngToCoords, latLngsToCoords, getFeature, asFeature} from './layer/GeoJSON';
GeoJSON.geometryToLayer = geometryToLayer;
GeoJSON.coordsToLatLng = coordsToLatLng;
GeoJSON.coordsToLatLngs = coordsToLatLngs;
GeoJSON.latLngToCoords = latLngToCoords;
GeoJSON.latLngsToCoords = latLngsToCoords;
GeoJSON.getFeature = getFeature;
GeoJSON.asFeature = asFeature;
export {GeoJSON, geoJSON, geoJson};
export {ImageOverlay, imageOverlay} from './layer/ImageOverlay';
export {DivOverlay} from './layer/DivOverlay';
export {Popup, popup} from './layer/Popup';
export {Tooltip, tooltip} from './layer/Tooltip';
import {Icon} from './layer/marker/Icon';
export {icon} from './layer/marker/Icon';
import {IconDefault} from './layer/marker/Icon.Default';
Icon.Default = IconDefault;
export {Icon};
export {DivIcon, divIcon} from './layer/marker/DivIcon';
export {Marker, marker} from './layer/marker/Marker';
// layer, tile
export {GridLayer, gridLayer} from './layer/tile/GridLayer';
import {TileLayer, tileLayer} from './layer/tile/TileLayer';
import {TileLayerWMS, tileLayerWMS} from './layer/tile/TileLayer.WMS';
TileLayer.WMS = TileLayerWMS;
tileLayer.wms = tileLayerWMS;
export {TileLayer, tileLayer};
// layer, vector
export {Renderer} from './layer/vector/Renderer';
export {Canvas, canvas} from './layer/vector/Canvas';
export {SVG, svg} from './layer/vector/SVG';
import './layer/vector/Renderer.getRenderer'; // This is a bit of a hack, but needed because circular dependencies
export {Path} from './layer/vector/Path';
export {CircleMarker, circleMarker} from './layer/vector/CircleMarker';
export {Circle, circle} from './layer/vector/Circle';
export {Polyline, polyline} from './layer/vector/Polyline';
export {Polygon, polygon} from './layer/vector/Polygon';
export {Rectangle, rectangle} from './layer/vector/Rectangle';
import * as LineUtil from './geometry/LineUtil';
export {LineUtil};
import * as PolyUtil from './geometry/PolyUtil';
export {PolyUtil};
// map
import {Map} from './map/Map';
import {BoxZoom} from './map/handler/Map.BoxZoom';
Map.BoxZoom = BoxZoom;
import {DoubleClickZoom} from './map/handler/Map.DoubleClickZoom';
Map.DoubleClickZoom = DoubleClickZoom;
import {Drag} from './map/handler/Map.Drag';
Map.Drag = Drag;
import {Keyboard} from './map/handler/Map.Keyboard';
Map.Keyboard = Keyboard;
import {ScrollWheelZoom} from './map/handler/Map.ScrollWheelZoom';
Map.ScrollWheelZoom = ScrollWheelZoom;
import {Tap} from './map/handler/Map.Tap';
Map.Tap = Tap;
import {TouchZoom} from './map/handler/Map.TouchZoom';
Map.TouchZoom = TouchZoom;
export {Map, createMap as map} from './map/Map';
// misc
var oldL = window.L;
export function noConflict() {
window.L = oldL;
return this;
}

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
* @aka L.Control.Attribution
@ -6,7 +13,7 @@
* The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
*/
L.Control.Attribution = L.Control.extend({
export var Attribution = Control.extend({
// @section
// @aka Control.Attribution options
options: {
@ -18,17 +25,15 @@ L.Control.Attribution = L.Control.extend({
},
initialize: function (options) {
L.setOptions(this, options);
Util.setOptions(this, options);
this._attributions = {};
},
onAdd: function (map) {
map.attributionControl = this;
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
if (L.DomEvent) {
L.DomEvent.disableClickPropagation(this._container);
}
this._container = DomUtil.create('div', 'leaflet-control-attribution');
DomEvent.disableClickPropagation(this._container);
// TODO ugly, refactor
for (var i in map._layers) {
@ -106,19 +111,19 @@ L.Control.Attribution = L.Control.extend({
// @section Control options
// @option attributionControl: Boolean = true
// Whether a [attribution control](#control-attribution) is added to the map by default.
L.Map.mergeOptions({
Map.mergeOptions({
attributionControl: true
});
L.Map.addInitHook(function () {
Map.addInitHook(function () {
if (this.options.attributionControl) {
new L.Control.Attribution().addTo(this);
new Attribution().addTo(this);
}
});
// @namespace Control.Attribution
// @factory L.control.attribution(options: Control.Attribution options)
// Creates an attribution control.
L.control.attribution = function (options) {
return new L.Control.Attribution(options);
export var attribution = function (options) {
return new Attribution(options);
};

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

View File

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

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

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

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
* @aka L.Browser
@ -13,139 +15,128 @@
* ```
*/
(function () {
var style = document.documentElement.style;
var ua = navigator.userAgent.toLowerCase(),
doc = document.documentElement,
// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
export var ie = 'ActiveXObject' in window;
ie = 'ActiveXObject' in window,
// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
export var ielt9 = ie && !document.addEventListener;
webkit = ua.indexOf('webkit') !== -1,
phantomjs = ua.indexOf('phantom') !== -1,
android23 = ua.search('android [23]') !== -1,
chrome = ua.indexOf('chrome') !== -1,
gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
// @property edge: Boolean; `true` for the Edge web browser.
export var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
win = navigator.platform.indexOf('Win') === 0,
// @property webkit: Boolean;
// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
export var webkit = userAgentContains('webkit');
mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
msPointer = !window.PointerEvent && window.MSPointerEvent,
pointer = window.PointerEvent || msPointer,
// @property android: Boolean
// `true` for any browser running on an Android platform.
export var android = userAgentContains('android');
ie3d = ie && ('transition' in doc.style),
webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
gecko3d = 'MozPerspective' in doc.style,
opera12 = 'OTransition' in doc.style;
// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
export var android23 = userAgentContains('android 2') || userAgentContains('android 3');
// @property opera: Boolean; `true` for the Opera browser
export var opera = !!window.opera;
// @property chrome: Boolean; `true` for the Chrome browser.
export var chrome = userAgentContains('chrome');
// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
export var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
// @property safari: Boolean; `true` for the Safari browser.
export var safari = !chrome && userAgentContains('safari');
export var phantom = userAgentContains('phantom');
// @property opera12: Boolean
// `true` for the Opera browser supporting CSS transforms (version 12 or later).
export var opera12 = 'OTransition' in style;
// @property win: Boolean; `true` when the browser is running in a Windows platform
export var win = navigator.platform.indexOf('Win') === 0;
// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
export var ie3d = ie && ('transition' in style);
// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
export var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
export var gecko3d = 'MozPerspective' in style;
// @property any3d: Boolean
// `true` for all browsers supporting CSS transforms.
export var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
// @property mobile: Boolean; `true` for all browsers running in a mobile device.
export var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
export var mobileWebkit = mobile && webkit;
// @property mobileWebkit3d: Boolean
// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
export var mobileWebkit3d = mobile && webkit3d;
// @property msPointer: Boolean
// `true` for browsers implementing the Microsoft touch events model (notably IE10).
export var msPointer = !window.PointerEvent && window.MSPointerEvent;
// @property pointer: Boolean
// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
export var pointer = !!(window.PointerEvent || msPointer);
// @property touch: Boolean
// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
// This does not necessarily mean that the browser is running in a computer with
// a touchscreen, it only means that the browser is capable of understanding
// touch events.
export var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
(window.DocumentTouch && document instanceof window.DocumentTouch));
// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
export var mobileOpera = mobile && opera;
// @property mobileGecko: Boolean
// `true` for gecko-based browsers running in a mobile device.
export var mobileGecko = mobile && gecko;
// @property retina: Boolean
// `true` for browsers on a high-resolution "retina" screen.
export var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
(window.DocumentTouch && document instanceof window.DocumentTouch));
L.Browser = {
// @property ie: Boolean
// `true` for all Internet Explorer versions (not Edge).
ie: ie,
// @property ielt9: Boolean
// `true` for Internet Explorer versions less than 9.
ielt9: ie && !document.addEventListener,
// @property edge: Boolean
// `true` for the Edge web browser.
edge: 'msLaunchUri' in navigator && !('documentMode' in document),
// @property webkit: Boolean
// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
webkit: webkit,
// @property gecko: Boolean
// `true` for gecko-based browsers like Firefox.
gecko: gecko,
// @property android: Boolean
// `true` for any browser running on an Android platform.
android: ua.indexOf('android') !== -1,
// @property android23: Boolean
// `true` for browsers running on Android 2 or Android 3.
android23: android23,
// @property chrome: Boolean
// `true` for the Chrome browser.
chrome: chrome,
// @property safari: Boolean
// `true` for the Safari browser.
safari: !chrome && ua.indexOf('safari') !== -1,
// @property win: Boolean
// `true` when the browser is running in a Windows platform
win: win,
// @property ie3d: Boolean
// `true` for all Internet Explorer versions supporting CSS transforms.
ie3d: ie3d,
// @property webkit3d: Boolean
// `true` for webkit-based browsers supporting CSS transforms.
webkit3d: webkit3d,
// @property gecko3d: Boolean
// `true` for gecko-based browsers supporting CSS transforms.
gecko3d: gecko3d,
// @property opera12: Boolean
// `true` for the Opera browser supporting CSS transforms (version 12 or later).
opera12: opera12,
// @property any3d: Boolean
// `true` for all browsers supporting CSS transforms.
any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
// @property mobile: Boolean
// `true` for all browsers running in a mobile device.
mobile: mobile,
// @property mobileWebkit: Boolean
// `true` for all webkit-based browsers in a mobile device.
mobileWebkit: mobile && webkit,
// @property mobileWebkit3d: Boolean
// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
mobileWebkit3d: mobile && webkit3d,
// @property mobileOpera: Boolean
// `true` for the Opera browser in a mobile device.
mobileOpera: mobile && window.opera,
// @property mobileGecko: Boolean
// `true` for gecko-based browsers running in a mobile device.
mobileGecko: mobile && gecko,
// @property touch: Boolean
// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
// This does not necessarily mean that the browser is running in a computer with
// a touchscreen, it only means that the browser is capable of understanding
// touch events.
touch: !!touch,
// @property msPointer: Boolean
// `true` for browsers implementing the Microsoft touch events model (notably IE10).
msPointer: !!msPointer,
// @property pointer: Boolean
// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
pointer: !!pointer,
// @property retina: Boolean
// `true` for browsers on a high-resolution "retina" screen.
retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
};
// @property canvas: Boolean
// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
export var canvas = (function () {
return !!document.createElement('canvas').getContext;
}());
// @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
export var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
// @property vml: Boolean
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
export var vml = !svg && (function () {
try {
var div = document.createElement('div');
div.innerHTML = '<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
// @aka L.Class
@ -7,9 +8,9 @@
// Thanks to John Resig and Dean Edwards for inspiration!
L.Class = function () {};
export function Class() {}
L.Class.extend = function (props) {
Class.extend = function (props) {
// @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included.
@ -27,7 +28,7 @@ L.Class.extend = function (props) {
var parentProto = NewClass.__super__ = this.prototype;
var proto = L.Util.create(parentProto);
var proto = Util.create(parentProto);
proto.constructor = NewClass;
NewClass.prototype = proto;
@ -41,23 +42,23 @@ L.Class.extend = function (props) {
// mix static properties into the class
if (props.statics) {
L.extend(NewClass, props.statics);
Util.extend(NewClass, props.statics);
delete props.statics;
}
// mix includes into the prototype
if (props.includes) {
L.Util.extend.apply(null, [proto].concat(props.includes));
Util.extend.apply(null, [proto].concat(props.includes));
delete props.includes;
}
// merge options
if (proto.options) {
props.options = L.Util.extend(L.Util.create(proto.options), props.options);
props.options = Util.extend(Util.create(proto.options), props.options);
}
// mix given properties into the prototype
L.extend(proto, props);
Util.extend(proto, props);
proto._initHooks = [];
@ -83,21 +84,21 @@ L.Class.extend = function (props) {
// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
L.Class.include = function (props) {
L.extend(this.prototype, props);
Class.include = function (props) {
Util.extend(this.prototype, props);
return this;
};
// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
L.Class.mergeOptions = function (options) {
L.extend(this.prototype.options, options);
Class.mergeOptions = function (options) {
Util.extend(this.prototype.options, options);
return this;
};
// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
Class.addInitHook = function (fn) { // (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {

View File

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

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
interaction features like dragging to classes like Map and Marker.
@ -7,7 +9,7 @@
// @aka L.Handler
// Abstract class for map interaction handlers
L.Handler = L.Class.extend({
export var Handler = Class.extend({
initialize: function (map) {
this._map = map;
},

View File

@ -4,247 +4,233 @@
* Various utility functions, used by Leaflet internally.
*/
L.Util = {
// @function extend(dest: Object, src?: Object): Object
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
export function extend(dest) {
var i, j, len, src;
// @function extend(dest: Object, src?: Object): Object
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
extend: function (dest) {
var i, j, len, src;
for (j = 1, len = arguments.length; j < len; j++) {
src = arguments[j];
for (i in src) {
dest[i] = src[i];
}
for (j = 1, len = arguments.length; j < len; j++) {
src = arguments[j];
for (i in src) {
dest[i] = src[i];
}
return dest;
},
// @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
create: Object.create || (function () {
function F() {}
return function (proto) {
F.prototype = proto;
return new F();
};
})(),
// @function bind(fn: Function, …): Function
// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
// Has a `L.bind()` shortcut.
bind: function (fn, obj) {
var slice = Array.prototype.slice;
if (fn.bind) {
return fn.bind.apply(fn, slice.call(arguments, 1));
}
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
};
},
// @function stamp(obj: Object): Number
// Returns the unique ID of an object, assiging it one if it doesn't have it.
stamp: function (obj) {
/*eslint-disable */
obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
return obj._leaflet_id;
/*eslint-enable */
},
// @property lastId: Number
// Last unique ID used by [`stamp()`](#util-stamp)
lastId: 0,
// @function throttle(fn: Function, time: Number, context: Object): Function
// Returns a function which executes function `fn` with the given scope `context`
// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
// `fn` will be called no more than one time per given amount of `time`. The arguments
// received by the bound function will be any arguments passed when binding the
// function, followed by any arguments passed when invoking the bound function.
// Has an `L.bind` shortcut.
throttle: function (fn, time, context) {
var lock, args, wrapperFn, later;
later = function () {
// reset lock and call if queued
lock = false;
if (args) {
wrapperFn.apply(context, args);
args = false;
}
};
wrapperFn = function () {
if (lock) {
// called too soon, queue to call later
args = arguments;
} else {
// call and lock until later
fn.apply(context, arguments);
setTimeout(later, time);
lock = true;
}
};
return wrapperFn;
},
// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
// Returns the number `num` modulo `range` in such a way so it lies within
// `range[0]` and `range[1]`. The returned value will be always smaller than
// `range[1]` unless `includeMax` is set to `true`.
wrapNum: function (x, range, includeMax) {
var max = range[1],
min = range[0],
d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
},
// @function falseFn(): Function
// Returns a function which always returns `false`.
falseFn: function () { return false; },
// @function formatNum(num: Number, digits?: Number): Number
// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
formatNum: function (num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
// @function trim(str: String): String
// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
trim: function (str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
},
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
splitWords: function (str) {
return L.Util.trim(str).split(/\s+/);
},
// @function setOptions(obj: Object, options: Object): Object
// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
setOptions: function (obj, options) {
if (!obj.hasOwnProperty('options')) {
obj.options = obj.options ? L.Util.create(obj.options) : {};
}
for (var i in options) {
obj.options[i] = options[i];
}
return obj.options;
},
// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
// be appended at the end. If `uppercase` is `true`, the parameter names will
// be uppercased (e.g. `'?A=foo&B=bar'`)
getParamString: function (obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
},
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
// `('Hello foo, bar')`. You can also specify functions instead of strings for
// data values — they will be evaluated passing `data` as an argument.
template: function (str, data) {
return str.replace(L.Util.templateRe, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
},
templateRe: /\{ *([\w_\-]+) *\}/g,
// @function isArray(obj): Boolean
// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
isArray: Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
},
// @function indexOf(array: Array, el: Object): Number
// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
indexOf: function (array, el) {
for (var i = 0; i < array.length; i++) {
if (array[i] === el) { return i; }
}
return -1;
},
// @property emptyImageUrl: String
// Data URI string containing a base64-encoded empty GIF image.
// Used as a hack to free memory from unused images on WebKit-powered
// mobile devices (by setting image `src` to this string).
emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};
(function () {
// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
return window['webkit' + name] || window['moz' + name] || window['ms' + name];
}
return dest;
}
var lastTime = 0;
// fallback for IE 7-8
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
// `context` if given. When `immediate` is set, `fn` is called immediately if
// the browser doesn't have native support for
// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
L.Util.requestAnimFrame = function (fn, context, immediate) {
if (immediate && requestFn === timeoutDefer) {
fn.call(context);
} else {
return requestFn.call(window, L.bind(fn, context));
}
};
// @function cancelAnimFrame(id: Number): undefined
// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
L.Util.cancelAnimFrame = function (id) {
if (id) {
cancelFn.call(window, id);
}
// @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
export var create = Object.create || (function () {
function F() {}
return function (proto) {
F.prototype = proto;
return new F();
};
})();
// shortcuts for most used utility functions
L.extend = L.Util.extend;
L.bind = L.Util.bind;
L.stamp = L.Util.stamp;
L.setOptions = L.Util.setOptions;
// @function bind(fn: Function, …): Function
// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
// Has a `L.bind()` shortcut.
export function bind(fn, obj) {
var slice = Array.prototype.slice;
if (fn.bind) {
return fn.bind.apply(fn, slice.call(arguments, 1));
}
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
};
}
var lastId = 0;
// @function stamp(obj: Object): Number
// Returns the unique ID of an object, assiging it one if it doesn't have it.
export function stamp(obj) {
/*eslint-disable */
obj._leaflet_id = obj._leaflet_id || ++lastId;
return obj._leaflet_id;
/*eslint-enable */
}
// @function throttle(fn: Function, time: Number, context: Object): Function
// Returns a function which executes function `fn` with the given scope `context`
// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
// `fn` will be called no more than one time per given amount of `time`. The arguments
// received by the bound function will be any arguments passed when binding the
// function, followed by any arguments passed when invoking the bound function.
// Has an `L.throttle` shortcut.
export function throttle(fn, time, context) {
var lock, args, wrapperFn, later;
later = function () {
// reset lock and call if queued
lock = false;
if (args) {
wrapperFn.apply(context, args);
args = false;
}
};
wrapperFn = function () {
if (lock) {
// called too soon, queue to call later
args = arguments;
} else {
// call and lock until later
fn.apply(context, arguments);
setTimeout(later, time);
lock = true;
}
};
return wrapperFn;
}
// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
// Returns the number `num` modulo `range` in such a way so it lies within
// `range[0]` and `range[1]`. The returned value will be always smaller than
// `range[1]` unless `includeMax` is set to `true`.
export function wrapNum(x, range, includeMax) {
var max = range[1],
min = range[0],
d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
}
// @function falseFn(): Function
// Returns a function which always returns `false`.
export function falseFn() { return false; }
// @function formatNum(num: Number, digits?: Number): Number
// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
export function formatNum(num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
}
// @function trim(str: String): String
// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
export function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
export function splitWords(str) {
return trim(str).split(/\s+/);
}
// @function setOptions(obj: Object, options: Object): Object
// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
export function setOptions(obj, options) {
if (!obj.hasOwnProperty('options')) {
obj.options = obj.options ? create(obj.options) : {};
}
for (var i in options) {
obj.options[i] = options[i];
}
return obj.options;
}
// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
// be appended at the end. If `uppercase` is `true`, the parameter names will
// be uppercased (e.g. `'?A=foo&B=bar'`)
export function getParamString(obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
}
var templateRe = /\{ *([\w_\-]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
// `('Hello foo, bar')`. You can also specify functions instead of strings for
// data values — they will be evaluated passing `data` as an argument.
export function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
}
// @function isArray(obj): Boolean
// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
export var isArray = Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
};
// @function indexOf(array: Array, el: Object): Number
// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
export function indexOf(array, el) {
for (var i = 0; i < array.length; i++) {
if (array[i] === el) { return i; }
}
return -1;
}
// @property emptyImageUrl: String
// Data URI string containing a base64-encoded empty GIF image.
// Used as a hack to free memory from unused images on WebKit-powered
// mobile devices (by setting image `src` to this string).
export var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
return window['webkit' + name] || window['moz' + name] || window['ms' + name];
}
var lastTime = 0;
// fallback for IE 7-8
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
export var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
export var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
// `context` if given. When `immediate` is set, `fn` is called immediately if
// the browser doesn't have native support for
// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
export function requestAnimFrame(fn, context, immediate) {
if (immediate && requestFn === timeoutDefer) {
fn.call(context);
} else {
return requestFn.call(window, bind(fn, context));
}
}
// @function cancelAnimFrame(id: Number): undefined
// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
export function cancelAnimFrame(id) {
if (id) {
cancelFn.call(window, id);
}
}

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.
*/
L.extend(L.DomEvent, {
var _touchstart = Browser.msPointer ? 'MSPointerDown' : Browser.pointer ? 'pointerdown' : 'touchstart',
_touchend = Browser.msPointer ? 'MSPointerUp' : Browser.pointer ? 'pointerup' : 'touchend',
_pre = '_leaflet_';
_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
// inspired by Zepto touch code by Thomas Fuchs
export function addDoubleTapListener(obj, handler, id) {
var last, touch,
doubleTap = false,
delay = 250;
// inspired by Zepto touch code by Thomas Fuchs
addDoubleTapListener: function (obj, handler, id) {
var last, touch,
doubleTap = false,
delay = 250;
function onTouchStart(e) {
var count;
function onTouchStart(e) {
var count;
if (L.Browser.pointer) {
if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
count = L.DomEvent._pointersCount;
} else {
count = e.touches.length;
}
if (count > 1) { return; }
var now = Date.now(),
delta = now - (last || now);
touch = e.touches ? e.touches[0] : e;
doubleTap = (delta > 0 && delta <= delay);
last = now;
if (Browser.pointer) {
if ((!Browser.edge) || e.pointerType === 'mouse') { return; }
count = _pointersCount;
} else {
count = e.touches.length;
}
function onTouchEnd(e) {
if (doubleTap && !touch.cancelBubble) {
if (L.Browser.pointer) {
if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
if (count > 1) { return; }
// work around .type being readonly with MSPointer* events
var newTouch = {},
prop, i;
var now = Date.now(),
delta = now - (last || now);
for (i in touch) {
prop = touch[i];
newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
}
touch = newTouch;
}
touch.type = 'dblclick';
handler(touch);
last = null;
}
}
var pre = '_leaflet_',
touchstart = this._touchstart,
touchend = this._touchend;
obj[pre + touchstart + id] = onTouchStart;
obj[pre + touchend + id] = onTouchEnd;
obj[pre + 'dblclick' + id] = handler;
obj.addEventListener(touchstart, onTouchStart, false);
obj.addEventListener(touchend, onTouchEnd, false);
// On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
// the browser doesn't fire touchend/pointerup events but does fire
// native dblclicks. See #4127.
// Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
obj.addEventListener('dblclick', handler, false);
return this;
},
removeDoubleTapListener: function (obj, id) {
var pre = '_leaflet_',
touchstart = obj[pre + this._touchstart + id],
touchend = obj[pre + this._touchend + id],
dblclick = obj[pre + 'dblclick' + id];
obj.removeEventListener(this._touchstart, touchstart, false);
obj.removeEventListener(this._touchend, touchend, false);
if (!L.Browser.edge) {
obj.removeEventListener('dblclick', dblclick, false);
}
return this;
touch = e.touches ? e.touches[0] : e;
doubleTap = (delta > 0 && delta <= delay);
last = now;
}
});
function onTouchEnd(e) {
if (doubleTap && !touch.cancelBubble) {
if (Browser.pointer) {
if ((!Browser.edge) || e.pointerType === 'mouse') { return; }
// work around .type being readonly with MSPointer* events
var newTouch = {},
prop, i;
for (i in touch) {
prop = touch[i];
newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
}
touch = newTouch;
}
touch.type = 'dblclick';
handler(touch);
last = null;
}
}
obj[_pre + _touchstart + id] = onTouchStart;
obj[_pre + _touchend + id] = onTouchEnd;
obj[_pre + 'dblclick' + id] = handler;
obj.addEventListener(_touchstart, onTouchStart, false);
obj.addEventListener(_touchend, onTouchEnd, false);
// On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
// the browser doesn't fire touchend/pointerup events but does fire
// native dblclicks. See #4127.
// Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
obj.addEventListener('dblclick', handler, false);
return this;
}
export function removeDoubleTapListener(obj, id) {
var touchstart = obj[_pre + _touchstart + id],
touchend = obj[_pre + _touchend + id],
dblclick = obj[_pre + 'dblclick' + id];
obj.removeEventListener(_touchstart, touchstart, false);
obj.removeEventListener(_touchend, touchend, false);
if (!Browser.edge) {
obj.removeEventListener('dblclick', dblclick, false);
}
return this;
}

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.
*/
L.extend(L.DomEvent, {
POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove',
POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup',
POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'],
_pointers: {},
_pointersCount: 0,
_pointers = {},
_pointerDocListener = false;
// Provides a touch events wrapper for (ms)pointer events.
// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
// DomEvent.DoubleTap needs to know about this
export var _pointersCount = 0;
addPointerListener: function (obj, type, handler, id) {
// Provides a touch events wrapper for (ms)pointer events.
// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
if (type === 'touchstart') {
this._addPointerStart(obj, handler, id);
export function addPointerListener(obj, type, handler, id) {
} else if (type === 'touchmove') {
this._addPointerMove(obj, handler, id);
if (type === 'touchstart') {
_addPointerStart(obj, handler, id);
} else if (type === 'touchend') {
this._addPointerEnd(obj, handler, id);
}
} else if (type === 'touchmove') {
_addPointerMove(obj, handler, id);
return this;
},
removePointerListener: function (obj, type, id) {
var handler = obj['_leaflet_' + type + id];
if (type === 'touchstart') {
obj.removeEventListener(this.POINTER_DOWN, handler, false);
} else if (type === 'touchmove') {
obj.removeEventListener(this.POINTER_MOVE, handler, false);
} else if (type === 'touchend') {
obj.removeEventListener(this.POINTER_UP, handler, false);
obj.removeEventListener(this.POINTER_CANCEL, handler, false);
}
return this;
},
_addPointerStart: function (obj, handler, id) {
var onDown = L.bind(function (e) {
if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
// In IE11, some touch events needs to fire for form controls, or
// the controls will stop working. We keep a whitelist of tag names that
// need these events. For other target tags, we prevent default on the event.
if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
L.DomEvent.preventDefault(e);
} else {
return;
}
}
this._handlePointer(e, handler);
}, this);
obj['_leaflet_touchstart' + id] = onDown;
obj.addEventListener(this.POINTER_DOWN, onDown, false);
// need to keep track of what pointers and how many are active to provide e.touches emulation
if (!this._pointerDocListener) {
var pointerUp = L.bind(this._globalPointerUp, this);
// we listen documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
this._pointerDocListener = true;
}
},
_globalPointerDown: function (e) {
this._pointers[e.pointerId] = e;
this._pointersCount++;
},
_globalPointerMove: function (e) {
if (this._pointers[e.pointerId]) {
this._pointers[e.pointerId] = e;
}
},
_globalPointerUp: function (e) {
delete this._pointers[e.pointerId];
this._pointersCount--;
},
_handlePointer: function (e, handler) {
e.touches = [];
for (var i in this._pointers) {
e.touches.push(this._pointers[i]);
}
e.changedTouches = [e];
handler(e);
},
_addPointerMove: function (obj, handler, id) {
var onMove = L.bind(function (e) {
// don't fire touch moves when mouse isn't down
if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
this._handlePointer(e, handler);
}, this);
obj['_leaflet_touchmove' + id] = onMove;
obj.addEventListener(this.POINTER_MOVE, onMove, false);
},
_addPointerEnd: function (obj, handler, id) {
var onUp = L.bind(function (e) {
this._handlePointer(e, handler);
}, this);
obj['_leaflet_touchend' + id] = onUp;
obj.addEventListener(this.POINTER_UP, onUp, false);
obj.addEventListener(this.POINTER_CANCEL, onUp, false);
} else if (type === 'touchend') {
_addPointerEnd(obj, handler, id);
}
});
return this;
}
export function removePointerListener(obj, type, id) {
var handler = obj['_leaflet_' + type + id];
if (type === 'touchstart') {
obj.removeEventListener(POINTER_DOWN, handler, false);
} else if (type === 'touchmove') {
obj.removeEventListener(POINTER_MOVE, handler, false);
} else if (type === 'touchend') {
obj.removeEventListener(POINTER_UP, handler, false);
obj.removeEventListener(POINTER_CANCEL, handler, false);
}
return this;
}
function _addPointerStart(obj, handler, id) {
var onDown = Util.bind(function (e) {
if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
// In IE11, some touch events needs to fire for form controls, or
// the controls will stop working. We keep a whitelist of tag names that
// need these events. For other target tags, we prevent default on the event.
if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
DomEvent.preventDefault(e);
} else {
return;
}
}
_handlePointer(e, handler);
});
obj['_leaflet_touchstart' + id] = onDown;
obj.addEventListener(POINTER_DOWN, onDown, false);
// need to keep track of what pointers and how many are active to provide e.touches emulation
if (!_pointerDocListener) {
// we listen documentElement as any drags that end by moving the touch off the screen get fired there
document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
_pointerDocListener = true;
}
}
function _globalPointerDown(e) {
_pointers[e.pointerId] = e;
_pointersCount++;
}
function _globalPointerMove(e) {
if (_pointers[e.pointerId]) {
_pointers[e.pointerId] = e;
}
}
function _globalPointerUp(e) {
delete _pointers[e.pointerId];
_pointersCount--;
}
function _handlePointer(e, handler) {
e.touches = [];
for (var i in _pointers) {
e.touches.push(_pointers[i]);
}
e.changedTouches = [e];
handler(e);
}
function _addPointerMove(obj, handler, id) {
var onMove = function (e) {
// don't fire touch moves when mouse isn't down
if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
_handlePointer(e, handler);
};
obj['_leaflet_touchmove' + id] = onMove;
obj.addEventListener(POINTER_MOVE, onMove, false);
}
function _addPointerEnd(obj, handler, id) {
var onUp = function (e) {
_handlePointer(e, handler);
};
obj['_leaflet_touchend' + id] = onUp;
obj.addEventListener(POINTER_UP, onUp, false);
obj.addEventListener(POINTER_CANCEL, onUp, false);
}

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
* Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
@ -5,307 +11,295 @@
// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Adds a listener function (`fn`) to a particular DOM event type of the
// element `el`. You can optionally specify the context of the listener
// (object the `this` keyword will point to). You can also pass several
// space-separated types (e.g. `'click dblclick'`).
// @alternative
// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
export function on(obj, types, fn, context) {
if (typeof types === 'object') {
for (var type in types) {
addOne(obj, type, types[type], fn);
}
} else {
types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
addOne(obj, types[i], fn, context);
}
}
return this;
}
// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Removes a previously added listener function. If no function is specified,
// it will remove all the listeners of that particular DOM event from the element.
// Note that if you passed a custom context to on, you must pass the same
// context to `off` in order to remove the listener.
// @alternative
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
export function off(obj, types, fn, context) {
if (typeof types === 'object') {
for (var type in types) {
removeOne(obj, type, types[type], fn);
}
} else {
types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
removeOne(obj, types[i], fn, context);
}
}
return this;
}
var eventsKey = '_leaflet_events';
L.DomEvent = {
function addOne(obj, type, fn, context) {
var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : '');
// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Adds a listener function (`fn`) to a particular DOM event type of the
// element `el`. You can optionally specify the context of the listener
// (object the `this` keyword will point to). You can also pass several
// space-separated types (e.g. `'click dblclick'`).
if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
// @alternative
// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
on: function (obj, types, fn, context) {
var handler = function (e) {
return fn.call(context || obj, e || window.event);
};
if (typeof types === 'object') {
for (var type in types) {
this._on(obj, type, types[type], fn);
}
} else {
types = L.Util.splitWords(types);
var originalHandler = handler;
for (var i = 0, len = types.length; i < len; i++) {
this._on(obj, types[i], fn, context);
}
}
if (Browser.pointer && type.indexOf('touch') === 0) {
// Needs DomEvent.Pointer.js
addPointerListener(obj, type, handler, id);
return this;
},
} else if (Browser.touch && (type === 'dblclick') && addDoubleTapListener &&
!(Browser.pointer && Browser.chrome)) {
// Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
// See #5180
addDoubleTapListener(obj, handler, id);
// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Removes a previously added listener function. If no function is specified,
// it will remove all the listeners of that particular DOM event from the element.
// Note that if you passed a custom context to on, you must pass the same
// context to `off` in order to remove the listener.
} else if ('addEventListener' in obj) {
// @alternative
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
off: function (obj, types, fn, context) {
if (type === 'mousewheel') {
obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
if (typeof types === 'object') {
for (var type in types) {
this._off(obj, type, types[type], fn);
}
} else {
types = L.Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._off(obj, types[i], fn, context);
}
}
return this;
},
_on: function (obj, type, fn, context) {
var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
var handler = function (e) {
return fn.call(context || obj, e || window.event);
};
var originalHandler = handler;
if (L.Browser.pointer && type.indexOf('touch') === 0) {
this.addPointerListener(obj, type, handler, id);
} else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
!(L.Browser.pointer && L.Browser.chrome)) {
// Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
// See #5180
this.addDoubleTapListener(obj, handler, id);
} else if ('addEventListener' in obj) {
if (type === 'mousewheel') {
obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
handler = function (e) {
e = e || window.event;
if (L.DomEvent._isExternalTarget(obj, e)) {
originalHandler(e);
}
};
obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
} else {
if (type === 'click' && L.Browser.android) {
handler = function (e) {
return L.DomEvent._filterClick(e, originalHandler);
};
} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
handler = function (e) {
e = e || window.event;
if (isExternalTarget(obj, e)) {
originalHandler(e);
}
obj.addEventListener(type, handler, false);
}
};
obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
} else if ('attachEvent' in obj) {
obj.attachEvent('on' + type, handler);
}
obj[eventsKey] = obj[eventsKey] || {};
obj[eventsKey][id] = handler;
return this;
},
_off: function (obj, type, fn, context) {
var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
handler = obj[eventsKey] && obj[eventsKey][id];
if (!handler) { return this; }
if (L.Browser.pointer && type.indexOf('touch') === 0) {
this.removePointerListener(obj, type, id);
} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
this.removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
if (type === 'mousewheel') {
obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
} else {
obj.removeEventListener(
type === 'mouseenter' ? 'mouseover' :
type === 'mouseleave' ? 'mouseout' : type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent('on' + type, handler);
}
obj[eventsKey][id] = null;
return this;
},
// @function stopPropagation(ev: DOMEvent): this
// Stop the given event from propagation to parent elements. Used inside the listener functions:
// ```js
// L.DomEvent.on(div, 'click', function (ev) {
// L.DomEvent.stopPropagation(ev);
// });
// ```
stopPropagation: function (e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (e.originalEvent) { // In case of Leaflet event.
e.originalEvent._stopped = true;
} else {
e.cancelBubble = true;
}
L.DomEvent._skipped(e);
return this;
},
// @function disableScrollPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
disableScrollPropagation: function (el) {
return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
},
// @function disableClickPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
// `'mousedown'` and `'touchstart'` events (plus browser variants).
disableClickPropagation: function (el) {
var stop = L.DomEvent.stopPropagation;
L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
return L.DomEvent.on(el, {
click: L.DomEvent._fakeStop,
dblclick: stop
});
},
// @function preventDefault(ev: DOMEvent): this
// Prevents the default action of the DOM Event `ev` from happening (such as
// following a link in the href of the a element, or doing a POST request
// with page reload when a `<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;
if (type === 'click' && Browser.android) {
handler = function (e) {
filterClick(e, originalHandler);
};
}
} catch (err) {
return false;
obj.addEventListener(type, handler, false);
}
return (related !== el);
},
// this is a horrible workaround for a bug in Android where a single touch triggers two click events
_filterClick: function (e, handler) {
var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
// are they closer together than 500ms yet more than 100ms?
// Android typically triggers them ~300ms apart while multiple listeners
// on the same event should be triggered far faster;
// or check if click is simulated on the element, and if it is, reject any non-simulated events
if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
L.DomEvent.stop(e);
return;
}
L.DomEvent._lastClick = timeStamp;
handler(e);
} else if ('attachEvent' in obj) {
obj.attachEvent('on' + type, handler);
}
};
obj[eventsKey] = obj[eventsKey] || {};
obj[eventsKey][id] = handler;
}
function removeOne(obj, type, fn, context) {
var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : ''),
handler = obj[eventsKey] && obj[eventsKey][id];
if (!handler) { return this; }
if (Browser.pointer && type.indexOf('touch') === 0) {
removePointerListener(obj, type, id);
} else if (Browser.touch && (type === 'dblclick') && removeDoubleTapListener) {
removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
if (type === 'mousewheel') {
obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
} else {
obj.removeEventListener(
type === 'mouseenter' ? 'mouseover' :
type === 'mouseleave' ? 'mouseout' : type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent('on' + type, handler);
}
obj[eventsKey][id] = null;
}
// @function stopPropagation(ev: DOMEvent): this
// Stop the given event from propagation to parent elements. Used inside the listener functions:
// ```js
// L.DomEvent.on(div, 'click', function (ev) {
// L.DomEvent.stopPropagation(ev);
// });
// ```
export function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (e.originalEvent) { // In case of Leaflet event.
e.originalEvent._stopped = true;
} else {
e.cancelBubble = true;
}
skipped(e);
return this;
}
// @function disableScrollPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
export function disableScrollPropagation(el) {
return addOne(el, 'mousewheel', stopPropagation);
}
// @function disableClickPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
// `'mousedown'` and `'touchstart'` events (plus browser variants).
export function disableClickPropagation(el) {
on(el, 'mousedown touchstart dblclick', stopPropagation);
addOne(el, 'click', fakeStop);
return this;
}
// @function preventDefault(ev: DOMEvent): this
// Prevents the default action of the DOM Event `ev` from happening (such as
// following a link in the href of the a element, or doing a POST request
// with page reload when a `<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
// Alias to [`L.DomEvent.on`](#domevent-on)
L.DomEvent.addListener = L.DomEvent.on;
export {on as addListener};
// @function removeListener(…): this
// Alias to [`L.DomEvent.off`](#domevent-off)
L.DomEvent.removeListener = L.DomEvent.off;
export {off as removeListener};

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
*
@ -9,312 +14,301 @@
* in HTML and SVG classes in SVG.
*/
L.DomUtil = {
// @function get(id: String|HTMLElement): HTMLElement
// Returns an element given its DOM id, or returns the element itself
// if it was passed directly.
get: function (id) {
return typeof id === 'string' ? document.getElementById(id) : id;
},
// @property TRANSFORM: String
// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
export var TRANSFORM = testProp(
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// @function getStyle(el: HTMLElement, styleAttrib: String): String
// Returns the value for a certain style attribute on an element,
// including computed values or values set through CSS.
getStyle: function (el, style) {
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
// @property TRANSITION: String
// Vendor-prefixed transform style name.
export var TRANSITION = testProp(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
export var TRANSITION_END =
TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
return value === 'auto' ? null : value;
},
// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
create: function (tagName, className, container) {
// @function get(id: String|HTMLElement): HTMLElement
// Returns an element given its DOM id, or returns the element itself
// if it was passed directly.
export function get(id) {
return typeof id === 'string' ? document.getElementById(id) : id;
}
var el = document.createElement(tagName);
el.className = className || '';
// @function getStyle(el: HTMLElement, styleAttrib: String): String
// Returns the value for a certain style attribute on an element,
// including computed values or values set through CSS.
export function getStyle(el, style) {
var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
if (container) {
container.appendChild(el);
}
return el;
},
// @function remove(el: HTMLElement)
// Removes `el` from its parent element
remove: function (el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
},
// @function empty(el: HTMLElement)
// Removes all of `el`'s children elements from `el`
empty: function (el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
},
// @function toFront(el: HTMLElement)
// Makes `el` the last children of its parent, so it renders in front of the other children.
toFront: function (el) {
el.parentNode.appendChild(el);
},
// @function toBack(el: HTMLElement)
// Makes `el` the first children of its parent, so it renders back from the other children.
toBack: function (el) {
var parent = el.parentNode;
parent.insertBefore(el, parent.firstChild);
},
// @function hasClass(el: HTMLElement, name: String): Boolean
// Returns `true` if the element's class attribute contains `name`.
hasClass: function (el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = L.DomUtil.getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
},
// @function addClass(el: HTMLElement, name: String)
// Adds `name` to the element's class attribute.
addClass: function (el, name) {
if (el.classList !== undefined) {
var classes = L.Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!L.DomUtil.hasClass(el, name)) {
var className = L.DomUtil.getClass(el);
L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
}
},
// @function removeClass(el: HTMLElement, name: String)
// Removes `name` from the element's class attribute.
removeClass: function (el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
},
// @function setClass(el: HTMLElement, name: String)
// Sets the element's class.
setClass: function (el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
},
// @function getClass(el: HTMLElement): String
// Returns the element's class.
getClass: function (el) {
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
},
// @function setOpacity(el: HTMLElement, opacity: Number)
// Set the opacity of an element (including old IE support).
// `opacity` must be a number from `0` to `1`.
setOpacity: function (el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
L.DomUtil._setOpacityIE(el, value);
}
},
_setOpacityIE: function (el, value) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) { return; }
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = (value !== 100);
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
},
// @function testProp(props: String[]): String|false
// Goes through the array of style names and returns the first name
// that is a valid style name for an element. If no such name is found,
// it returns false. Useful for vendor-prefixed styles like `transform`.
testProp: function (props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
},
// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
// and optionally scaled by `scale`. Does not have an effect if the
// browser doesn't support 3D CSS transforms.
setTransform: function (el, offset, scale) {
var pos = offset || new L.Point(0, 0);
el.style[L.DomUtil.TRANSFORM] =
(L.Browser.ie3d ?
'translate(' + pos.x + 'px,' + pos.y + 'px)' :
'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
(scale ? ' scale(' + scale + ')' : '');
},
// @function setPosition(el: HTMLElement, position: Point)
// Sets the position of `el` to coordinates specified by `position`,
// using CSS translate or top/left positioning depending on the browser
// (used by Leaflet internally to position its layers).
setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
/*eslint-disable */
el._leaflet_pos = point;
/*eslint-enable */
if (L.Browser.any3d) {
L.DomUtil.setTransform(el, point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
},
// @function getPosition(el: HTMLElement): Point
// Returns the coordinates of an element previously positioned with setPosition.
getPosition: function (el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new L.Point(0, 0);
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
};
return value === 'auto' ? null : value;
}
// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
export function create(tagName, className, container) {
var el = document.createElement(tagName);
el.className = className || '';
(function () {
// prefix style property names
if (container) {
container.appendChild(el);
}
return el;
}
// @property TRANSFORM: String
// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
L.DomUtil.TRANSFORM = L.DomUtil.testProp(
['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// @function remove(el: HTMLElement)
// Removes `el` from its parent element
export function remove(el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
// @function empty(el: HTMLElement)
// Removes all of `el`'s children elements from `el`
export function empty(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
// @function toFront(el: HTMLElement)
// Makes `el` the last children of its parent, so it renders in front of the other children.
export function toFront(el) {
el.parentNode.appendChild(el);
}
// @property TRANSITION: String
// Vendor-prefixed transform style name.
var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
// @function toBack(el: HTMLElement)
// Makes `el` the first children of its parent, so it renders back from the other children.
export function toBack(el) {
var parent = el.parentNode;
parent.insertBefore(el, parent.firstChild);
}
L.DomUtil.TRANSITION_END =
transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
// @function hasClass(el: HTMLElement, name: String): Boolean
// Returns `true` if the element's class attribute contains `name`.
export function hasClass(el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
}
// @function disableTextSelection()
// Prevents the user from generating `selectstart` DOM events, usually generated
// when the user drags the mouse through a page with text. Used internally
// by Leaflet to override the behaviour of any click-and-drag interaction on
// the map. Affects drag interactions on the whole document.
// @function enableTextSelection()
// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
if ('onselectstart' in document) {
L.DomUtil.disableTextSelection = function () {
L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
};
L.DomUtil.enableTextSelection = function () {
L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
};
// @function addClass(el: HTMLElement, name: String)
// Adds `name` to the element's class attribute.
export function addClass(el, name) {
if (el.classList !== undefined) {
var classes = Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!hasClass(el, name)) {
var className = getClass(el);
setClass(el, (className ? className + ' ' : '') + name);
}
}
// @function removeClass(el: HTMLElement, name: String)
// Removes `name` from the element's class attribute.
export function removeClass(el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
var userSelectProperty = L.DomUtil.testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
}
L.DomUtil.disableTextSelection = function () {
if (userSelectProperty) {
var style = document.documentElement.style;
this._userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
}
};
L.DomUtil.enableTextSelection = function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = this._userSelect;
delete this._userSelect;
}
};
// @function setClass(el: HTMLElement, name: String)
// Sets the element's class.
export function setClass(el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
}
// @function getClass(el: HTMLElement): String
// Returns the element's class.
export function getClass(el) {
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
}
// @function setOpacity(el: HTMLElement, opacity: Number)
// Set the opacity of an element (including old IE support).
// `opacity` must be a number from `0` to `1`.
export function setOpacity(el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
_setOpacityIE(el, value);
}
}
function _setOpacityIE(el, value) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) { return; }
}
// @function disableImageDrag()
// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
// for `dragstart` DOM events, usually generated when the user drags an image.
L.DomUtil.disableImageDrag = function () {
L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
};
value = Math.round(value * 100);
// @function enableImageDrag()
// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
L.DomUtil.enableImageDrag = function () {
L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
};
if (filter) {
filter.Enabled = (value !== 100);
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
// @function preventOutline(el: HTMLElement)
// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
// of the element `el` invisible. Used internally by Leaflet to prevent
// focusable elements from displaying an outline when the user performs a
// drag interaction on them.
L.DomUtil.preventOutline = function (element) {
while (element.tabIndex === -1) {
element = element.parentNode;
// @function testProp(props: String[]): String|false
// Goes through the array of style names and returns the first name
// that is a valid style name for an element. If no such name is found,
// it returns false. Useful for vendor-prefixed styles like `transform`.
export function testProp(props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
if (!element || !element.style) { return; }
L.DomUtil.restoreOutline();
this._outlineElement = element;
this._outlineStyle = element.style.outline;
element.style.outline = 'none';
L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
};
}
return false;
}
// @function restoreOutline()
// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
L.DomUtil.restoreOutline = function () {
if (!this._outlineElement) { return; }
this._outlineElement.style.outline = this._outlineStyle;
delete this._outlineElement;
delete this._outlineStyle;
L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
// and optionally scaled by `scale`. Does not have an effect if the
// browser doesn't support 3D CSS transforms.
export function setTransform(el, offset, scale) {
var pos = offset || new Point(0, 0);
el.style[TRANSFORM] =
(Browser.ie3d ?
'translate(' + pos.x + 'px,' + pos.y + 'px)' :
'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
(scale ? ' scale(' + scale + ')' : '');
}
// @function setPosition(el: HTMLElement, position: Point)
// Sets the position of `el` to coordinates specified by `position`,
// using CSS translate or top/left positioning depending on the browser
// (used by Leaflet internally to position its layers).
export function setPosition(el, point) {
/*eslint-disable */
el._leaflet_pos = point;
/*eslint-enable */
if (Browser.any3d) {
setTransform(el, point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
}
// @function getPosition(el: HTMLElement): Point
// Returns the coordinates of an element previously positioned with setPosition.
export function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new Point(0, 0);
}
// @function disableTextSelection()
// Prevents the user from generating `selectstart` DOM events, usually generated
// when the user drags the mouse through a page with text. Used internally
// by Leaflet to override the behaviour of any click-and-drag interaction on
// the map. Affects drag interactions on the whole document.
// @function enableTextSelection()
// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
export var disableTextSelection;
export var enableTextSelection;
var _userSelect;
if ('onselectstart' in document) {
disableTextSelection = function () {
DomEvent.on(window, 'selectstart', DomEvent.preventDefault);
};
})();
enableTextSelection = function () {
DomEvent.off(window, 'selectstart', DomEvent.preventDefault);
};
} else {
var userSelectProperty = testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
disableTextSelection = function () {
if (userSelectProperty) {
var style = document.documentElement.style;
_userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
}
};
enableTextSelection = function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = _userSelect;
_userSelect = undefined;
}
};
}
// @function disableImageDrag()
// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
// for `dragstart` DOM events, usually generated when the user drags an image.
export function disableImageDrag() {
DomEvent.on(window, 'dragstart', DomEvent.preventDefault);
}
// @function enableImageDrag()
// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
export function enableImageDrag() {
DomEvent.off(window, 'dragstart', DomEvent.preventDefault);
}
var _outlineElement, _outlineStyle;
// @function preventOutline(el: HTMLElement)
// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
// of the element `el` invisible. Used internally by Leaflet to prevent
// focusable elements from displaying an outline when the user performs a
// drag interaction on them.
export function preventOutline(element) {
while (element.tabIndex === -1) {
element = element.parentNode;
}
if (!element || !element.style) { return; }
restoreOutline();
_outlineElement = element;
_outlineStyle = element.style.outline;
element.style.outline = 'none';
DomEvent.on(window, 'keydown', restoreOutline);
}
// @function restoreOutline()
// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
export function restoreOutline() {
if (!_outlineElement) { return; }
_outlineElement.style.outline = _outlineStyle;
_outlineElement = undefined;
_outlineStyle = undefined;
DomEvent.off(window, 'keydown', restoreOutline);
}

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

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

View File

@ -1,3 +1,7 @@
import * as Util from '../core/Util';
import {Earth} from './crs/CRS.Earth';
import {toLatLngBounds} from './LatLngBounds';
/* @class LatLng
* @aka L.LatLng
*
@ -19,7 +23,7 @@
* ```
*/
L.LatLng = function (lat, lng, alt) {
export function LatLng(lat, lng, alt) {
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
@ -37,15 +41,15 @@ L.LatLng = function (lat, lng, alt) {
if (alt !== undefined) {
this.alt = +alt;
}
};
}
L.LatLng.prototype = {
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
equals: function (obj, maxMargin) {
if (!obj) { return false; }
obj = L.latLng(obj);
obj = toLatLng(obj);
var margin = Math.max(
Math.abs(this.lat - obj.lat),
@ -58,20 +62,20 @@ L.LatLng.prototype = {
// Returns a string representation of the point (for debugging purposes).
toString: function (precision) {
return 'LatLng(' +
L.Util.formatNum(this.lat, precision) + ', ' +
L.Util.formatNum(this.lng, precision) + ')';
Util.formatNum(this.lat, precision) + ', ' +
Util.formatNum(this.lng, precision) + ')';
},
// @method distanceTo(otherLatLng: LatLng): Number
// Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
distanceTo: function (other) {
return L.CRS.Earth.distance(this, L.latLng(other));
return Earth.distance(this, toLatLng(other));
},
// @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap: function () {
return L.CRS.Earth.wrapLatLng(this);
return Earth.wrapLatLng(this);
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
@ -80,13 +84,13 @@ L.LatLng.prototype = {
var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
return L.latLngBounds(
return toLatLngBounds(
[this.lat - latAccuracy, this.lng - lngAccuracy],
[this.lat + latAccuracy, this.lng + lngAccuracy]);
},
clone: function () {
return new L.LatLng(this.lat, this.lng, this.alt);
return new LatLng(this.lat, this.lng, this.alt);
}
};
@ -103,16 +107,16 @@ L.LatLng.prototype = {
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
L.latLng = function (a, b, c) {
if (a instanceof L.LatLng) {
export function toLatLng(a, b, c) {
if (a instanceof LatLng) {
return a;
}
if (L.Util.isArray(a) && typeof a[0] !== 'object') {
if (Util.isArray(a) && typeof a[0] !== 'object') {
if (a.length === 3) {
return new L.LatLng(a[0], a[1], a[2]);
return new LatLng(a[0], a[1], a[2]);
}
if (a.length === 2) {
return new L.LatLng(a[0], a[1]);
return new LatLng(a[0], a[1]);
}
return null;
}
@ -120,10 +124,10 @@ L.latLng = function (a, b, c) {
return a;
}
if (typeof a === 'object' && 'lat' in a) {
return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
}
if (b === undefined) {
return null;
}
return new L.LatLng(a, b, c);
};
return new LatLng(a, b, c);
}

View File

@ -1,3 +1,5 @@
import {LatLng, toLatLng} from './LatLng';
/*
* @class LatLngBounds
* @aka L.LatLngBounds
@ -24,7 +26,7 @@
* Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
*/
L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
export function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
if (!corner1) { return; }
var latlngs = corner2 ? [corner1, corner2] : corner1;
@ -32,9 +34,9 @@ L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
};
}
L.LatLngBounds.prototype = {
LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point
@ -47,23 +49,23 @@ L.LatLngBounds.prototype = {
ne = this._northEast,
sw2, ne2;
if (obj instanceof L.LatLng) {
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof L.LatLngBounds) {
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) { return this; }
} else {
return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
}
if (!sw && !ne) {
this._southWest = new L.LatLng(sw2.lat, sw2.lng);
this._northEast = new L.LatLng(ne2.lat, ne2.lng);
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
@ -82,15 +84,15 @@ L.LatLngBounds.prototype = {
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new L.LatLngBounds(
new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
return new LatLngBounds(
new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
// @method getCenter(): LatLng
// Returns the center point of the bounds.
getCenter: function () {
return new L.LatLng(
return new LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
@ -110,13 +112,13 @@ L.LatLngBounds.prototype = {
// @method getNorthWest(): LatLng
// Returns the north-west point of the bounds.
getNorthWest: function () {
return new L.LatLng(this.getNorth(), this.getWest());
return new LatLng(this.getNorth(), this.getWest());
},
// @method getSouthEast(): LatLng
// Returns the south-east point of the bounds.
getSouthEast: function () {
return new L.LatLng(this.getSouth(), this.getEast());
return new LatLng(this.getSouth(), this.getEast());
},
// @method getWest(): Number
@ -150,17 +152,17 @@ L.LatLngBounds.prototype = {
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
obj = L.latLng(obj);
if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = toLatLng(obj);
} else {
obj = L.latLngBounds(obj);
obj = toLatLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof L.LatLngBounds) {
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
@ -174,7 +176,7 @@ L.LatLngBounds.prototype = {
// @method intersects(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects: function (bounds) {
bounds = L.latLngBounds(bounds);
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
@ -190,7 +192,7 @@ L.LatLngBounds.prototype = {
// @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps: function (bounds) {
bounds = L.latLngBounds(bounds);
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
@ -214,7 +216,7 @@ L.LatLngBounds.prototype = {
equals: function (bounds) {
if (!bounds) { return false; }
bounds = L.latLngBounds(bounds);
bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest()) &&
this._northEast.equals(bounds.getNorthEast());
@ -235,9 +237,9 @@ L.LatLngBounds.prototype = {
// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
L.latLngBounds = function (a, b) {
if (a instanceof L.LatLngBounds) {
export function toLatLngBounds(a, b) {
if (a instanceof LatLngBounds) {
return a;
}
return new L.LatLngBounds(a, b);
};
return new LatLngBounds(a, b);
}

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

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

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

View File

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

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

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

View File

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

View File

@ -1,3 +1,7 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @projection L.Projection.Mercator
@ -5,11 +9,11 @@
* Elliptical Mercator projection more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
*/
L.Projection.Mercator = {
export var Mercator = {
R: 6378137,
R_MINOR: 6356752.314245179,
bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project: function (latlng) {
var d = Math.PI / 180,
@ -22,7 +26,7 @@ L.Projection.Mercator = {
var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));
return new L.Point(latlng.lng * d * r, y);
return new Point(latlng.lng * d * r, y);
},
unproject: function (point) {
@ -40,6 +44,6 @@ L.Projection.Mercator = {
phi += dphi;
}
return new L.LatLng(phi * d, point.x * d / r);
return new LatLng(phi * d, point.x * d / r);
}
};

View File

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

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

View File

@ -1,228 +1,236 @@
import {Point} from './Point';
import * as Util from '../core/Util';
/*
* @namespace LineUtil
*
* Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
*/
L.LineUtil = {
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
// Improves rendering performance dramatically by lessening the number of points to draw.
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
// Improves rendering performance dramatically by lessening the number of points to draw.
// @function simplify(points: Point[], tolerance: Number): Point[]
// Dramatically reduces the number of points in a polyline while retaining
// its shape and returns a new array of simplified points, using the
// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
// Used for a huge performance boost when processing/displaying Leaflet polylines for
// each zoom level and also reducing visual noise. tolerance affects the amount of
// simplification (lesser value means higher quality but slower and with more points).
// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
simplify: function (points, tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = this._reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = this._simplifyDP(points, sqTolerance);
return points;
},
// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
// Returns the distance between point `p` and segment `p1` to `p2`.
pointToSegmentDistance: function (p, p1, p2) {
return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
},
// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
// Returns the closest point from a point `p` on a segment `p1` to `p2`.
closestPointOnSegment: function (p, p1, p2) {
return this._sqClosestPointOnSegment(p, p1, p2);
},
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
_simplifyDP: function (points, sqTolerance) {
var len = points.length,
ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
},
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
var maxSqDist = 0,
index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
this._simplifyDPStep(points, markers, sqTolerance, first, index);
this._simplifyDPStep(points, markers, sqTolerance, index, last);
}
},
// reduce points that are too close to each other to a single point
_reducePoints: function (points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
},
// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
// Clips the segment a to b by rectangular bounds with the
// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
// (modifying the segment points directly!). Used by Leaflet to only show polyline
// points that are on the screen or near, increasing performance.
clipSegment: function (a, b, bounds, useLastCode, round) {
var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
codeB = this._getBitCode(b, bounds),
codeOut, p, newCode;
// save 2nd code to avoid calculating it on the next segment
this._lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
}
// if a,b is outside the clip window (trivial reject)
if (codeA & codeB) {
return false;
}
// other cases
codeOut = codeA || codeB;
p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = this._getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
},
_getEdgeIntersection: function (a, b, code, bounds, round) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max,
x, y;
if (code & 8) { // top
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) { // bottom
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) { // right
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) { // left
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new L.Point(x, y, round);
},
_getBitCode: function (p, bounds) {
var code = 0;
if (p.x < bounds.min.x) { // left
code |= 1;
} else if (p.x > bounds.max.x) { // right
code |= 2;
}
if (p.y < bounds.min.y) { // bottom
code |= 4;
} else if (p.y > bounds.max.y) { // top
code |= 8;
}
return code;
},
// square distance (to avoid unnecessary Math.sqrt calls)
_sqDist: function (p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
},
// return closest point on segment or distance to that point
_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
// @function simplify(points: Point[], tolerance: Number): Point[]
// Dramatically reduces the number of points in a polyline while retaining
// its shape and returns a new array of simplified points, using the
// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
// Used for a huge performance boost when processing/displaying Leaflet polylines for
// each zoom level and also reducing visual noise. tolerance affects the amount of
// simplification (lesser value means higher quality but slower and with more points).
// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
export function simplify(points, tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
};
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = _reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = _simplifyDP(points, sqTolerance);
return points;
}
// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
// Returns the distance between point `p` and segment `p1` to `p2`.
export function pointToSegmentDistance(p, p1, p2) {
return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
}
// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
// Returns the closest point from a point `p` on a segment `p1` to `p2`.
export function closestPointOnSegment(p, p1, p2) {
return _sqClosestPointOnSegment(p, p1, p2);
}
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
function _simplifyDP(points, sqTolerance) {
var len = points.length,
ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
function _simplifyDPStep(points, markers, sqTolerance, first, last) {
var maxSqDist = 0,
index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
_simplifyDPStep(points, markers, sqTolerance, first, index);
_simplifyDPStep(points, markers, sqTolerance, index, last);
}
}
// reduce points that are too close to each other to a single point
function _reducePoints(points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (_sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
}
var _lastCode;
// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
// Clips the segment a to b by rectangular bounds with the
// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
// (modifying the segment points directly!). Used by Leaflet to only show polyline
// points that are on the screen or near, increasing performance.
export function clipSegment(a, b, bounds, useLastCode, round) {
var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
codeB = _getBitCode(b, bounds),
codeOut, p, newCode;
// save 2nd code to avoid calculating it on the next segment
_lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
}
// if a,b is outside the clip window (trivial reject)
if (codeA & codeB) {
return false;
}
// other cases
codeOut = codeA || codeB;
p = _getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = _getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
export function _getEdgeIntersection(a, b, code, bounds, round) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max,
x, y;
if (code & 8) { // top
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) { // bottom
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) { // right
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) { // left
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new Point(x, y, round);
}
export function _getBitCode(p, bounds) {
var code = 0;
if (p.x < bounds.min.x) { // left
code |= 1;
} else if (p.x > bounds.max.x) { // right
code |= 2;
}
if (p.y < bounds.min.y) { // bottom
code |= 4;
} else if (p.y > bounds.max.y) { // top
code |= 8;
}
return code;
}
// square distance (to avoid unnecessary Math.sqrt calls)
function _sqDist(p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
}
// return closest point on segment or distance to that point
export function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new Point(x, y);
}
export function _flat(latlngs) {
// true if it's a flat array of latlngs; false if nested
return !Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
}

View File

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

View File

@ -1,26 +1,25 @@
import * as LineUtil from './LineUtil';
/*
* @namespace PolyUtil
* Various utility functions for polygon geometries.
*/
L.PolyUtil = {};
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
* Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
* Used by Leaflet to only show polygon points that are on the screen or near, increasing
* performance. Note that polygon points needs different algorithm for clipping
* than polyline, so there's a seperate method for it.
*/
L.PolyUtil.clipPolygon = function (points, bounds, round) {
export function clipPolygon(points, bounds, round) {
var clippedPoints,
edges = [1, 4, 2, 8],
i, j, k,
a, b,
len, edge, p,
lu = L.LineUtil;
len, edge, p;
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = lu._getBitCode(points[i], bounds);
points[i]._code = LineUtil._getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
@ -36,16 +35,16 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) {
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = lu._getEdgeIntersection(b, a, edge, bounds, round);
p._code = lu._getBitCode(p, bounds);
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = lu._getEdgeIntersection(b, a, edge, bounds, round);
p._code = lu._getBitCode(p, bounds);
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
}
@ -53,4 +52,4 @@ L.PolyUtil.clipPolygon = function (points, bounds, round) {
}
return points;
};
}

View File

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

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

View File

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

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
* @aka L.GeoJSON
@ -19,7 +31,7 @@
* ```
*/
L.GeoJSON = L.FeatureGroup.extend({
export var GeoJSON = FeatureGroup.extend({
/* @section
* @aka GeoJSON options
@ -69,7 +81,7 @@ L.GeoJSON = L.FeatureGroup.extend({
*/
initialize: function (geojson, options) {
L.setOptions(this, options);
Util.setOptions(this, options);
this._layers = {};
@ -81,7 +93,7 @@ L.GeoJSON = L.FeatureGroup.extend({
// @method addData( <GeoJSON> data ): this
// Adds a GeoJSON object to the layer.
addData: function (geojson) {
var features = L.Util.isArray(geojson) ? geojson : geojson.features,
var features = Util.isArray(geojson) ? geojson : geojson.features,
i, len, feature;
if (features) {
@ -99,11 +111,11 @@ L.GeoJSON = L.FeatureGroup.extend({
if (options.filter && !options.filter(geojson)) { return this; }
var layer = L.GeoJSON.geometryToLayer(geojson, options);
var layer = geometryToLayer(geojson, options);
if (!layer) {
return this;
}
layer.feature = L.GeoJSON.asFeature(geojson);
layer.feature = asFeature(geojson);
layer.defaultOptions = layer.options;
this.resetStyle(layer);
@ -119,7 +131,7 @@ L.GeoJSON = L.FeatureGroup.extend({
// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
resetStyle: function (layer) {
// reset any custom styles
layer.options = L.Util.extend({}, layer.defaultOptions);
layer.options = Util.extend({}, layer.defaultOptions);
this._setLayerStyle(layer, this.options.style);
return this;
},
@ -144,143 +156,142 @@ L.GeoJSON = L.FeatureGroup.extend({
// @section
// There are several static functions which can be called without instantiating L.GeoJSON:
L.extend(L.GeoJSON, {
// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
// Creates a `Layer` from a given GeoJSON feature. Can use a custom
// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
// functions if provided as options.
geometryToLayer: function (geojson, options) {
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
coords = geometry ? geometry.coordinates : null,
layers = [],
pointToLayer = options && options.pointToLayer,
coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
latlng, latlngs, i, len;
// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
// Creates a `Layer` from a given GeoJSON feature. Can use a custom
// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
// functions if provided as options.
export function geometryToLayer(geojson, options) {
if (!coords && !geometry) {
return null;
}
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
coords = geometry ? geometry.coordinates : null,
layers = [],
pointToLayer = options && options.pointToLayer,
_coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
latlng, latlngs, i, len;
switch (geometry.type) {
case 'Point':
latlng = coordsToLatLng(coords);
return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
case 'MultiPoint':
for (i = 0, len = coords.length; i < len; i++) {
latlng = coordsToLatLng(coords[i]);
layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
}
return new L.FeatureGroup(layers);
case 'LineString':
case 'MultiLineString':
latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
return new L.Polyline(latlngs, options);
case 'Polygon':
case 'MultiPolygon':
latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
return new L.Polygon(latlngs, options);
case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
var layer = this.geometryToLayer({
geometry: geometry.geometries[i],
type: 'Feature',
properties: geojson.properties
}, options);
if (layer) {
layers.push(layer);
}
}
return new L.FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
},
// @function coordsToLatLng(coords: Array): LatLng
// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
coordsToLatLng: function (coords) {
return new L.LatLng(coords[1], coords[0], coords[2]);
},
// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
var latlngs = [];
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ?
this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
(coordsToLatLng || this.coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
return latlngs;
},
// @function latLngToCoords(latlng: LatLng): Array
// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
latLngToCoords: function (latlng) {
return latlng.alt !== undefined ?
[latlng.lng, latlng.lat, latlng.alt] :
[latlng.lng, latlng.lat];
},
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
latLngsToCoords: function (latlngs, levelsDeep, closed) {
var coords = [];
for (var i = 0, len = latlngs.length; i < len; i++) {
coords.push(levelsDeep ?
L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
L.GeoJSON.latLngToCoords(latlngs[i]));
}
if (!levelsDeep && closed) {
coords.push(coords[0]);
}
return coords;
},
getFeature: function (layer, newGeometry) {
return layer.feature ?
L.extend({}, layer.feature, {geometry: newGeometry}) :
L.GeoJSON.asFeature(newGeometry);
},
// @function asFeature(geojson: Object): Object
// Normalize GeoJSON geometries/features into GeoJSON features.
asFeature: function (geojson) {
if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
return geojson;
}
return {
type: 'Feature',
properties: {},
geometry: geojson
};
if (!coords && !geometry) {
return null;
}
});
switch (geometry.type) {
case 'Point':
latlng = _coordsToLatLng(coords);
return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
case 'MultiPoint':
for (i = 0, len = coords.length; i < len; i++) {
latlng = _coordsToLatLng(coords[i]);
layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
}
return new FeatureGroup(layers);
case 'LineString':
case 'MultiLineString':
latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
return new Polyline(latlngs, options);
case 'Polygon':
case 'MultiPolygon':
latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
return new Polygon(latlngs, options);
case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
var layer = geometryToLayer({
geometry: geometry.geometries[i],
type: 'Feature',
properties: geojson.properties
}, options);
if (layer) {
layers.push(layer);
}
}
return new FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
}
// @function coordsToLatLng(coords: Array): LatLng
// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
export function coordsToLatLng(coords) {
return new LatLng(coords[1], coords[0], coords[2]);
}
// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
export function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
var latlngs = [];
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ?
coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
(_coordsToLatLng || coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
return latlngs;
}
// @function latLngToCoords(latlng: LatLng): Array
// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
export function latLngToCoords(latlng) {
return latlng.alt !== undefined ?
[latlng.lng, latlng.lat, latlng.alt] :
[latlng.lng, latlng.lat];
}
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
export function latLngsToCoords(latlngs, levelsDeep, closed) {
var coords = [];
for (var i = 0, len = latlngs.length; i < len; i++) {
coords.push(levelsDeep ?
latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
latLngToCoords(latlngs[i]));
}
if (!levelsDeep && closed) {
coords.push(coords[0]);
}
return coords;
}
export function getFeature(layer, newGeometry) {
return layer.feature ?
Util.extend({}, layer.feature, {geometry: newGeometry}) :
asFeature(newGeometry);
}
// @function asFeature(geojson: Object): Object
// Normalize GeoJSON geometries/features into GeoJSON features.
export function asFeature(geojson) {
if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
return geojson;
}
return {
type: 'Feature',
properties: {},
geometry: geojson
};
}
var PointToGeoJSON = {
toGeoJSON: function () {
return L.GeoJSON.getFeature(this, {
return getFeature(this, {
type: 'Point',
coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
coordinates: latLngToCoords(this.getLatLng())
});
}
};
@ -288,51 +299,55 @@ var PointToGeoJSON = {
// @namespace Marker
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
L.Marker.include(PointToGeoJSON);
Marker.include(PointToGeoJSON);
// @namespace CircleMarker
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
L.Circle.include(PointToGeoJSON);
L.CircleMarker.include(PointToGeoJSON);
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);
// @namespace Polyline
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
L.Polyline.prototype.toGeoJSON = function () {
var multi = !L.Polyline._flat(this._latlngs);
Polyline.include({
toGeoJSON: function () {
var multi = !LineUtil._flat(this._latlngs);
var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0);
return L.GeoJSON.getFeature(this, {
type: (multi ? 'Multi' : '') + 'LineString',
coordinates: coords
});
};
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'LineString',
coordinates: coords
});
}
});
// @namespace Polygon
// @method toGeoJSON(): Object
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
L.Polygon.prototype.toGeoJSON = function () {
var holes = !L.Polyline._flat(this._latlngs),
multi = holes && !L.Polyline._flat(this._latlngs[0]);
Polygon.include({
toGeoJSON: function () {
var holes = !LineUtil._flat(this._latlngs),
multi = holes && !LineUtil._flat(this._latlngs[0]);
var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
if (!holes) {
coords = [coords];
if (!holes) {
coords = [coords];
}
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
}
return L.GeoJSON.getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
};
});
// @namespace LayerGroup
L.LayerGroup.include({
LayerGroup.include({
toMultiPoint: function () {
var coords = [];
@ -340,7 +355,7 @@ L.LayerGroup.include({
coords.push(layer.toGeoJSON().geometry.coordinates);
});
return L.GeoJSON.getFeature(this, {
return getFeature(this, {
type: 'MultiPoint',
coordinates: coords
});
@ -362,12 +377,12 @@ L.LayerGroup.include({
this.eachLayer(function (layer) {
if (layer.toGeoJSON) {
var json = layer.toGeoJSON();
jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
jsons.push(isGeometryCollection ? json.geometry : asFeature(json));
}
});
if (isGeometryCollection) {
return L.GeoJSON.getFeature(this, {
return getFeature(this, {
geometries: jsons,
type: 'GeometryCollection'
});
@ -385,8 +400,9 @@ L.LayerGroup.include({
// Creates a GeoJSON layer. Optionally accepts an object in
// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
// (you can alternatively add it later with `addData` method) and an `options` object.
L.geoJSON = function (geojson, options) {
return new L.GeoJSON(geojson, options);
};
export function geoJSON(geojson, options) {
return new GeoJSON(geojson, options);
}
// Backward compatibility.
L.geoJson = L.geoJSON;
export var geoJson = geoJSON;

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

View File

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

View File

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

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

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

View File

@ -1,3 +1,6 @@
import {Icon} from './Icon';
import {toPoint as point} from '../../geometry/Point';
/*
* @class DivIcon
* @aka L.DivIcon
@ -17,7 +20,7 @@
* By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
*/
L.DivIcon = L.Icon.extend({
export var DivIcon = Icon.extend({
options: {
// @section
// @aka DivIcon options
@ -44,7 +47,7 @@ L.DivIcon = L.Icon.extend({
div.innerHTML = options.html !== false ? options.html : '';
if (options.bgPos) {
var bgPos = L.point(options.bgPos);
var bgPos = point(options.bgPos);
div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
}
this._setIconStyles(div, 'icon');
@ -59,6 +62,6 @@ L.DivIcon = L.Icon.extend({
// @factory L.divIcon(options: DivIcon options)
// Creates a `DivIcon` instance with the given options.
L.divIcon = function (options) {
return new L.DivIcon(options);
};
export function divIcon(options) {
return new DivIcon(options);
}

View File

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

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

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

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

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

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
* @inherits TileLayer
@ -16,7 +21,7 @@
* ```
*/
L.TileLayer.WMS = L.TileLayer.extend({
export var TileLayerWMS = TileLayer.extend({
// @section
// @aka TileLayer.WMS options
@ -63,7 +68,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
this._url = url;
var wmsParams = L.extend({}, this.defaultWmsParams);
var wmsParams = extend({}, this.defaultWmsParams);
// all keys that are not TileLayer options go to WMS params
for (var i in options) {
@ -72,9 +77,9 @@ L.TileLayer.WMS = L.TileLayer.extend({
}
}
options = L.setOptions(this, options);
options = setOptions(this, options);
wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
this.wmsParams = wmsParams;
},
@ -87,7 +92,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = this._crs.code;
L.TileLayer.prototype.onAdd.call(this, map);
TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function (coords) {
@ -96,14 +101,14 @@ L.TileLayer.WMS = L.TileLayer.extend({
nw = this._crs.project(tileBounds.getNorthWest()),
se = this._crs.project(tileBounds.getSouthEast()),
bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
[se.y, nw.x, nw.y, se.x] :
[nw.x, se.y, se.x, nw.y]).join(','),
url = L.TileLayer.prototype.getTileUrl.call(this, coords);
url = TileLayer.prototype.getTileUrl.call(this, coords);
return url +
L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
getParamString(this.wmsParams, url, this.options.uppercase) +
(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
},
@ -111,7 +116,7 @@ L.TileLayer.WMS = L.TileLayer.extend({
// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
setParams: function (params, noRedraw) {
L.extend(this.wmsParams, params);
extend(this.wmsParams, params);
if (!noRedraw) {
this.redraw();
@ -124,6 +129,6 @@ L.TileLayer.WMS = L.TileLayer.extend({
// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
L.tileLayer.wms = function (url, options) {
return new L.TileLayer.WMS(url, options);
};
export function tileLayerWMS(url, options) {
return new TileLayerWMS(url, options);
}

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

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
* @inherits Renderer
@ -30,7 +37,7 @@
* ```
*/
L.Canvas = L.Renderer.extend({
export var Canvas = Renderer.extend({
getEvents: function () {
var events = L.Renderer.prototype.getEvents.call(this);
events.viewprereset = this._onViewPreReset;
@ -43,7 +50,7 @@ L.Canvas = L.Renderer.extend({
},
onAdd: function () {
L.Renderer.prototype.onAdd.call(this);
Renderer.prototype.onAdd.call(this);
// Redraw vectors since canvas is cleared upon removal,
// in case of removing the renderer itself from the map.
@ -53,10 +60,9 @@ L.Canvas = L.Renderer.extend({
_initContainer: function () {
var container = this._container = document.createElement('canvas');
L.DomEvent
.on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
.on(container, 'mouseout', this._handleMouseOut, this);
DomEvent.on(container, 'mousemove', Util.throttle(this._onMouseMove, 32, this), this);
DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
DomEvent.on(container, 'mouseout', this._handleMouseOut, this);
this._ctx = container.getContext('2d');
},
@ -78,14 +84,14 @@ L.Canvas = L.Renderer.extend({
this._drawnLayers = {};
L.Renderer.prototype._update.call(this);
Renderer.prototype._update.call(this);
var b = this._bounds,
container = this._container,
size = b.getSize(),
m = L.Browser.retina ? 2 : 1;
m = Browser.retina ? 2 : 1;
L.DomUtil.setPosition(container, b.min);
DomUtil.setPosition(container, b.min);
// set canvas size (also clearing it); use double size on retina
container.width = m * size.x;
@ -93,7 +99,7 @@ L.Canvas = L.Renderer.extend({
container.style.width = size.x + 'px';
container.style.height = size.y + 'px';
if (L.Browser.retina) {
if (Browser.retina) {
this._ctx.scale(2, 2);
}
@ -115,7 +121,7 @@ L.Canvas = L.Renderer.extend({
_initPath: function (layer) {
this._updateDashArray(layer);
this._layers[L.stamp(layer)] = layer;
this._layers[Util.stamp(layer)] = layer;
var order = layer._order = {
layer: layer,
@ -186,12 +192,12 @@ L.Canvas = L.Renderer.extend({
if (!this._map) { return; }
this._extendRedrawBounds(layer);
this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
this._redrawRequest = this._redrawRequest || Util.requestAnimFrame(this._redraw, this);
},
_extendRedrawBounds: function (layer) {
var padding = (layer.options.weight || 0) + 1;
this._redrawBounds = this._redrawBounds || new L.Bounds();
this._redrawBounds = this._redrawBounds || new Bounds();
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
},
@ -335,7 +341,7 @@ L.Canvas = L.Renderer.extend({
}
}
if (clickedLayer) {
L.DomEvent._fakeStop(e);
DomEvent.fakeStop(e);
this._fireEvent([clickedLayer], e);
}
},
@ -352,7 +358,7 @@ L.Canvas = L.Renderer.extend({
var layer = this._hoveredLayer;
if (layer) {
// if we're leaving the layer, fire mouseout
L.DomUtil.removeClass(this._container, 'leaflet-interactive');
DomUtil.removeClass(this._container, 'leaflet-interactive');
this._fireEvent([layer], e, 'mouseout');
this._hoveredLayer = null;
}
@ -372,7 +378,7 @@ L.Canvas = L.Renderer.extend({
this._handleMouseOut(e);
if (candidateHoveredLayer) {
L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
this._fireEvent([candidateHoveredLayer], e, 'mouseover');
this._hoveredLayer = candidateHoveredLayer;
}
@ -444,64 +450,8 @@ L.Canvas = L.Renderer.extend({
}
});
// @namespace Browser; @property canvas: Boolean
// `true` when the browser supports [`<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)
// Creates a Canvas renderer with the given options.
L.canvas = function (options) {
return L.Browser.canvas ? new L.Canvas(options) : null;
};
L.Polyline.prototype._containsPoint = function (p, closed) {
var i, j, k, len, len2, part,
w = this._clickTolerance();
if (!this._pxBounds.contains(p)) { return false; }
// hit detection for polylines
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
if (!closed && (j === 0)) { continue; }
if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
return true;
}
}
}
return false;
};
L.Polygon.prototype._containsPoint = function (p) {
var inside = false,
part, p1, p2, i, j, k, len, len2;
if (!this._pxBounds.contains(p)) { return false; }
// ray casting algorithm for detecting if point is in polygon
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
p1 = part[j];
p2 = part[k];
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
inside = !inside;
}
}
}
// also check if it's on polygon stroke
return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
};
L.CircleMarker.prototype._containsPoint = function (p) {
return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
};
export function canvas(options) {
return Browser.canvas ? new Canvas(options) : null;
}

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

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

View File

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

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
* @aka L.Polygon
@ -44,7 +51,7 @@
* ```
*/
L.Polygon = L.Polyline.extend({
export var Polygon = Polyline.extend({
options: {
fill: true
@ -90,25 +97,25 @@ L.Polygon = L.Polyline.extend({
},
_convertLatLngs: function (latlngs) {
var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
len = result.length;
// remove last point if it equals first one
if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
result.pop();
}
return result;
},
_setLatLngs: function (latlngs) {
L.Polyline.prototype._setLatLngs.call(this, latlngs);
if (L.Polyline._flat(this._latlngs)) {
Polyline.prototype._setLatLngs.call(this, latlngs);
if (LineUtil._flat(this._latlngs)) {
this._latlngs = [this._latlngs];
}
},
_defaultShape: function () {
return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
return LineUtil._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
},
_clipPoints: function () {
@ -116,10 +123,10 @@ L.Polygon = L.Polyline.extend({
var bounds = this._renderer._bounds,
w = this.options.weight,
p = new L.Point(w, w);
p = new Point(w, w);
// increase clip padding by stroke width to avoid stroke on clip edges
bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
@ -132,7 +139,7 @@ L.Polygon = L.Polyline.extend({
}
for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
clipped = PolyUtil.clipPolygon(this._rings[i], bounds, true);
if (clipped.length) {
this._parts.push(clipped);
}
@ -141,11 +148,37 @@ L.Polygon = L.Polyline.extend({
_updatePath: function () {
this._renderer._updatePoly(this, true);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function (p) {
var inside = false,
part, p1, p2, i, j, k, len, len2;
if (!this._pxBounds.contains(p)) { return false; }
// ray casting algorithm for detecting if point is in polygon
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
p1 = part[j];
p2 = part[k];
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
inside = !inside;
}
}
}
// also check if it's on polygon stroke
return inside || Polyline.prototype._containsPoint.call(this, p, true);
}
});
// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
L.polygon = function (latlngs, options) {
return new L.Polygon(latlngs, options);
};
export function polygon(latlngs, options) {
return new Polygon(latlngs, options);
}

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

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

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
* @inherits Layer
@ -18,7 +26,7 @@
* its map has moved
*/
L.Renderer = L.Layer.extend({
export var Renderer = Layer.extend({
// @section
// @aka Renderer options
@ -30,8 +38,8 @@ L.Renderer = L.Layer.extend({
},
initialize: function (options) {
L.setOptions(this, options);
L.stamp(this);
Util.setOptions(this, options);
Util.stamp(this);
this._layers = this._layers || {};
},
@ -40,7 +48,7 @@ L.Renderer = L.Layer.extend({
this._initContainer(); // defined by renderer implementations
if (this._zoomAnimated) {
L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
DomUtil.addClass(this._container, 'leaflet-zoom-animated');
}
}
@ -50,7 +58,7 @@ L.Renderer = L.Layer.extend({
},
onRemove: function () {
L.DomUtil.remove(this._container);
DomUtil.remove(this._container);
this.off('update', this._updatePaths, this);
},
@ -77,7 +85,7 @@ L.Renderer = L.Layer.extend({
_updateTransform: function (center, zoom) {
var scale = this._map.getZoomScale(zoom, this._zoom),
position = L.DomUtil.getPosition(this._container),
position = DomUtil.getPosition(this._container),
viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
currentCenterPoint = this._map.project(this._center, zoom),
destCenterPoint = this._map.project(center, zoom),
@ -85,10 +93,10 @@ L.Renderer = L.Layer.extend({
topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
if (L.Browser.any3d) {
L.DomUtil.setTransform(this._container, topLeftOffset, scale);
if (Browser.any3d) {
DomUtil.setTransform(this._container, topLeftOffset, scale);
} else {
L.DomUtil.setPosition(this._container, topLeftOffset);
DomUtil.setPosition(this._container, topLeftOffset);
}
},
@ -120,48 +128,9 @@ L.Renderer = L.Layer.extend({
size = this._map.getSize(),
min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
}
});
L.Map.include({
// @namespace Map; @method getRenderer(layer: Path): Renderer
// Returns the instance of `Renderer` that should be used to render the given
// `Path`. It will ensure that the `renderer` options of the map and paths
// are respected, and that the renderers do exist on the map.
getRenderer: function (layer) {
// @namespace Path; @option renderer: Renderer
// Use this specific instance of `Renderer` for this path. Takes
// precedence over the map's [default renderer](#map-renderer).
var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
if (!renderer) {
// @namespace Map; @option preferCanvas: Boolean = false
// Whether `Path`s should be rendered on a `Canvas` renderer.
// By default, all `Path`s are rendered in a `SVG` renderer.
renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
}
if (!this.hasLayer(renderer)) {
this.addLayer(renderer);
}
return renderer;
},
_getPaneRenderer: function (name) {
if (name === 'overlayPane' || name === undefined) {
return false;
}
var renderer = this._paneRenderers[name];
if (renderer === undefined) {
renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
this._paneRenderers[name] = renderer;
}
return renderer;
}
});

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!
*/
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
*
@ -11,48 +30,31 @@
* with old versions of Internet Explorer.
*/
// @namespace Browser; @property vml: Boolean
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
L.Browser.vml = !L.Browser.svg && (function () {
try {
var div = document.createElement('div');
div.innerHTML = '<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 ? {} : {
// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
export var vmlMixin = {
_initContainer: function () {
this._container = L.DomUtil.create('div', 'leaflet-vml-container');
this._container = DomUtil.create('div', 'leaflet-vml-container');
},
_update: function () {
if (this._map._animatingZoom) { return; }
L.Renderer.prototype._update.call(this);
Renderer.prototype._update.call(this);
this.fire('update');
},
_initPath: function (layer) {
var container = layer._container = L.SVG.create('shape');
var container = layer._container = vmlCreate('shape');
L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
container.coordsize = '1 1';
layer._path = L.SVG.create('path');
layer._path = vmlCreate('path');
container.appendChild(layer._path);
this._updateStyle(layer);
this._layers[L.stamp(layer)] = layer;
this._layers[Util.stamp(layer)] = layer;
},
_addPath: function (layer) {
@ -66,9 +68,9 @@ L.SVG.include(!L.Browser.vml ? {} : {
_removePath: function (layer) {
var container = layer._container;
L.DomUtil.remove(container);
DomUtil.remove(container);
layer.removeInteractiveTarget(container);
delete this._layers[L.stamp(layer)];
delete this._layers[Util.stamp(layer)];
},
_updateStyle: function (layer) {
@ -82,7 +84,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
if (options.stroke) {
if (!stroke) {
stroke = layer._stroke = L.SVG.create('stroke');
stroke = layer._stroke = vmlCreate('stroke');
}
container.appendChild(stroke);
stroke.weight = options.weight + 'px';
@ -90,7 +92,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
stroke.opacity = options.opacity;
if (options.dashArray) {
stroke.dashStyle = L.Util.isArray(options.dashArray) ?
stroke.dashStyle = Util.isArray(options.dashArray) ?
options.dashArray.join(' ') :
options.dashArray.replace(/( *, *)/g, ' ');
} else {
@ -106,7 +108,7 @@ L.SVG.include(!L.Browser.vml ? {} : {
if (options.fill) {
if (!fill) {
fill = layer._fill = L.SVG.create('fill');
fill = layer._fill = vmlCreate('fill');
}
container.appendChild(fill);
fill.color = options.fillColor || options.color;
@ -132,25 +134,10 @@ L.SVG.include(!L.Browser.vml ? {} : {
},
_bringToFront: function (layer) {
L.DomUtil.toFront(layer._container);
DomUtil.toFront(layer._container);
},
_bringToBack: function (layer) {
L.DomUtil.toBack(layer._container);
DomUtil.toBack(layer._container);
}
});
if (L.Browser.vml) {
L.SVG.create = (function () {
try {
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
return function (name) {
return document.createElement('<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
* @inherits Renderer
@ -34,21 +43,21 @@
* ```
*/
L.SVG = L.Renderer.extend({
export var SVG = Renderer.extend({
getEvents: function () {
var events = L.Renderer.prototype.getEvents.call(this);
var events = Renderer.prototype.getEvents.call(this);
events.zoomstart = this._onZoomStart;
return events;
},
_initContainer: function () {
this._container = L.SVG.create('svg');
this._container = create('svg');
// makes it possible to click through svg root; we'll reset it back in individual paths
this._container.setAttribute('pointer-events', 'none');
this._rootGroup = L.SVG.create('g');
this._rootGroup = create('g');
this._container.appendChild(this._rootGroup);
},
@ -62,7 +71,7 @@ L.SVG = L.Renderer.extend({
_update: function () {
if (this._map._animatingZoom && this._bounds) { return; }
L.Renderer.prototype._update.call(this);
Renderer.prototype._update.call(this);
var b = this._bounds,
size = b.getSize(),
@ -76,7 +85,7 @@ L.SVG = L.Renderer.extend({
}
// movement: update container viewBox so that we don't have to change coordinates of individual layers
L.DomUtil.setPosition(container, b.min);
DomUtil.setPosition(container, b.min);
container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
this.fire('update');
@ -85,17 +94,17 @@ L.SVG = L.Renderer.extend({
// methods below are called by vector layers implementations
_initPath: function (layer) {
var path = layer._path = L.SVG.create('path');
var path = layer._path = create('path');
// @namespace Path
// @option className: String = null
// Custom class name set on an element. Only for SVG renderer.
if (layer.options.className) {
L.DomUtil.addClass(path, layer.options.className);
DomUtil.addClass(path, layer.options.className);
}
if (layer.options.interactive) {
L.DomUtil.addClass(path, 'leaflet-interactive');
DomUtil.addClass(path, 'leaflet-interactive');
}
this._updateStyle(layer);
@ -108,7 +117,7 @@ L.SVG = L.Renderer.extend({
},
_removePath: function (layer) {
L.DomUtil.remove(layer._path);
DomUtil.remove(layer._path);
layer.removeInteractiveTarget(layer._path);
delete this._layers[L.stamp(layer)];
},
@ -156,7 +165,7 @@ L.SVG = L.Renderer.extend({
},
_updatePoly: function (layer, closed) {
this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
this._setPath(layer, pointsToPath(layer._parts, closed));
},
_updateCircle: function (layer) {
@ -180,58 +189,20 @@ L.SVG = L.Renderer.extend({
// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
_bringToFront: function (layer) {
L.DomUtil.toFront(layer._path);
DomUtil.toFront(layer._path);
},
_bringToBack: function (layer) {
L.DomUtil.toBack(layer._path);
DomUtil.toBack(layer._path);
}
});
if (Browser.vml) {
SVG.include(vmlMixin);
}
// @namespace SVG; @section
// There are several static functions which can be called without instantiating L.SVG:
L.extend(L.SVG, {
// @function create(name: String): SVGElement
// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
// corresponding to the class name passed. For example, using 'line' will return
// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
create: function (name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
},
// @function pointsToPath(rings: Point[], closed: Boolean): String
// Generates a SVG path string for multiple rings, with each ring turning
// into "M..L..L.." instructions
pointsToPath: function (rings, closed) {
var str = '',
i, j, len, len2, points, p;
for (i = 0, len = rings.length; i < len; i++) {
points = rings[i];
for (j = 0, len2 = points.length; j < len2; j++) {
p = points[j];
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
// closes the ring for polygons; "x" is VML syntax
str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
}
// SVG complains about empty path strings
return str || 'M0 0';
}
});
// @namespace Browser; @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
L.svg = function (options) {
return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
};
export function svg(options) {
return Browser.svg || Browser.vml ? new SVG(options) : null;
}

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

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

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

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

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

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

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

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