diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml new file mode 100644 index 00000000000..ed81431b5de --- /dev/null +++ b/.github/workflows/npmpublish.yml @@ -0,0 +1,45 @@ +name: Node.js Package + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: npm ci + - run: npm test + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} + + publish-gpr: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://npm.pkg.github.com/ + scope: '@your-github-username' + - run: npm ci + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/CNAME b/CNAME new file mode 100644 index 00000000000..7dd1131fb8e --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +leafletjs.com \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ceaeaf6e2b3..e1870f53982 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,19 +4,20 @@ Contributing to Leaflet 1. [Getting Involved](#getting-involved) 2. [Reporting Bugs](#reporting-bugs) 3. [Contributing Code](#contributing-code) - 4. [Improving Documentation](#improving-documentation) - 5. [Code of Conduct](#code-of-conduct) + 4. [Running the Tests](#running-the-tests) + 6. [Improving Documentation](#improving-documentation) + 7. [Code of Conduct](#code-of-conduct) + 8. [Thank You](#thank-you) ## Getting Involved -Third-party patches are absolutely essential on our quest to create the best mapping library that will ever exist. +Third-party patches are absolutely essential in our quest to create the best mapping library that will ever exist. However, they're not the only way to get involved with Leaflet development. You can help the project tremendously by discovering and [reporting bugs](#reporting-bugs); [improving documentation](#improving-documentation); helping others on [Stack Overflow](https://stackoverflow.com/questions/tagged/leaflet), [GIS Stack Exchange](https://gis.stackexchange.com/questions/tagged/leaflet) and [GitHub issues](https://github.com/Leaflet/Leaflet/issues); -showing your support for your favorite feature suggestions on [Leaflet UserVoice page](http://leaflet.uservoice.com); tweeting to [@LeafletJS](http://twitter.com/LeafletJS); and spreading the word about Leaflet among your colleagues and friends. @@ -33,7 +34,7 @@ here are some tips for creating a helpful report that will make fixing it much e * Write a **descriptive, specific title**. Bad: *Problem with polylines*. Good: *Doing X in IE9 causes Z*. * Include **browser, OS and Leaflet version** info in the description. - * Create a **simple test case** that demonstrates the bug (e.g. using [Leaflet playground](http://playground-leaflet.rhcloud.com/)). + * Create a **simple test case** that demonstrates the bug (e.g. using [Leaflet plunker](http://leafletjs.com/edit.html)). * Check whether the bug can be reproduced in **other browsers**. * Check if the bug occurs in the stable version, master, or both. * *Bonus tip:* if the bug only appears in the master version but the stable version is fine, @@ -52,8 +53,7 @@ So bugfixes, performance optimizations and small improvements that don't add a l are much more likely to get accepted quickly. Before sending a pull request with a new feature, check if it's been discussed before already -(either on [GitHub issues](https://github.com/Leaflet/Leaflet/issues) -or [Leaflet UserVoice](http://leaflet.uservoice.com/)), +on [GitHub issues](https://github.com/Leaflet/Leaflet/issues) and ask yourself two questions: 1. Are you sure that this new feature is important enough to justify its presence in the Leaflet core? @@ -65,16 +65,17 @@ please consider submitting another pull request with the corresponding [document ### Setting up the Build System -The Leaflet build system uses [Node](http://nodejs.org/), and the [Jake](http://jakejs.com/) Javascript build tool. -To set up the Leaflet build system, install Node then run the following commands in the project root to install Jake: +The Leaflet build system uses [NodeJS](http://nodejs.org/). +To set up the Leaflet build system, install [NodeJS](https://nodejs.org/). +Then run the following commands in the project root to install dependencies: ``` -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 +``` ### Making Changes to Leaflet Source @@ -88,7 +89,7 @@ Please do not commit to the `master` branch, or your unrelated changes will go i You should also follow the code style and whitespace conventions of the original codebase. In particular, use tabs for indentation and spaces for alignment. -Before committing your changes, run `jake lint` to catch any JS errors in the code and fix them. +Before committing your changes, run `npm run lint` to catch any JS errors in the code and fix them. If you add any new files to the Leaflet source, make sure to also add them to `build/deps.js` so that the build system knows about them. @@ -96,53 +97,70 @@ Also, please make sure that you have [line endings configured properly](https:// Happy coding! -## Running the Tests +### Using RollupJS -To run the tests from the command line, -install [PhantomJS](http://phantomjs.org/) (and make sure it's in your `PATH`), -then run: +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. -``` -jake test -``` +In order to create this file, run `npm run rollup` or `yarn run rollup`. -To run all the tests in actual browsers at the same time, you can do: +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 watch` or `yarn run watch`. This will keep +on rebuilding the bundles whenever any source file changes. + +## Running the Tests + +To run the tests from the command line, first make sure you have installed all +the build system requirements with `npm install`. Then, simply run: ``` -jake test --ff --chrome --safari --ie +npm test ``` -To run the tests in a browser manually, open `spec/index.html`. - -## Code Coverage +By default, the tests will run in [PhantomJS](http://phantomjs.org/), and also +in [SlimerJS](https://slimerjs.org/) if possible. If `npm test` complains about +PhantomJS, install it manually and make sure it's in your `PATH`. -To generate a detailed report about test coverage (which helps tremendously when working on test improvements), run: +To run all the tests in actual browsers at the same time, you can do: ``` -jake test --cov +npm test -- -- --browsers Firefox,Chrome,Safari,IE ``` -After that, open `spec/coverage//index.html` in a browser to see the report. -From there you can click through folders/files to get details on their individual coverage. +(Note: the doubling of "`--`" [special option](https://docs.npmjs.com/cli/run-script#description) is [important](https://github.com/Leaflet/Leaflet/pull/6166#issuecomment-390959903)) + +To run the tests in a browser manually, open `spec/index.html`. ## Improving Documentation -The code of the live Leaflet website that contains all documentation and examples is located in the `gh-pages` branch +The code of the live Leaflet website that contains all documentation and examples is located in the `docs/` directory of the `master` branch and is automatically generated from a set of HTML and Markdown files by [Jekyll](http://jekyllrb.com/). The easiest way to make little improvements such as fixing typos without even leaving the browser is by editing one of the files with the online GitHub editor: -browse the [gh-pages branch](https://github.com/Leaflet/Leaflet/tree/gh-pages), -choose a certain file for editing (e.g. `plugins.html` for the list of Leaflet plugins), +browse the [`docs/ directory`](https://github.com/Leaflet/Leaflet/tree/master/docs), +choose a certain file for editing (e.g. `plugins.md` for the list of Leaflet plugins), click the Edit button, make changes and follow instructions from there. Once it gets merged, the changes will immediately appear on the website. If you need to make edits in a local repository to see how it looks in the process, do the following: - 1. [Install Ruby](http://www.ruby-lang.org/en/) if don't have it yet. + 1. [Install Ruby](http://www.ruby-lang.org/en/) if you don't have it yet. 2. Run `gem install jekyll`. - 3. Run `jekyll serve --watch` in the root `Leaflet` folder. - 4. Open `localhost:4000` in your browser. + 3. Enter the directory where you cloned the Leaflet repository + 4. Run `bundle install` + 5. Make sure you are in the `master` branch by running `git checkout master` + 6. Enter the documentation subdirectory by running `cd docs` + 7. Run `jekyll serve --watch`. + 8. Open `localhost:4000` in your web browser. Now any file changes will be updated when you reload pages automatically. After committing the changes, just send a pull request. @@ -155,17 +173,17 @@ 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 +npm run docs ``` and you'll find a `.html` file in the `dist/` directory. On every release of a new Leaflet version, this file will be generated and copied -over to the `gh-pages` branch - there is no need to send pull requests to this -branch anymore to update the API documentation. +over to `docs/reference.html` - there is no need to send pull requests with changes to this file to update the API documentation. ## Code of Conduct diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000000..97355ea723a --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'github-pages' diff --git a/LICENSE b/LICENSE index 9a246c9d3c8..13f6bcf2540 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2016, Vladimir Agafonkin +Copyright (c) 2010-2015, Vladimir Agafonkin Copyright (c) 2010-2011, CloudMade All rights reserved. diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000000..1d6eae1fedb --- /dev/null +++ b/_config.yml @@ -0,0 +1,6 @@ +source: docs + +markdown: kramdown + +kramdown: + entity_output: as_input diff --git a/bower.json b/bower.json index fb64aa5ac4b..0029c7150bc 100644 --- a/bower.json +++ b/bower.json @@ -1,11 +1,17 @@ { "name": "leaflet", + "version": "0.7.8", "description": "JavaScript library for mobile-friendly interactive maps", "main": [ "dist/leaflet.css", - "dist/leaflet-src.js" + "dist/leaflet-src.js", + "dist/images/layers-2x.png", + "dist/images/layers.png", + "dist/images/marker-icon-2x.png", + "dist/images/marker-icon.png", + "dist/images/marker-shadow.png" ], - "ignore": [ + "ignore": [ ".*", "CHANGELOG.json", "FAQ.md", diff --git a/build/bower.json b/build/bower.json new file mode 100644 index 00000000000..ca85bde2744 --- /dev/null +++ b/build/bower.json @@ -0,0 +1,22 @@ +{ + "name": "leaflet", + "description": "JavaScript library for mobile-friendly interactive maps", + "main": [ + "dist/leaflet.css", + "dist/leaflet-src.js", + "dist/images/layers-2x.png", + "dist/images/layers.png", + "dist/images/marker-icon-2x.png", + "dist/images/marker-icon.png", + "dist/images/marker-shadow.png" + ], + "ignore": [ + ".*", + "CHANGELOG.json", + "FAQ.md", + "debug", + "spec", + "src", + "build" + ] +} diff --git a/build/build.js b/build/build.js index b3fa0c4f9da..e9b286a29e7 100644 --- a/build/build.js +++ b/build/build.js @@ -1,7 +1,7 @@ var fs = require('fs'), + jshint = require('jshint'), UglifyJS = require('uglify-js'), zlib = require('zlib'), - SourceNode = require( 'source-map' ).SourceNode; deps = require('./deps.js').deps; @@ -70,67 +70,31 @@ function loadSilently(path) { } } -// 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) {')); - +function combineFiles(files) { + var content = ''; 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 + + + Leaflet debug page + + + + + + + + +
+ + + + + diff --git a/debug/vector/geojson-sample.js b/debug/vector/geojson-sample.js index 16f6f5749cd..811bfbd953e 100644 --- a/debug/vector/geojson-sample.js +++ b/debug/vector/geojson-sample.js @@ -12,7 +12,7 @@ var geojsonSample = { "color": "blue" } }, - + { "type": "Feature", "geometry": { @@ -24,7 +24,7 @@ var geojsonSample = { "prop1": 0.0 } }, - + { "type": "Feature", "geometry": { @@ -44,10 +44,10 @@ var geojsonSample = { "geometry": { "type": "MultiPolygon", "coordinates": [[[[100.0, 1.5], [100.5, 1.5], [100.5, 2.0], [100.0, 2.0], [100.0, 1.5]]], [[[100.5, 2.0], [100.5, 2.5], [101.0, 2.5], [101.0, 2.0], [100.5, 2.0]]]] - }, - "properties": { - "color": "purple" + }, + "properties": { + "color": "#ff7800" } } ] -}; +}; \ No newline at end of file diff --git a/dist/leaflet-src.js b/dist/leaflet-src.js new file mode 100644 index 00000000000..017fa0e4cc4 --- /dev/null +++ b/dist/leaflet-src.js @@ -0,0 +1,640 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/dist/leaflet.js b/dist/leaflet.js new file mode 100644 index 00000000000..017fa0e4cc4 --- /dev/null +++ b/dist/leaflet.js @@ -0,0 +1,640 @@ +/* required styles */ + +.leaflet-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-container, +.leaflet-pane > svg, +.leaflet-pane > canvas, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +/* Prevents IE11 from highlighting tiles in blue */ +.leaflet-tile::selection { + background: transparent; +} +/* Safari renders non-retina tile on retina better with this, but Chrome is worse */ +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast; + } +/* hack that prevents hw layers "stretching" when loading new tiles */ +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */ +/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container .leaflet-overlay-pane svg, +.leaflet-container .leaflet-marker-pane img, +.leaflet-container .leaflet-shadow-pane img, +.leaflet-container .leaflet-tile-pane img, +.leaflet-container img.leaflet-image-layer, +.leaflet-container .leaflet-tile { + max-width: none !important; + max-height: none !important; + } + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y; + } +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + /* Fallback for FF which doesn't support pinch-zoom */ + touch-action: none; + touch-action: pinch-zoom; +} +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none; +} +.leaflet-container { + -webkit-tap-highlight-color: transparent; +} +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4); +} +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-pane { z-index: 400; } + +.leaflet-tile-pane { z-index: 200; } +.leaflet-overlay-pane { z-index: 400; } +.leaflet-shadow-pane { z-index: 500; } +.leaflet-marker-pane { z-index: 600; } +.leaflet-tooltip-pane { z-index: 650; } +.leaflet-popup-pane { z-index: 700; } + +.leaflet-map-pane canvas { z-index: 100; } +.leaflet-map-pane svg { z-index: 200; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity; + } +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform; + } +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-interactive { + cursor: pointer; + } +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } +.leaflet-crosshair, +.leaflet-crosshair .leaflet-interactive { + cursor: crosshair; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-grab, +.leaflet-dragging .leaflet-grab .leaflet-interactive, +.leaflet-dragging .leaflet-marker-draggable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing; + } + +/* marker & overlays interactivity */ +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-image-layer, +.leaflet-pane > svg path, +.leaflet-tile-container { + pointer-events: none; + } + +.leaflet-marker-icon.leaflet-interactive, +.leaflet-image-layer.leaflet-interactive, +.leaflet-pane > svg path.leaflet-interactive, +svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */ + pointer-events: auto; + } + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + +/* Default icon URLs */ +.leaflet-default-icon-path { + background-image: url(images/marker-icon.png); + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: border-box; + box-sizing: border-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + color: #333; + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + -ms-zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } + + +/* Tooltip */ +/* Base styles for the element that has a tooltip */ +.leaflet-tooltip { + position: absolute; + padding: 6px; + background-color: #fff; + border: 1px solid #fff; + border-radius: 3px; + color: #222; + white-space: nowrap; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + box-shadow: 0 1px 3px rgba(0,0,0,0.4); + } +.leaflet-tooltip.leaflet-clickable { + cursor: pointer; + pointer-events: auto; + } +.leaflet-tooltip-top:before, +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + position: absolute; + pointer-events: none; + border: 6px solid transparent; + background: transparent; + content: ""; + } + +/* Directions */ + +.leaflet-tooltip-bottom { + margin-top: 6px; +} +.leaflet-tooltip-top { + margin-top: -6px; +} +.leaflet-tooltip-bottom:before, +.leaflet-tooltip-top:before { + left: 50%; + margin-left: -6px; + } +.leaflet-tooltip-top:before { + bottom: 0; + margin-bottom: -12px; + border-top-color: #fff; + } +.leaflet-tooltip-bottom:before { + top: 0; + margin-top: -12px; + margin-left: -6px; + border-bottom-color: #fff; + } +.leaflet-tooltip-left { + margin-left: -6px; +} +.leaflet-tooltip-right { + margin-left: 6px; +} +.leaflet-tooltip-left:before, +.leaflet-tooltip-right:before { + top: 50%; + margin-top: -6px; + } +.leaflet-tooltip-left:before { + right: 0; + margin-right: -12px; + border-left-color: #fff; + } +.leaflet-tooltip-right:before { + left: 0; + margin-left: -12px; + border-right-color: #fff; + } diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 00000000000..f0dd55c8c54 --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,206 @@ + + + + {% capture title %}{% if page.title %}{{ page.title }} - {% elsif post.title %}{{ post.title }} - {% endif %}{% endcapture %} + + {{ title }}Leaflet - a JavaScript library for mobile-friendly maps + + + + {% if title == '' %} + + + + + + + + {% endif %} + + + + {% capture root %}{% if page.root %}{{ page.root }}{% else %}{{ post.root }}{% endif %}{% endcapture %} + + + + + + + + + + + + + + + + + + + {% if page.css %}{% endif %} + + + + +
+

Leaflet

+

An Open-Source JavaScript Library for Mobile-Friendly Interactive Maps

+ + + + + + {{ content }} + + +
+ + + + + + + + diff --git a/docs/_layouts/post.html b/docs/_layouts/post.html new file mode 100644 index 00000000000..30a2c8c03e4 --- /dev/null +++ b/docs/_layouts/post.html @@ -0,0 +1,29 @@ +--- +layout: default +root: ../../../ +post: true +--- + +

← Back to the list of blog posts

+ +

{{ page.title }}

+ + + +{{ content }} + +
+ + + +comments powered by Disqus + diff --git a/docs/_layouts/tutorial.html b/docs/_layouts/tutorial.html new file mode 100644 index 00000000000..4471a1a2d9f --- /dev/null +++ b/docs/_layouts/tutorial.html @@ -0,0 +1,9 @@ +--- +layout: default +root: ../ +tutorial: true +--- + +

← Back to the list of tutorials

+ +{{ content }} diff --git a/docs/_posts/2012-07-30-leaflet-0-4-released.md b/docs/_posts/2012-07-30-leaflet-0-4-released.md new file mode 100644 index 00000000000..18ab66e1d7d --- /dev/null +++ b/docs/_posts/2012-07-30-leaflet-0-4-released.md @@ -0,0 +1,240 @@ +--- +layout: post +title: Leaflet 0.4 Released +description: After 5.5 months of development with 33 contributors involved, I'm proud to announce the release of Leaflet 0.4! It comes with a simpler API and lots of great improvements, along with a major update to documentation, a plugins page and the launch of the developer blog. +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +After 5.5 months of development with [33 contributors](https://github.com/Leaflet/Leaflet/graphs/contributors?from=2012-02-15&to=2012-07-30&type=c) involved since the previous stable release, I'm proud to announce the release of Leaflet 0.4! It comes with a simpler API and *lots* of great improvements and important bugfixes, along with a major update to documentation, an official plugins page and the launch of this developer blog. Lets take a look at the improvements one by one. + +### Simpler API + +Leaflet 0.4 contains several API improvements that allow you to write simpler, terser code ([jQuery](http://jquery.com)-like), while being backwards compatible with the previous approach (so that you can use both styles). + + L.marker([51.5, -0.09]) + .addTo(map) + .bindPopup('Hello world!') + .openPopup(); + +First, Leaflet methods now accept [LatLng][], [LatLngBounds][], [Point][] and [Bounds][] objects in a simple array form, so you don't need to always create them explicitly: + + map.panTo([50, 30]); // the same as: + map.panTo(new L.LatLng(50, 30)); + +Second, Map methods like [addLayer][], [addControl][], [openPopup][] got their counterparts from the other side: + + marker.addTo(map); // same as map.addLayer(marker) + control.addTo(map); // map.addControl(control) + popup.openOn(map); // map.openPopup(popup) + +Along with the fact that all Leaflet methods that don't explicitly return a value return the object itself, this allows for convenient method chaining. + +Third, Leaflet classes now come with lowercase shortcuts (class factories) that allow you to create objects without the new keyword, which makes chained code look nicer: + + L.map('map').fitWorld(); // same as + (new L.Map('map')).fitWorld(); + +### Notable New Features + +
+ +#### Improved Zoom Animation + +Markers, popups, vector layers and image overlays were hidden during zoom in the previous version, but now (thanks to [Dave Leaver][]) they all have beautiful, smooth zoom animation unlike any other existing mapping libraries. Try zooming on the map above to see how it looks! If you have thousands of markers on a map though, you can turn off the marker animation if it gets slow with the Map's `markerZoomAnimation` option. + +In addition, now tiles won't disappear if you zoom in or out more than once quickly. + +#### Keyboard Navigation + +Leaflet maps got a nice accessibility boost in 0.4 with the new keyboard handler (contributed by [Eric Martinez](https://github.com/ericmmartinez)), enabled by default. It allows users to navigate the map by using arrow keys for panning and +/- keys for zooming (after making the map focused either by tabbing to it or clicking on it). Try it on the map above, it feels very nice! + +#### Panning Inertia + +Another nice improvement comes to the panning experience --- now it has an inertial movement effect, where the map smoothly continues to move after a quick pan. Feels especially natural on touch devices --- and it's enabled by default too, try it now! It's also highly configurable, allowing you to set the maximum speed of the effect, decceleration, and time threshold under which it triggers. + +#### Pinch-Zoom on Android 4 + +In the previous Leaflet version, pinch-zoom only worked on iOS devices, but now it finally comes to Android! Works for Android 4+ not only in the stock browser, but also on Chrome and Firefox for Android. + +#### Scale Control + +A simple, lightweight control that indicates the scale of the current map view in metric and/or imperial systems. As usual, you can customize its appearance with CSS. Take a look at the bottom left corner of the map above! + + L.control.scale().addTo(map); + +#### Polyline and Polygon Editing + +Allows users to edit polylines and polygons with a simple, intuitive interface. Note that this feature will eventually be merged into [Leaflet.draw][] --- an awesome plugin for drawing shapes by Jacob Toye. + + polygon.editing.enable(); + +#### Div-based Icons + +In addition to the image-based [Icon][] class, Leaflet 0.4 gets a [DivIcon][] class for creating lightweight div-based markers (that can contain custom HTML and can be styled with CSS). For example, you can see them in action when editing polylines (the square handles), or in the [Leaflet.markercluster][] plugin I'll talk about later (the colored clusters). + + L.marker([50.505, 30.57], { + icon: L.divIcon({className: 'my-div-icon'}) + }).addTo(map); + +#### Rectangle Layer + +Rectangle is a convenient shortcut for creating rectangular area layers. You could do this earlier with polygons, but this is easier: + + L.rectangle([[51.505, -0.03], [51.5, -0.045]]).addTo(map); + +### API improvements + +#### GeoJSON API + +[GeoJSON][] API was improved to be simpler and much more flexible. [Jason Sanford][] wrote a [great tutorial](../../../examples/geojson.html) that showcases the new API. The changes are not backwards-compatible though, so be sure to update your old code. + +#### Icon API + +[Icon][] API was improved to be simpler and more flexible, and the changes are not backwards-compatible too (the old code can be updated very quickly though). Check out the updated [Custom Icons tutorial](../../../examples/custom-icons.html), or head straigt to the [API docs](../../../reference.html#icon). + +#### Control API + +Custom Controls are much easier to create now --- checkout the [API docs](../../../reference.html#icontrol) that also have a simple example. + +#### Better Events API + +[Aaron King][] brough some improvements to [event methods](../../../reference.html#events). `on` and `off` methods can now accept multiple event types at once as a string space-separated types: + + map.on('click dblclick moveend', doStuff); + +Also, they can accept an object with types and listener functions as key/value pairs, like this: + + marker.on({ + click: onMarkerClick, + dragend: onMarkerDragEnd + }); + +Moreover, now if you only specify an event type to the `off` method, it will remove all listeners tied to this event. + + map.off('click'); + +#### Other API Improvements + +Leaflet 0.4 features more than 30 new methods, options and events across different Leaflet classes that make the API more complete and powerful. Check out the [full changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md#other-api-improvements) for the complete list. + +### Performance and Usability Improvements + +You may think that Leaflet is unbelievably fast already, but this version brings several performance improvements that make it even faster. + + * Panning, map resizing and pinch-zoom performance was improved (some tricks behind this will be explained in a future blog post). + * Updating and removing vector layers on the canvas backend (e.g. on Android 2) works many times faster. + * Box shadows on controls were replaced with simple borders on mobile devices to improve performance. + * Vector layers won't flicker after each panning on iOS now. + +In addition, there are several usability improvents not already mentioned: + + * Panning now works even if there are markers under the cursor (helps on crowded maps). + * Popup appearance is slightly improved. + * Tile layer now has detectRetina option that, when enabled, doubles the tile resolution for retina displays (contributed by [Mithgol][]) . + +### Bugfixes + +Leaflet 0.4 brings around 45 bugfixes that make it more stable and reliable across all browsers and platforms. Notable bugfixes include the dreaded iOS bug that caused the map to completely disappear after pinch-zooming in some rare cases, broken zooming on IE10 beta, broken Leaflet maps on pages served with an XHTML content type, and incorrect zooming on maps inside a fixed-position element. + +Here's [a full list of bugfixes](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md#bug-fixes) in the changelog. + +### Upgrading from older versions + +Besides the GeoJSON and Icon changes mentioned above, here's a [list of potentially breaking changes](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md#other-breaking-api-changes) --- read it carefully when updating your code (shouldn't take much time though). + +Download options for Leaflet 0.4 (including the actual download, the CDN-hosted version, and intructions for building manually) are listed on the [download page](../../../download.html). + +### Code Stats + +I'm still commited to keeping Leaflet as small and lightweight as possible. Here's a breakdown of the current size of the library: + + * JavaScript: **27 KB** minified and gzipped (102 KB minified, 176 KB in source, 7578 lines of code) + * CSS: **1.8 KB** gzipped (8 KB, 377 lines of code) + * Images: **10 KB** (5 PNG images) + +### Documentation Update + +Until now, Leaflet API reference was incomplete. But for this release, enourmous effort was put into making it 100% complete, up-to-date and generally the best API reference page you've ever seen. All remaining classes, methods, options, events and properties were carefully documented and more code examples added, and the docs will always be kept up-to-date from now on. + +Besides, the design of the page was significantly improved --- with better colors, font, spacing, hyphenation, manually adjusted column widths, etc. --- lots of detail to make it beautiful and easy to read. + +### Plugins Page + +Leaflet website now has an official [plugins page](../../../plugins.html) that lists many Leaflet plugins created by the awesome Leaflet community, adding lots of great features and helping with service integration. + +One plugin I'd like to mention is [Leaflet.markercluster][] by [Dave Leaver], currently the best marker clustering plugin I've ever seen among any mapping libraries --- it's fast, beautiful, provides smooth animations for clusters, includes a smart Google Earth-style solution for crowded markers on the last zoom level (by [George MacKerron][]), can highlight the area covered by a cluster on hover, works well on mobile devices, and can be customized easily. I think we'll cover this plugin in more detail in one of the next posts. + +Another plugin to note is [Leaflet.draw][] by [Jacob Toye][], inspired by a similar plugin by [Bruno B](https://github.com/brunob). It enables drawing features like polylines, polygons, rectangles, circles and markers through a very nice user-friendly interface with icons and hints. Other editing-related code will probably move into this plugin in future. + +Also, thanks to [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin by [Kartena](http://www.kartena.se/), GIS enthusiasts can now enjoy Leaflet for maps with some quirky and rare projections. + +One more Leaflet-based creation everyone needs to check out is [OSM Buildings](http://flyjs.com/buildings/) by [Jan Marsch](http://flyjs.com/buildings/about.php), an amazing JS library for visualizing 3D OSM building data on top of Leaflet maps. Incredibly cool stuff. + +### Developer Blog + +This is the first post of the official Leaflet developer blog, that will become the main place for all important Leaflet-related news, tutorials, tips and development notes. + +### Big Players Using Leaflet + +Since the previous release, Leaflet got adopted by many great companies, including [Flickr](http://flickr.com/map), [foursquare](http://foursquare.com) and [Wikimedia Foundation](http://blog.wikimedia.org/2012/04/05/new-wikipedia-app-for-ios-and-an-update-for-our-android-app/) (featured on [frontpage](../../../index.html) now). This is a really exciting time for Leaflet and open source maps, and I look forward to see many other companies follow this awesome trend in future. + +### Thank You + +I'd like to thank all the awesome people that helped Leaflet becoming what it is now --- contributed code, reported bugs, used Leaflet on their websites, told collegues about it, talked about it on conferences, etc. Keep up the great work! + +Special thanks go to [Dave Leaver][] for his inspiring contributions including improved zoom animation and the state-of-the-art clustering plugin, and [Jason Sanford][] for his friendly support (and setting up the Leaflet CDN among other things). + +And, of course, thanks to my amazing company, [CloudMade](http://cloudmade.com), for embracing open source and supporting this development. + +Sincerely,
+Vladimir Agafonkin, Leaflet maintainer. + + [LatLng]: ../../../reference.html#latlng + [LatLngBounds]: ../../../reference.html#latlngbounds + [Point]: ../../../reference.html#point + [Bounds]: ../../../reference.html#bounds + [Icon]: ../../../reference.html#icon + [DivIcon]: ../../../reference.html#divicon + [GeoJSON]: ../../../reference.html#geojson + + [addControl]: ../../../reference.html#map-addcontrol + [addLayer]: ../../../reference.html#map-addlayer + [openPopup]: ../../../reference.html#map-openpopup + + [Leaflet.draw]: https://github.com/jacobtoye/Leaflet.draw + [Leaflet.markercluster]: https://github.com/danzel/Leaflet.markercluster + + [Dave Leaver]: https://github.com/danzel + [Jason Sanford]: https://github.com/JasonSanford + [Aaron King]: https://github.com/Guiswa + [Mithgol]: https://github.com/Mithgol + [George MacKerron]: https://github.com/jawj/ + [Jacob Toye]: https://github.com/jacobtoye + + diff --git a/docs/_posts/2012-08-07-leaflet-0-4-3-and-a-new-tutorial.md b/docs/_posts/2012-08-07-leaflet-0-4-3-and-a-new-tutorial.md new file mode 100644 index 00000000000..2a227154791 --- /dev/null +++ b/docs/_posts/2012-08-07-leaflet-0-4-3-and-a-new-tutorial.md @@ -0,0 +1,47 @@ +--- +layout: post +title: Leaflet 0.4.3 and a New Tutorial +description: Leaflet 0.4.3 released with several bugfixes and improvements, and comes with a new tutorial on creating a colorful interactive choropleth map. +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +Following the [Leaflet 0.4 release](leaflet-0-4-released.html), there were several minor bugfix releases over the past week, with Leaflet 0.4.3 released today. They contain fixes for some bugs that were discovered and also bring some improvements to the new GeoJSON API to make it even more flexible --- see the changelog below. + +I've also written [a new tutorial](../../../examples/choropleth.html), inspired by the [Texas Tribune US Senate Runoff Results map](http://www.texastribune.org/library/data/us-senate-runoff-results-map/) by [Ryan Murphy](http://www.texastribune.org/about/staff/ryan-murphy/) (also powered by Leaflet). It will show you step-by-step how to create a beautiful interactive [choropleth map](http://en.wikipedia.org/wiki/Choropleth_map) of US States Population Density with the help of GeoJSON and custom controls, and hopefully convince more major news and government websites to switch to Leaflet. :) + +Grab the new Leaflet 0.4.3 at the [download page](../../../download.html). Enjoy! + +**update**: IE9 regression was discovered in 0.4.3, so I had to release 0.4.4 with a fix. Sorry! + +### 0.4.3 (August 7, 2012) + +#### Improvements + + * Improved `GeoJSON` `setStyle` to also accept function (like the corresponding option). + * Added `GeoJSON` `resetStyle(layer)`, useful for resetting hover state. + * Added `feature` property to layers created with `GeoJSON` (containing the GeoJSON feature data). + * Added `FeatureGroup` `bringToFront` and `bringToBack` methods (so that they would work for multipolys). + * Added optional `animate` argument to `Map` `invalidateSize` (by [@ajbeaven](https://github.com/ajbeaven)). [#857](https://github.com/Leaflet/Leaflet/pull/857) + +#### Bugfixes + + * Fixed a bug where tiles sometimes disappeared on initial map load on Android 2/3 (by [@danzel](https://github.com/danzel)). [#868](https://github.com/Leaflet/Leaflet/pull/868) + * Fixed a bug where map would occasionally flicker near the border on zoom or pan on Chrome. + * Fixed a bug where `Path` `bringToFront` and `bringToBack` didn't return `this`. + * Removed zoom out on Win/Meta key binding (since it interferes with global keyboard shortcuts). [#869](https://github.com/Leaflet/Leaflet/issues/869) + +### 0.4.2 (August 1, 2012) + + * Fixed a bug where layers control radio buttons would not work correctly in IE7 (by [@danzel](https://github.com/danzel)). [#862](https://github.com/Leaflet/Leaflet/pull/862) + * Fixed a bug where `FeatureGroup` `removeLayer` would unbind popups of removed layers even if the popups were not put by the group (affected [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) plugin) (by [@danzel](https://github.com/danzel)). [#861](https://github.com/Leaflet/Leaflet/pull/861) + +### 0.4.1 (July 31, 2012) + + * Fixed a bug that caused marker shadows appear as opaque black in IE6-8. [#850](https://github.com/Leaflet/Leaflet/issues/850) + * Fixed a bug with incorrect calculation of scale by the scale control. [#852](https://github.com/Leaflet/Leaflet/issues/852) + * Fixed broken L.tileLayer.wms class factory (by [@mattcurrie](https://github.com/mattcurrie)). [#856](https://github.com/Leaflet/Leaflet/issues/856) + * Improved retina detection for `TileLayer` `detectRetina` option (by [@sxua](https://github.com/sxua)). [#854](https://github.com/Leaflet/Leaflet/issues/854) + +Sincerely,
+Vladimir Agafonkin, Leaflet maintainer. diff --git a/docs/_posts/2012-08-20-guest-post-markerclusterer-0-1-released.md b/docs/_posts/2012-08-20-guest-post-markerclusterer-0-1-released.md new file mode 100644 index 00000000000..fc1e9c046bf --- /dev/null +++ b/docs/_posts/2012-08-20-guest-post-markerclusterer-0-1-released.md @@ -0,0 +1,112 @@ +--- +layout: post +title: Leaflet.MarkerCluster 0.1 Released +description: Introducing Leaflet.MarkerCluster, a beautiful, fast, customizable plugin to reduce the visual clutter on crowded maps. +author: Dave Leaver +authorsite: https://github.com/danzel/ +--- + +_This is a guest post from Dave Leaver, an active Leaflet contributor (particularly, he implemented 0.4 zoom animation improvements) and also the author of the best marker clustering plugin out there, which is presented in this post._ + +Almost anyone who has a map with markers on it will eventually end up having those markers overlap. At my day job at Smartrak we regularly have customers with thousands of points on the map. When you zoom it out, these markers all overlap and make the map look messy and crowded. There are also cases where the markers overlap even on the maximum zoom level, which makes interacting with them impossible. Also, having a large amount of markers on the map usually ends up lowering performance to an unacceptable level. + +To improve this, many sites use marker clustering, a technique of grouping markers that are close to each other together on each zom level. One good example of this is Redfin. We needed something like this, but in Leaflet. In the spirit of open source we developed and released our solution so that everyone can take advantage of it. So we proudly present Leaflet.MarkerCluster. + +
+ +{:#plugin-features} +### Features + +The clusterer has all sorts of great built in behaviour: + + * Everything is brilliantly animated. As you zoom in and out you can logically see which clusters have become which markers. + * It is very fast, so for example [clustering 50,000 points](http://danzel.github.com/Leaflet.markercluster/example/marker-clustering-realworld.50000.html) isn't a problem. Also, all the heavy calculation happens on initial page load, and after this the map works smoothly. + * Markers that don't need clustering aren't and will be visible at the relevant zoom levels. + * When you mouse over a cluster the bounds of the marker within that cluster are shown. + * Clicking a cluster will zoom you in to the bounds of its children. + * At the bottom zoom level if there are still clusters you can click on them to "spiderfy" them, which makes interaction with individual markers within the cluster possible (based on jawj's Overlapping MarkerSpidifer). + * Cluster and markers that are further than a screen width from the view port are removed from the map to increase performance. + * As with core Leaflet, everything works on both mobile and desktop browsers and is tested all the way back to IE6. + * Supports adding and removing markers after being added to the map (see Best Practices below!). + * It is highly customizable, allowing you to easily change the appearance of clusters, disable certain features and add custom behavior on cluster interaction. + +### Usage + +Using the Marker Clusterer is easy, just replace your existing [LayerGroup](../../../examples/layers-control.html) usage with an `L.MarkerClusterGroup`: + + var markers = new L.MarkerClusterGroup(); + + markers.addLayer(L.marker([175.3107, -37.7784])); + // add more markers here... + + map.addLayer(markers); + +You can also use all of the [FeatureGroup events](../../../reference.html#featuregroup) (and additionally `clusterclick`) for both individual markers and clusters. + + markers.on('clusterclick', function (a) { alert('Cluster Clicked'); }); + markers.on('click', function (a) { alert('Marker Clicked'); }); + +### Best Practices + + * To get the best performance from the clusterer, you should add all of your markers to it before adding it to the map (like we did in the example). + * If you are going to move a marker that is in a L.MarkerClusterGroup you must remove it first, then move it, then re-add it. If you move it while it is in the MarkerClusterGroup we can't track it and that marker will become lost. + * Although the clusterer supports having markers added and removed from it while it is on the map it does not perform as well as when they are added while it is not on the map. If you need to do a large update to the markers in a `MarkerClusterGroup` you may want to remove it from the map, change the markers then re-add it. + +### Get It + +You can download the latest release on the github download page. + +### The Technical Bits + +The underlying clustering algorithm (`MarkerClusterGroup._cluster`) is plain greedy clustering. + +{: .no-highlight} + foreach marker + if there is a cluster within the clustering distance, join it. + else if there is an unclustered marker within the clustering distance, form a cluster with it. + +The first clustering step we do for the maximum (bottom most) zoom level, we then cluster all of the resulting markers and clusters to generate the next zoom level up and so on until we have reached the top. +These clusters are stored in a tree (A cluster contains its child clusters) with good geospatial qualities. We use this tree to optimise identifying what markers and clusters are on screen at any particular zoom level. + +#### L.DistanceGrid + +`L.DistanceGrid` provides some nice optimization when clustering (contributed by [Vladimir](http://agafonkin.com/en/), Leaflet maintainer). + +To cluster the markers, we need to compare every marker with every other marker to try form a cluster. +To make this quicker, we need reduce the set of markers we need to compare with. `DistanceGrid` does this by putting all markers on a grid sized the same as the distance we need to search. Then, when looking for a marker to cluster with, we only need to look at markers in the grid square we are in and its immediate neighbours. This can be quite a big performance win as we only look at markers that we are likely to form a cluster with. (check out the initial PR for numbers) + +### Closing Words + +I hope you enjoy using the clusterer and get everything you want out of it. If you do use it in a public site please throw me an email so I can check it out and potentially link it on the github site. + +If you have any issues also please log a bug on the github page. + +Enjoy!
+Dave Leaver. + + + + + + + + diff --git a/docs/_posts/2012-10-25-leaflet-0-4-5-bugfix-release-and-plans-for-0.5.md b/docs/_posts/2012-10-25-leaflet-0-4-5-bugfix-release-and-plans-for-0.5.md new file mode 100644 index 00000000000..d5ea237ed50 --- /dev/null +++ b/docs/_posts/2012-10-25-leaflet-0-4-5-bugfix-release-and-plans-for-0.5.md @@ -0,0 +1,36 @@ +--- +layout: post +title: Leaflet 0.4.5 Bugfix Release and Plans for 0.5 +description: Leaflet 0.4.5 released, containing a small but important zoom animation bugfix for upcoming Chrome 23+ (currently beta) and IE10. Work on future 0.5 release goes on! +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +### 0.4.5 release + +While we contrinue working on the next major release (0.5), today we decided to release **Leaflet 0.4.5**. It contains only one small but important bugfix for **wonky zoom animation** on upcoming **Chrome 23** (currently in beta and to be released in a couple of weeks) and **Internet Explorer 10** (that will eventually hit Windows 7 in addition to Windows 8). + +Everyone is encouraged to upgrade (before Chrome 23 turns stable). As always, you can find CDN links and downloads for the new release on the [download page](../../../download.html). + +### Plans for 0.5 + +As Leaflet approaches feature-complete state and API stabilization, we naturally shift our focus from new features towards performance and usability improvements, better browser and device support, bugfixes and internal refactoring to make certain parts of Leaflet (like projections and vector rendering) easier to extend and customize for plugin developers and advanced users. + +Highlights of things already implemented in the `master` branch include touch interaction support for **IE10 touch devices and Metro apps** and a more smooth and responsive panning inertia. Follow the [full changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md) for more details. + +We're also in the process of a major refactoring of vector rendering code to allow much simpler extension of base functionality with custom shapes, additional rendering systems (like WebGL in addition to existing SVG/VML and Canvas renderers), easy switching between renderers, also making the code simpler and easier to understand. + +The same goes for projection-related code to make using Leaflet with non-standard projections easier, inluding plain projections for game and indoor maps. Thanks to these changes, in addition to making advanced GIS folks happier, we'll see much more awesome Leaflet projects like [interactive Skyrim map on IGN](http://www.ign.com/wikis/the-elder-scrolls-5-skyrim/interactive-maps/Skyrim) or [World of Warcraft map on Wowhead](http://www.wowhead.com/map). + +Another important task for upcoming weeks is working more closely with plugin developers. In particular, one of the areas of focus will be the [Leaflet.draw](https://github.com/jacobtoye/Leaflet.draw) plugin that will soon become a state-of-the-art map vector drawing/editing solution, just as Dave's [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) became the best marker clustering solution among all mapping platforms out there. + +The current plan is to release 0.5 stable sometime in mid-November. Stay tuned! + +### Contributing to Leaflet + +Leaflet is a true open source project, so we're always happy to meet new contributors, accept patches and bugreports. To help others become involved with Leaflet development and make managing contributions easier, I've put up a [Contributing to Leaflet](https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md) guide with best practices and advices — check it out! + +Thanks to everyone! Leaflet has got quite an amazing community which makes me really proud. Keep it up! + +Cheers,
+Vladimir, Leaflet author and maintainer. diff --git a/docs/_posts/2013-01-17-leaflet-0-5-released.md b/docs/_posts/2013-01-17-leaflet-0-5-released.md new file mode 100644 index 00000000000..c9630db2915 --- /dev/null +++ b/docs/_posts/2013-01-17-leaflet-0-5-released.md @@ -0,0 +1,24 @@ +--- +layout: post +title: Leaflet 0.5 Released +description: Leaflet 0.5 released — with IE10 touch support, retina-enabled markers, better panning inertia, new zoom control and about a hundred of other improvements and bugfixes! +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +Rejoice, everyone — after 4.5 months of development with [26 contributors involved](https://github.com/Leaflet/Leaflet/graphs/contributors?from=2012-08-30&to=2013-01-17&type=c) since the previous major release, I'm happy to announce the release of Leaflet 0.5 stable, hooray! + +0.5 highlights include IE10 touch devices and Metro apps support, retina-enabled markers, a much better panning inertia implementation, hand cursors for dragging and a new zoom control design. But the real power of this release comes with about a hundred of subtle improvements and bugfixes, improving usability, performance and overall "feel" of browsing the map even further. + +As always, you can find CDN links and downloads for the new release on the [download page](../../../download.html). + +The huge detailed list of changes is documented in the [changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md). Be sure to read the "Breaking Changes" part of it before upgrading to avoid any issues! The [API reference](../../../reference.html) was updated to accomodate all the changes too. + +In other news, [Leaflet repository](https://github.com/Leaflet/Leaflet) has moved to [its own GitHub organization](https://github.com/Leaflet), along with the two of the most important plugins — [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster) and [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw). As some of you have noticed, this is one of the clues to a really nice upcoming announcement about Leaflet future — stay tuned. :) + +Thanks to everyone! It's absolutely breathtaking to see what the Leaflet community has achieved over the last months with all the contributions, amazing projects and demos, and I'm honestly proud to be a part of it. + +P.S. I also heard in a dream that everyone who tweets about the new Leaflet release will get an incredible luck boost for the next month. Sounds like true to me. + +Cheers,
+Vladimir, Leaflet creator and maintainer. diff --git a/docs/_posts/2013-02-20-guest-post-draw.md b/docs/_posts/2013-02-20-guest-post-draw.md new file mode 100644 index 00000000000..9a0587813c0 --- /dev/null +++ b/docs/_posts/2013-02-20-guest-post-draw.md @@ -0,0 +1,146 @@ +--- +layout: post +title: Leaflet.draw 0.2 Released +description: Leaflet.draw 0.2 released — brings vector drawing and editing tools to your Leaflet map. +author: Jacob Toye +authorsite: https://github.com/jacobtoye/ +--- + +_This is a guest post from Jacob Toye, an active Leaflet contributor and also the author of the most sophisticated vector drawing and editing plugin out there, which is presented in this post._ + +[Leaflet.draw](https://github.com/Leaflet/Leaflet.draw/) was born from the need to provide users with the ability draw polygons on the map. Leaflet already provided a very nice way of editing existing polylines and polygons. The logical next step was to expand on this functionality to allow the creation of these layers, and ultimately the other vector layers. + +Upon release the immediate response from the Leaflet community was very positive. It became clear that the next step would be progressing this tool to a state where users could edit and delete shapes in addition to creating them. This is ultimately what Leaflet.draw 0.2 set out to do. + +After a few months of off and on development, with most of this spare time kindy sponsored by my employer Smartrak, we proudly present Leaflet.draw 0.2 -- your one stop plugin for drawing, editing and deleting vectors and markers on Leaflet maps. :) + +_Note from Vladimir: the polyline/polygon editing functionality from Leaflet core has been moved into this plugin where it fits much better. The plugin in turn has moved into [Leaflet organization on GitHub](https://github.com/Leaflet) and is now officially supported by the Leaflet development team. Note that version 0.2 currently depends on Leaflet master (in-progress development version) to work._ + +You can download the latest version from the github repo. Please report any bugs you come across on the issues page. + +
+ +{:#plugin-features} +### Features + +Leaflet.draw is designed to not only be easy for end users to use, but also for developers to integrate. + + * Draw shapes on your map with easy to use drawing tools. + * Edit and delete vectors and markers. + * Super customizable: + * Customize the styles of each shape to fit in with your maps theme. + * Pick and choose the which tools you want to use. + * Roll your own by simply using the drawing and editing handlers. + * Event based system allows you to perform any necessary actions when shapes are created, edited or deleted. + +### How to use + +Leaflet.draw is very simple to drop into you Leaflet application. The following example will add both the draw and edit toolbars to a map: + + // create a map in the "map" div, set the view to a given place and zoom + var map = L.map('map').setView([175.30867, -37.77914], 13); + + // add an OpenStreetMap tile layer + L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + + // Initialize the FeatureGroup to store editable layers + var drawnItems = new L.FeatureGroup(); + map.addLayer(drawnItems); + + // Initialize the draw control and pass it the FeatureGroup of editable layers + var drawControl = new L.Control.Draw({ + edit: { + featureGroup: drawnItems + } + }); + map.addControl(drawControl); + +#### Handling newly created layers + +Once you have successfully added the Leaflet.draw plugin your map you will want to respond to the different actions users can trigger. + + map.on('draw:created', function (e) { + var type = e.layerType, + layer = e.layer; + + if (type === 'marker') { + // Do marker specific actions + } + + // Do whatever else you need to. (save to db, add to map etc) + drawnItems.addLayer(layer); + }); + + map.on('draw:edited', function () { + // Update db to save latest changes. + }); + + map.on('draw:deleted', function () { + // Update db to save latest changes. + }); + +See the Leaflet.draw README for more details on how to configure the plugin. + +### Thanks + +First and foremost I would like to thank my employer Smartrak. Without their attitude to open source software I would not have had the time to complete this plugin. + +The Leaflet developer community have been great in supporting this plugin through inspiration, pull requests and issue reports. Special thanks to: @mourner, @danzel, @brunob, @tnightingale, @Starefossen, and @shramov. + +### Closing + +I've had a great time implementing this plugin. I hope you enjoy using it. If you have a question or just want to say hi, send me an email at jacob.toye@gmail.com. + +Cheers, +Jacob Toye + + + + + + + + + + diff --git a/docs/_posts/2013-06-26-leaflet-0-6-released-dc-code-sprint-mapbox.md b/docs/_posts/2013-06-26-leaflet-0-6-released-dc-code-sprint-mapbox.md new file mode 100644 index 00000000000..152588df038 --- /dev/null +++ b/docs/_posts/2013-06-26-leaflet-0-6-released-dc-code-sprint-mapbox.md @@ -0,0 +1,31 @@ +--- +layout: post +title: Leaflet 0.6 Released, Code Sprint in DC with MapBox +description: Leaflet 0.6 released — with nicer controls, better usability and tons of API improvements and various bugfixes — more than 120 changes! The first ever Leaflet Code Sprint happening in Washington, DC with the MapBox team +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +_Leaflet 0.6 released — with nicer controls, better usability and tons of API improvements and various bugfixes — more than 120 changes! The first ever Leaflet Code Sprint happening in Washington, DC with the MapBox team..._ + +After 5 months of active development with [36 contributors involved](https://github.com/Leaflet/Leaflet/graphs/contributors?from=2013-01-18&to=2013-06-26&type=c) since the previous major version, today I'm excited to finally announce the **release of Leaflet 0.6** stable. + +0.6 highlights include nicer controls, lots of interaction usability improvements, many new API methods, events and options, ability to save layers as [GeoJSON](http://en.wikipedia.org/wiki/GeoJSON), much better test infrastructure and TONS of bugfixes that made Leaflet significantly more reliable. Checkout the huge detailed list of changes (**120+ total**!) [in the changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md). The [API reference](../../../reference.html) was also updated to reflect all these changes. + +The final push for the release (last 2 days) was a part of the first ever **Leaflet Code Sprint**, organized in Washington DC by the amazing [MapBox](http://mapbox.com), a company responsible for perhaps the greatest innovations and awesome tools in the geospatial world of recent years, now [using Leaflet for its JS API](mapbox.com/blog/mapbox-js-with-leaflet/) too. The sprint will go on for a week and there are many more awesome improvements to come in upcoming days (and lots of partying as well). + +On a related note, even GitHub itself is now [using Leaflet for GeoJSON visualizations](https://github.com/blog/1541-geojson-rendering-improvements), along with [Leaflet.markercluster](github.com/Leaflet/Leaflet.markercluster) & MapBox tiles! How cool is that? + +Special thanks go to [Universal Mind](http://universalmind.com/), my awesome employer and sponsor of Leaflet development for the past 5 months, to the most active contributors — [John Firebaugh](https://github.com/jfirebaugh) and [Tom MacWright](https://github.com/tmcw) of [MapBox](http://mapbox.com), [Dave Leaver](https://github.com/danzel) and [Jacob Toye](https://github.com/jacobtoye) of [Smartrak](http://www.smartrak.co.nz/), [Steve Kashishian](https://github.com/snkashis) of [First Mile Geo](http://www.firstmilegeo.com/), and to everyone else involved in contributions, bug reports, mailing list, Twitter, making awesome apps, etc. You're such an amazing community! I'm really happy to be a part of it. + +Grab the CDN links or downloads for the new release on the [download page](../../../download.html) as always. Enjoy! And be sure try it out and report any regressions in your apps so that we can patch them up immediately. + +**update** (June 27): 0.6.1 hotfix released with a couple of regressions fixed (particularly the one with FF scroll-zooming too fast).
+**update** (June 28): 0.6.2 hotfix released with a couple more minor issues fixed.
+**update** (July 17): 0.6.3 released with lots of regressions and bugs fixed.
+**update** (July 25): 0.6.4 released with a fix to 0.6.3 regression. + +P.S. Everyone who tweets/posts about the new Leaflet release or why he loves Leaflet over the next few days will get a karma boost of over 9000 points. I heard that from a passing monk in Georgetown yesterday, true story! + +Cheers,
+Vladimir, Leaflet creator and maintainer. diff --git a/docs/_posts/2013-06-28-leaflet-plugin-authoring-guide.md b/docs/_posts/2013-06-28-leaflet-plugin-authoring-guide.md new file mode 100644 index 00000000000..38c22d854e1 --- /dev/null +++ b/docs/_posts/2013-06-28-leaflet-plugin-authoring-guide.md @@ -0,0 +1,111 @@ +--- +layout: post +title: Leaflet Plugin Authoring Guide +description: A number of best practices and tips for publishing your own perfect Leaflet plugin +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +One of the greatest things about Leaflet is its powerful plugin ecosystem. +The [Leaflet plugins page](http://leafletjs.com/plugins.html) lists dozens of awesome plugins, and more are being added every week. + +This guide lists a number of best practices for publishing a Leaflet plugin that meets the quality standards of Leaflet itself. Also available [in the repo](https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md). + +### Presentation + +#### Repository + +The best place to put your Leaflet plugin to is a separate [GitHub](http://github.com) repository. +If you create a collection of plugins for different uses, +don't put them in one repo — +it's usually easier to work with small, self-contained plugins in individual repositories. + +#### Demo + +The most essential thing to do when publishing a plugin is to include a demo that showcases what the plugin does — +it's usually the first thing people will look for. + +The easiest way to put up a demo is using [GitHub Pages](http://pages.github.com/). +A good [starting point](https://help.github.com/articles/creating-project-pages-manually) is creating a `gh-pages` branch in your repo and adding an `index.html` page to it — +after pushing, it'll be published as `http://.github.io/`. + +#### Readme + +The next thing you need to have is a descriptive `README.md` in the root of the repo (or a link to a website with a similar content). +At a minimum it should contain the following items: + +- name of the plugin +- a simple, concise description of what it does +- requirements + - Leaflet version + - other external dependencies (if any) + - browser / device compatibility +- links to demos +- instructions for including the plugin +- simple usage code example +- API reference (methods, options, events) + +#### License + +Every open source repository should include a license. +If you don't know what open source license to choose for your code, +[MIT License](http://opensource.org/licenses/MIT) and [BSD 2-Clause License](http://opensource.org/licenses/BSD-2-Clause) are both good choices. +You can either put it in the repo as a `LICENSE` file or just link to the license from the Readme. + +### Code + +#### File Structure + +Keep the file structure clean and simple, +don't pile up lots of files in one place — +make it easy for a new person to find their way in your repo. + +A barebones repo for a simple plugin would look like this: + + my-plugin.js + README.md + +An example of a file structure for a more sophisticated plugin: + + /src JS source files + /dist minified plugin JS, CSS, images + /spec test files + /examples HTML examples of plugin usage + README.md + LICENSE + package.json + +#### Code Conventions + +Everyone's tastes are different, but it's important to be consistent with whatever conventions you choose for your plugin. + +For a good starting point, check out [Airbnb JavaScript Guide](https://github.com/airbnb/javascript). +Leaflet follows pretty much the same conventions +except for using smart tabs (hard tabs for indentation, spaces for alignment) +and putting a space after the `function` keyword. + +#### Plugin API + +Never expose global variables in your plugin.
+If you have a new class, put it directly in the `L` namespace (`L.MyPlugin`).
+If you inherit one of the existing classes, make it a sub-property (`L.TileLayer.Banana`).
+If you want to add new methods to existing Leaflet classes, you can do it like this: `L.Marker.include({myPlugin: …})`. + +Function, method and property names should be in `camelCase`.
+Class names should be in `CapitalizedCamelCase`. + +If you have a lot of arguments in your function, consider accepting an options object instead (putting default values where possible so that users don't need specify all of them): + + // bad + marker.myPlugin('bla', 'foo', null, {}, 5, 0); + + // good + marker.myPlugin('bla', { + optionOne: 'foo', + optionThree: 5 + }); + +And most importantly, keep it simple. Leaflet is all about *simplicity*. + +Cheers,
+Vladimir. diff --git a/docs/_posts/2013-11-18-leaflet-0-7-released-plans-for-future.md b/docs/_posts/2013-11-18-leaflet-0-7-released-plans-for-future.md new file mode 100644 index 00000000000..96b5d2a4d93 --- /dev/null +++ b/docs/_posts/2013-11-18-leaflet-0-7-released-plans-for-future.md @@ -0,0 +1,56 @@ +--- +layout: post +title: Leaflet 0.7 Release, MapBox and Plans for Future +description: Leaflet 0.7 Released — with IE11 touch support, upscaling tiles and tons of other improvements and bugfixes! Meanwhile, I've joined the MapBox team full-time. +author: Vladimir Agafonkin +authorsite: http://agafonkin.com/en +--- + +_Leaflet 0.7 Released — with IE11 touch support, upscaling tiles and tons of other improvements and bugfixes! Meanwhile, I've joined the MapBox team full-time..._ + +After another 5 months of active development with [lots of contributors involved](https://github.com/Leaflet/Leaflet/graphs/contributors?from=2013-06-27&to=2013-11-18&type=c), I'm happy to announce the **release of Leaflet 0.7** stable. + +This is a bugfix-heavy release — as Leaflet becomes more and more stable feature-wise, the focus shifts towards stability, usability and API improvements over new features. I've also been holding back some of the planned deep refactorings (which I'll talk about later in the post) until 0.7 is released, so that the heavy risky stuff is done at the beginning of the release cycle, leaving plenty of room to catch bugs and incompatible changes that can unintentionally break existing apps. + +### Joining MapBox + +In other news, I [joined the MapBox team full-time](https://www.mapbox.com/blog/vladimir-agafonkin-joins-mapbox/). This is extremely exciting for me, as this was my dream job for quite a while — [MapBox](https://www.mapbox.com) have changed the world of interactive mapping forever with all their amazing work, having some of the greatest geomapping engineers and designers of the world working together, pushing the boundaries of what's possible and inspiring others every day. + +For Leaflet, this can only mean very good things — much more time on Leaflet development, more enthusiasm, more play, more crazy experiments with maps (like [this one](https://www.mapbox.com/blog/dynamic-hill-shading/)), and lots of learning. I'm now one of the happiest map geeks ever. Stay tuned for tons of awesome! + +### 0.7 changes + +You can check out the [detailed changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md#07-dev-master) of what's already done over the recent months for 0.7 (about 90 improvements and bugfixes), but I'd like to mention some highlights: + +* Added the ability to **upscale tiles** to higher zoom levels (e.g. have zoom 19-20 when the source has 18 max). +* Added support for **IE11 touch devices**. MS unexpectedly broke their pointer API compatibility between Developer Preview and final IE11 release, and we eventually rewrote quite a bit of code to make everything work smoothly across all IE versions (both dekstop & mobile), fixing a bunch of IE10 bugs along the way as well. +* Officially **dropped IE6 support** (nobody cares anyway) and cleaned up/fixed IE7-8 styles. +* Dropped the need for **IE conditional comment** when including Leaflet, making the snippet much simpler — all IE7/8-specific styles got simplified and moved to the main `leaflet.css` file. +* Fixed an **obscure iOS7 memory leak** that crashed Safari when you tried to create several thousands of layers (e.g. markers for clustering). I still don't understand why it happens, but we managed to fix it with a bit of trickery. +* Fixed a critical **Chrome for Android** bug that made the tiles disappear after zooming on some devices. +* Removed some **Earth-related hardcode** in TileLayer implementation to make it easier for plugins like Proj4Leaflet to handle complex projections without horrible hacks. Some other work in this direction to follow in 0.8. +* Improved **panning performance** on complex pages with significant number of elements — we found out that simple things like setting a different cursor to `document` (for a "grabbing" hand) caused noticeable performance hit on some browsers (Chrome in particular). +* **Changed the way maxBounds works**, not enforcing a derived `minZoom` from it but restricting panning across lower zoom levels, along with some tricks to make it play better with panning inertia or offset zooming, etc. + +### Plans for 0.8 + +There are several big undertakings in refactoring Leaflet that I'd want to switch to immediately after releasing 0.7 — I've been holding them off for too long, and they'll be extremely beneficial for plugin and Leaflet-based API authors. Some of them are already in progress. + +* Refactoring the **layers** architecture. Currently there's a lot of duplication of logic across implementation of different layers (map, markers, vector layers, etc.), specifically event handling, zoom animation logic, zIndex and pane handling (what appears on top of what etc.). Making the code consistent, more universal and shared across different layers will make it much easier to customize layers and make your own (e.g. integrate d3, etc.) +* Splitting the huge TileLayer implementation into **GridLayer and TileLayer**, separating image tiles-related logic and grid-logic that will make other grid-like layer implementations (e.g. UTFGrid interaction or tiled GeoJSON) much simpler. +* Refactoring **zoom animation logic** to make the long-awaited Easey-style animations (zoom-panning between points) possible. +* Refactoring **projections** code to make it easier to set up flat maps and weird projections and customize how Leaflet handles them. +* Refactoring the **vector layers** code to make it possible to use different rendering backends (Canvas, SVG, etc.) for different layers on the same map and switch between them easily. This will also open it up for interesting extensions, like indexing layers with [RBush](https://github.com/mourner/rbush) for fast interaction features. + +While it's an ambitious plan and it may take more than one stable release, finishing all those refactorings will mean that Leaflet is getting ready for a 1.0 release. + +Another direction I'd like to focus on after releasing 0.7 is **website and documentation improvements**. First, Leaflet is begging for **more step-by-step tutorials** (with more advanced features like custom layers, custom controls, etc.), and I'd love to do a docs/tutorials sprint some time in future. Second, the presentation could be significantly improved — adding a prominent visual **showcase** or app gallery, making Leaflet users more prominent with some logos and quotes/testimonials, and updating the layout/design for a more stylish, clean look, etc. + +Hope that gives a good glimpse of the stuff to expect from Leafet in near future, and don't hesitate to ask any questions in comments — I'll be happy to answer! + +Grab the CDN links or downloads for the new release on the [download page](../../../download.html) as always. Be sure to try it out on your apps and report any regressions so that we can patch them up immediately. And lets make some nice Twitter buzz about the release as usual! + +To all the people wo've been involved in Leaflet contributions, bug reports, mailing list, Twitter buzz, making awesome apps and spreading the word about Leaflet — thank you! You are the most awesome community ever. + +Cheers,
+Vladimir. diff --git a/docs/atom.xml b/docs/atom.xml new file mode 100644 index 00000000000..773e1b69732 --- /dev/null +++ b/docs/atom.xml @@ -0,0 +1,29 @@ +--- +title: Leaflet Developer Blog Atom Feed +--- + + + + Leaflet Dev Blog + + + + + {{ site.time | date_to_xmlschema }} + http://leafletjs.com/ + + + Vladimir Agafonkin + + + {% for post in site.posts %} + + {{ post.title }} + + {{ post.date | date_to_xmlschema }} + http://leafletjs.com{{ post.id }} + {{ post.content | xml_escape }} + + {% endfor %} + + diff --git a/docs/blog.md b/docs/blog.md new file mode 100644 index 00000000000..74e85741a9c --- /dev/null +++ b/docs/blog.md @@ -0,0 +1,23 @@ +--- +layout: default +title: Blog +--- + +## Leaflet Developer Blog + +The main place for all important Leaflet-related news, tutorials, tips and development notes. [Subscribe to Atom feed](atom.xml) + +--- + +{% for post in site.posts %} +
+ +
+

{{ post.title }}

+

{{ post.description }}

+
+
+{% unless forloop.last %}
{% endunless %} +{% endfor %} diff --git a/docs/css/blueprint/ie.css b/docs/css/blueprint/ie.css new file mode 100644 index 00000000000..61a53714370 --- /dev/null +++ b/docs/css/blueprint/ie.css @@ -0,0 +1,36 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 1.0 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* ie.css */ +body {text-align:center;} +.container {text-align:left;} +* html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;} +* html legend {margin:0px -8px 16px 0;padding:0;} +sup {vertical-align:text-top;} +sub {vertical-align:text-bottom;} +html>body p code {*white-space:normal;} +hr {margin:-8px auto 11px;} +img {-ms-interpolation-mode:bicubic;} +.clearfix, .container {display:inline-block;} +* html .clearfix, * html .container {height:1%;} +fieldset {padding-top:0;} +legend {margin-top:-0.2em;margin-bottom:1em;margin-left:-0.5em;} +textarea {overflow:auto;} +label {vertical-align:middle;position:relative;top:-0.25em;} +input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;} +input.text:focus, input.title:focus {border-color:#666;} +input.text, input.title, textarea, select {margin:0.5em 0;} +input.checkbox, input.radio {position:relative;top:.25em;} +form.inline div, form.inline p {vertical-align:middle;} +form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;} +button, input.button {position:relative;top:0.25em;} \ No newline at end of file diff --git a/docs/css/blueprint/print.css b/docs/css/blueprint/print.css new file mode 100644 index 00000000000..fe2e0894467 --- /dev/null +++ b/docs/css/blueprint/print.css @@ -0,0 +1,29 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 1.0 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* print.css */ +body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;} +.container {background:none;} +hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;} +code {font:.9em "Courier New", Monaco, Courier, monospace;} +a img {border:none;} +p img.top {margin-top:0;} +blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;} +.small {font-size:.9em;} +.large {font-size:1.1em;} +.quiet {color:#999;} +.hide {display:none;} +a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;} +a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;} \ No newline at end of file diff --git a/docs/css/blueprint/screen.css b/docs/css/blueprint/screen.css new file mode 100644 index 00000000000..d0ef713d2a3 --- /dev/null +++ b/docs/css/blueprint/screen.css @@ -0,0 +1,265 @@ +/* ----------------------------------------------------------------------- + + + Blueprint CSS Framework 1.0 + http://blueprintcss.org + + * Copyright (c) 2007-Present. See LICENSE for more info. + * See README for instructions on how to use Blueprint. + * For credits and origins, see AUTHORS. + * This is a compressed file. See the sources in the 'src' directory. + +----------------------------------------------------------------------- */ + +/* reset.css */ +html {margin:0;padding:0;border:0;} +body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} +article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;} +body {line-height:1.5;background:white;} +table {border-collapse:separate;border-spacing:0;} +caption, th, td {text-align:left;font-weight:normal;float:none !important;} +table, th, td {vertical-align:middle;} +blockquote:before, blockquote:after, q:before, q:after {content:'';} +blockquote, q {quotes:"" "";} +a img {border:none;} +/*:focus {outline:0;}*/ + +/* typography.css */ +html {font-size:100.01%;} +body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;} +h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;} +h1 {font-size:3em;line-height:1;margin-bottom:0.5em;} +h2 {font-size:2em;margin-bottom:0.75em;} +h3 {font-size:1.5em;line-height:1;margin-bottom:1em;} +h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;} +h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;} +h6 {font-size:1em;font-weight:bold;} +h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;} +p {margin:0 0 1.5em;} +.left {float:left !important;} +p .left {margin:1.5em 1.5em 1.5em 0;padding:0;} +.right {float:right !important;} +p .right {margin:1.5em 0 1.5em 1.5em;padding:0;} +a:focus, a:hover {color:#09f;} +a {color:#06c;text-decoration:underline;} +blockquote {margin:1.5em;color:#666;font-style:italic;} +strong, dfn {font-weight:bold;} +em, dfn {font-style:italic;} +sup, sub {line-height:0;} +abbr, acronym {border-bottom:1px dotted #666;} +address {margin:0 0 1.5em;font-style:italic;} +del {color:#666;} +pre {margin:1.5em 0;white-space:pre;} +pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;} +li ul, li ol {margin:0;} +ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;} +ul {list-style-type:disc;} +ol {list-style-type:decimal;} +dl {margin:0 0 1.5em 0;} +dl dt {font-weight:bold;} +dd {margin-left:1.5em;} +table {margin-bottom:1.4em;width:100%;} +th {font-weight:bold;} +thead th {background:#c3d9ff;} +th, td, caption {padding:4px 10px 4px 5px; border: 1px solid #ccc;} +/*tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}*/ +tfoot {font-style:italic;} +caption {background:#eee;} +.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;} +.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;} +.hide {display:none;} +.quiet {color:#666;} +.loud {color:#000;} +.highlight {background:#ff0;} +.added {background:#060;color:#fff;} +.removed {background:#900;color:#fff;} +.first {margin-left:0;padding-left:0;} +.last {margin-right:0;padding-right:0;} +.top {margin-top:0;padding-top:0;} +.bottom {margin-bottom:0;padding-bottom:0;} + +/* forms.css */ +label {font-weight:bold;} +fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;} +legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;} +fieldset, #IE8#HACK {padding-top:1.4em;} +legend, #IE8#HACK {margin-top:0;margin-bottom:0;} +input[type=text], input[type=password], input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;} +input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666;} +select {background-color:#fff;border-width:1px;border-style:solid;} +input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;} +input.text, input.title {width:300px;padding:5px;} +input.title {font-size:1.5em;} +textarea {width:390px;height:250px;padding:5px;} +form.inline {line-height:3;} +form.inline p {margin-bottom:0;} +.error, .alert, .notice, .success/*, .info*/ {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;} +.error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;} +.notice {background:#fff6bf;color:#514721;border-color:#ffd324;} +.success {background:#e6efc2;color:#264409;border-color:#c6d880;} +/*.info {background:#d5edf8;color:#205791;border-color:#92cae4;}*/ +.error a, .alert a {color:#8a1f11;} +.notice a {color:#514721;} +.success a {color:#264409;} +/*.info a {color:#205791;}*/ + +/* grid.css */ +.container {width:950px;margin:0 auto;} +.showgrid {background:url(src/grid.png);} +.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;} +.last {margin-right:0;} +.span-1 {width:30px;} +.span-2 {width:70px;} +.span-3 {width:110px;} +.span-4 {width:150px;} +.span-5 {width:190px;} +.span-6 {width:230px;} +.span-7 {width:270px;} +.span-8 {width:310px;} +.span-9 {width:350px;} +.span-10 {width:390px;} +.span-11 {width:430px;} +.span-12 {width:470px;} +.span-13 {width:510px;} +.span-14 {width:550px;} +.span-15 {width:590px;} +.span-16 {width:630px;} +.span-17 {width:670px;} +.span-18 {width:710px;} +.span-19 {width:750px;} +.span-20 {width:790px;} +.span-21 {width:830px;} +.span-22 {width:870px;} +.span-23 {width:910px;} +.span-24 {width:950px;margin-right:0;} +input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;} +input.span-1, textarea.span-1 {width:18px;} +input.span-2, textarea.span-2 {width:58px;} +input.span-3, textarea.span-3 {width:98px;} +input.span-4, textarea.span-4 {width:138px;} +input.span-5, textarea.span-5 {width:178px;} +input.span-6, textarea.span-6 {width:218px;} +input.span-7, textarea.span-7 {width:258px;} +input.span-8, textarea.span-8 {width:298px;} +input.span-9, textarea.span-9 {width:338px;} +input.span-10, textarea.span-10 {width:378px;} +input.span-11, textarea.span-11 {width:418px;} +input.span-12, textarea.span-12 {width:458px;} +input.span-13, textarea.span-13 {width:498px;} +input.span-14, textarea.span-14 {width:538px;} +input.span-15, textarea.span-15 {width:578px;} +input.span-16, textarea.span-16 {width:618px;} +input.span-17, textarea.span-17 {width:658px;} +input.span-18, textarea.span-18 {width:698px;} +input.span-19, textarea.span-19 {width:738px;} +input.span-20, textarea.span-20 {width:778px;} +input.span-21, textarea.span-21 {width:818px;} +input.span-22, textarea.span-22 {width:858px;} +input.span-23, textarea.span-23 {width:898px;} +input.span-24, textarea.span-24 {width:938px;} +.append-1 {padding-right:40px;} +.append-2 {padding-right:80px;} +.append-3 {padding-right:120px;} +.append-4 {padding-right:160px;} +.append-5 {padding-right:200px;} +.append-6 {padding-right:240px;} +.append-7 {padding-right:280px;} +.append-8 {padding-right:320px;} +.append-9 {padding-right:360px;} +.append-10 {padding-right:400px;} +.append-11 {padding-right:440px;} +.append-12 {padding-right:480px;} +.append-13 {padding-right:520px;} +.append-14 {padding-right:560px;} +.append-15 {padding-right:600px;} +.append-16 {padding-right:640px;} +.append-17 {padding-right:680px;} +.append-18 {padding-right:720px;} +.append-19 {padding-right:760px;} +.append-20 {padding-right:800px;} +.append-21 {padding-right:840px;} +.append-22 {padding-right:880px;} +.append-23 {padding-right:920px;} +.prepend-1 {padding-left:40px;} +.prepend-2 {padding-left:80px;} +.prepend-3 {padding-left:120px;} +.prepend-4 {padding-left:160px;} +.prepend-5 {padding-left:200px;} +.prepend-6 {padding-left:240px;} +.prepend-7 {padding-left:280px;} +.prepend-8 {padding-left:320px;} +.prepend-9 {padding-left:360px;} +.prepend-10 {padding-left:400px;} +.prepend-11 {padding-left:440px;} +.prepend-12 {padding-left:480px;} +.prepend-13 {padding-left:520px;} +.prepend-14 {padding-left:560px;} +.prepend-15 {padding-left:600px;} +.prepend-16 {padding-left:640px;} +.prepend-17 {padding-left:680px;} +.prepend-18 {padding-left:720px;} +.prepend-19 {padding-left:760px;} +.prepend-20 {padding-left:800px;} +.prepend-21 {padding-left:840px;} +.prepend-22 {padding-left:880px;} +.prepend-23 {padding-left:920px;} +.border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;} +.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;} +.pull-1 {margin-left:-40px;} +.pull-2 {margin-left:-80px;} +.pull-3 {margin-left:-120px;} +.pull-4 {margin-left:-160px;} +.pull-5 {margin-left:-200px;} +.pull-6 {margin-left:-240px;} +.pull-7 {margin-left:-280px;} +.pull-8 {margin-left:-320px;} +.pull-9 {margin-left:-360px;} +.pull-10 {margin-left:-400px;} +.pull-11 {margin-left:-440px;} +.pull-12 {margin-left:-480px;} +.pull-13 {margin-left:-520px;} +.pull-14 {margin-left:-560px;} +.pull-15 {margin-left:-600px;} +.pull-16 {margin-left:-640px;} +.pull-17 {margin-left:-680px;} +.pull-18 {margin-left:-720px;} +.pull-19 {margin-left:-760px;} +.pull-20 {margin-left:-800px;} +.pull-21 {margin-left:-840px;} +.pull-22 {margin-left:-880px;} +.pull-23 {margin-left:-920px;} +.pull-24 {margin-left:-960px;} +.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;} +.push-1 {margin:0 -40px 1.5em 40px;} +.push-2 {margin:0 -80px 1.5em 80px;} +.push-3 {margin:0 -120px 1.5em 120px;} +.push-4 {margin:0 -160px 1.5em 160px;} +.push-5 {margin:0 -200px 1.5em 200px;} +.push-6 {margin:0 -240px 1.5em 240px;} +.push-7 {margin:0 -280px 1.5em 280px;} +.push-8 {margin:0 -320px 1.5em 320px;} +.push-9 {margin:0 -360px 1.5em 360px;} +.push-10 {margin:0 -400px 1.5em 400px;} +.push-11 {margin:0 -440px 1.5em 440px;} +.push-12 {margin:0 -480px 1.5em 480px;} +.push-13 {margin:0 -520px 1.5em 520px;} +.push-14 {margin:0 -560px 1.5em 560px;} +.push-15 {margin:0 -600px 1.5em 600px;} +.push-16 {margin:0 -640px 1.5em 640px;} +.push-17 {margin:0 -680px 1.5em 680px;} +.push-18 {margin:0 -720px 1.5em 720px;} +.push-19 {margin:0 -760px 1.5em 760px;} +.push-20 {margin:0 -800px 1.5em 800px;} +.push-21 {margin:0 -840px 1.5em 840px;} +.push-22 {margin:0 -880px 1.5em 880px;} +.push-23 {margin:0 -920px 1.5em 920px;} +.push-24 {margin:0 -960px 1.5em 960px;} +.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;} +div.prepend-top, .prepend-top {margin-top:1.5em;} +div.append-bottom, .append-bottom {margin-bottom:1.5em;} +.box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;} +hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:none;} +hr.space {background:#fff;color:#fff;visibility:hidden;} +.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;} +.clearfix, .container {display:block;} +.clear {clear:both;} diff --git a/docs/css/screen.css b/docs/css/screen.css new file mode 100644 index 00000000000..572c3abe922 --- /dev/null +++ b/docs/css/screen.css @@ -0,0 +1,553 @@ +body { + background: #f0f0f0 url(../images/paper.png); + } + +.container { + background: white; + position: relative; + z-index: 1; + + padding: 15px 40px 20px; + width: 790px; + + /*border: 1px solid #dfdfdf;*/ + border-top: 0; + + box-shadow: 0 0 60px rgba(0,0,0,0.15); + } +h1 { + margin-bottom: 0; + } +h1 a { + display: block; + height: 0; + padding-top: 73px; + width: 220px; + overflow: hidden; + background: url(../images/logo.png) 0 0 no-repeat; + margin-left: -10px; + margin-bottom: 8px; + text-decoration: none; + + -webkit-transition: 0.8s all; + + /*-webkit-animation-duration: 2s; + -webkit-animation-delay: 15s; + -webkit-animation-name: autumn; + -webkit-animation-iteration-count: 2; + -webkit-animation-direction: alternate;*/ + } + +@-webkit-keyframes autumn { + from { -webkit-filter: none; } + to { -webkit-filter: hue-rotate(-70deg) saturate(1.5); } +} + +h1 a:hover, h1 a:focus { + -webkit-filter: hue-rotate(-70deg) saturate(1.5); +} + + + +h2 { + font-size: 1.8em; + margin-top: 20px; + margin-bottom: 16px; + font-weight: bold; + padding: 4px 12px 3px; + margin-left: 0; + margin-right: 0; + background: #edeeef; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + + color: white; + + background-color: #b0de5c; + background-image: -moz-linear-gradient(top, #b0de5c, #82cb00); /* FF3.6 */ + background-image: -o-linear-gradient(top, #b0de5c, #82cb00); /* Opera 11.10+ */ + background-image: -webkit-gradient(linear, left top, left bottom, from(#b0de5c), to(#82cb00)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(top, #b0de5c, #82cb00); /* Chrome 10+, Saf5.1+ */ + background-image: linear-gradient(top, #b0de5c, #82cb00); + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#b0de5c', EndColorStr='#82cb00'); /* IE6�IE9 */ + + border-radius: 5px; + + text-shadow: 0 -1px 1px rgba(0,0,0,0.35); + } + +.api-page h2 { + cursor: pointer; + } + +.api-page h2[id]:before, +.api-page tr[id] td:first-child:before { + content: 'Permalink'; + display: inline-block; + margin: 0px 5px 0px -35px; + width: 30px; + height: 20px; + line-height: 20px; + vertical-align: baseline; + background: url(../images/sprite.png) -0px -0px no-repeat; + opacity: 0.2; + border-radius: 50%; + text-indent: -9999px; + overflow: hidden; + cursor: pointer; + position: absolute; + } + +.api-page tr[id] td:first-child:before { + opacity:0; + } + +.api-page tr[id]:hover td:first-child:before { + opacity:1; + } + +.api-page h2[id]:hover:before { opacity:1; } + +.api-page h2 { + margin-top: 2.5em; + } +h3 { + margin-top: 1.5em; + } +h3.alt { + margin-top: 0; + } +h4 { + margin-bottom: 0.5em; + line-height: inherit; + font-size: 1.1em; + font-weight: bold; + color: #777; + } + +h3.alt { + color: #999; + font-family: Georgia, serif; + font-style: italic; + font-weight: normal; + font-size: 1.7em; + } + +.container code, .container pre code { + font-family: "Consolas", "Menlo", "Lucida Console", "Courier New", monospace; + } +.container pre code { + padding: 10px 15px; + border: 1px solid #ccc; + background: white; + color: #444; + + box-shadow: 0 0 15px #ddd; + + border-radius: 5px; + } +pre code a { + text-decoration: none; + } +pre code a:hover { + text-decoration: underline; + } + +p code, td:last-child code { + color: #666; +} + +p, ul, ol, table { + font-size: 13px; +} +code { + font-size: 12px; +} + +/*.api-page td:last-child pre { + margin-top: 0.5em; +}*/ + +table { + border-collapse: collapse; + box-shadow: 0 3px 15px #f0f0f0; + } +th { + background-color: #eeeeee; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#d7d7d7)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(top, #eeeeee, #d7d7d7); /* Chrome 10+, Saf5.1+, iOS 5+ */ + background-image: -moz-linear-gradient(top, #eeeeee, #d7d7d7); /* FF3.6+ */ + background-image: -o-linear-gradient(top, #eeeeee, #d7d7d7); /* Opera 11.10+ */ + background-image: linear-gradient(to bottom, #eeeeee, #d7d7d7); + + text-shadow: 1px 1px 0 white; + + padding: 5px 10px; + } +th, td { + vertical-align: top; + /*line-height: 1.4;*/ + } +td { + padding: 8px 10px; + /*border-right: 1px solid #eee;*/ +} + + +.nav { + padding: 0; + margin: 28px 0 28px; + + list-style: none; + font-size: 1.2em; + border: 1px solid #dcddde; + overflow: hidden; + + background-color: #ffffff; + background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#eaebec)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(top, #ffffff, #eaebec); /* Chrome 10+, Saf5.1+, iOS 5+ */ + background-image: -moz-linear-gradient(top, #ffffff, #eaebec); /* FF3.6+ */ + background-image: -o-linear-gradient(top, #ffffff, #eaebec); /* Opera 11.10+ */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#ffffff', EndColorStr='#eff0f1'); /* IE6-IE9 */ + + border-radius: 10px; + box-shadow: 0 0 8px #eee; + } + + .nav li a, .nav li span { + float: left; + padding: 0 13px; + border-right: 1px solid #ddd; + text-shadow: 1px 1px 0 white; + line-height: 40px; + } + .nav a { + text-decoration: none; + color: #000; + background-color: rgba(0,0,0,0); + background-repeat: no-repeat; + -webkit-transition: 0.08s all; + } + .nav li span { + color: #000; + + background-color: #e8e9ea; + background-color: #f3f4f5; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f3f4f5), to(#cacbcc)); /* Saf4+, Chrome */ + background-image: -webkit-linear-gradient(top, #f3f4f5, #cacbcc); /* Chrome 10+, Saf5.1+, iOS 5+ */ + background-image: -moz-linear-gradient(top, #f3f4f5, #cacbcc); /* FF3.6+ */ + background-image: -o-linear-gradient(top, #f3f4f5, #cacbcc); /* Opera 11.10+ */ + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#f8f9fa', EndColorStr='#d4d5d6'); + } + .nav li a:hover, .nav li a:focus { + color: black; + + background-color: rgba(0,0,0,0.03); + + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#eff0f1', EndColorStr='#f6f7f8'); + } + .nav li a.active { + color: black; + + background-color: rgba(0,0,0,0.06); + + filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#eff0f1', EndColorStr='#f6f7f8'); + } + .nav li a.github-link, + .nav li a.twitter-link, + .nav li a.forum-link { + font-size: 12px; + color: #555; + float: right; + border-right: none; + border-left: 1px solid #ddd; + padding-left: 30px; + padding-right: 10px; + filter: none; + background-position: 9px 12px; + } + .nav li a.github-link { + background-image: url(../images/github.png); + background-position: 10px 12px; + padding-left: 32px; + } + .nav li a.twitter-link { + background-image: url(../images/twitter.png); + } + .nav li a.forum-link { + padding-right: 12px; + background-image: url(../images/forum.png); + } + .nav li a.github-link:hover, + .nav li a.twitter-link:hover, + .nav li a.forum-link:hover { + color: black; + } + + +#toc { + padding-left: 20px; + } + #toc ul { + list-style-type: none; + padding-left: 0; + } + #toc .colborder { + padding-right: 14px; + } + +#map-class { + margin-top: 1em; + } +a.nodocs { + color: #bbb; + } + +#features { + margin-top: -1.5em; + } + #features .quiet { + color: #777; + } + .noimpl { + color: #bbb; + } + +.container .map { + border: 1px solid #ccc; + margin: 1.5em 0; +} +.container .map:focus { + border-color: #999; +} + +h3 span { + color: #888; + } +h3.alt a { + color: #56b1ee; + text-decoration: none; +} +h3.alt a:hover { + text-decoration: underline; +} + +.example-img { + float: left; + margin: 0 18px 18px 0; + padding: 1px; + } +.bordered-img { + padding: 0; + border: 1px solid #ddd; + } + +.text-cut[class] { + font-size: 0; + color: white; +} + +.text-cut:after { + content: attr(data-cut); + font-size: 12px; + color: #777; +} + +.last ul { + margin-right: 0; +} + +table td { + color: #666; +} +table td:first-child { + color: #777; +} +table td:last-child { + color: black; +} + +table td code i { + color: #00A707; +} + +table td code b { + color: black; + font-weight: normal; +} + +.width250 { + width: 250px; +} +.width200 { + width: 200px; +} +.width300 { + width: 300px; +} +.width100 { + width: 100px; +} +.width140 { + width: 140px; +} +.minwidth { + width: 1px; +} + +table.factory-table { + width: 1px; +} + +table.factory-table td, table.factory-table th { + white-space: nowrap; + color: black; +} + +.factory-usage { + white-space: nowrap; +} + +.factory-usage code { + color: black; +} + +.social-buttons { + margin: 18px 0 0; + padding: 6px 0 2px 8px; + background: #f3f3f3; + position: absolute; + top: 10px; + right: 40px; + border-radius: 5px; +} + +.twitter-follow-button { + margin-left: 10px; +} + +.footer { + margin-top: 2.5em; +} + +dl { + overflow: hidden; +} + +dl dt { + font-size: 14px; + font-weight: normal; + float: left; + clear: left; + width: 160px; +} + +dl dd { + margin-bottom: 2em; + float: left; + width: 500px; +} + +.plugins td:first-child, .plugins td:last-child { + white-space: nowrap; +} + +.plugins td { + color: black; +} + + +#back-to-top { + text-decoration: none; + padding: 0px 10px 2px; + background: white; + color: black; + font-weight: bold; + font-size: 16px; + position: fixed; + top: -1px; + left: 0; + box-shadow: 0 3px 20px rgba(0, 0, 0, 0.1); +} + +.api-page td pre { + margin: 1em 0 0; + +} + +.api-page td pre code { + padding: 0 10px; + border: 0; + box-shadow: none; +} + + +p.notice { + border: none; + border-radius: 10px; + color: #444; + background: #DBF8A7; + padding: 5px 10px; + margin-bottom: 1.5em; +} + +.usedby a { + text-decoration: none; + margin: 0 2px; + color: #777; + font-size: 13px; +} + +.usedby a:hover { + text-decoration: underline; + color: black; +} + +.usedby { + background: #f3f3f3; + border-radius: 5px; + padding: 5px 7px; + margin: 1.5em -7px; + white-space: nowrap; + overflow: hidden; +} + +.post-title { + margin-top: 0; +} + +.post-date { + color: #777; + font-size: 18px; + line-height: 1; +} + +.post-meta { + color: #888; + margin-top: -1em; +} + +#disqus_thread { + margin-top: 3em; +} + +tr:target { + background: yellow; + -webkit-animation: highlight 2s ease 0.5s 1 normal forwards; + -moz-animation: highlight 2s ease 0.5s 1 normal forwards; + -o-animation: highlight 2s ease 0.5s 1 normal forwards; + animation: highlight 2s ease 0.5s 1 normal forwards; +} + +@-webkit-keyframes highlight { + 0% { background: yellow; } + 100% { background: white; } +} +@-moz-keyframes highlight { + 0% { background: yellow; } + 100% { background: white; } +} +@keyframes highlight { + 0% { background: yellow; } + 100% { background: white; } +} diff --git a/docs/download.md b/docs/download.md new file mode 100644 index 00000000000..b974e8474ca --- /dev/null +++ b/docs/download.md @@ -0,0 +1,66 @@ +--- +layout: default +title: Download +--- + +## Download Leaflet + + + + + + + + + + + + + + + + + + +
VersionDescription
Leaflet 0.7.3Stable version, released on November 18, 2013 and last updated on May 23, 2014.
Leaflet 0.6.4Previous stable version, released on June 26, 2013 and last updated on July 25, 2013.
Leaflet 0.8-devIn-progress version, developed on the master branch.
+ +[View Changelog](https://github.com/Leaflet/Leaflet/blob/master/CHANGELOG.md) + +Note that the master version can contain incompatible changes, +so please read the changelog carefully when upgrading to it. + +### Using a Hosted Version of Leaflet + +The latest stable Leaflet release is hosted on a CDN — to start using +it straight away, place this in the `head` of your HTML code: + + + + +### Leaflet Source Code + +These download packages above only contain the library itself. +If you want to download the full source code, including unit tests, files for debugging, build scripts, etc., +you can download it +from the GitHub repository. + +### Building Leaflet from the Source + +Leaflet build system is powered by the [Node.js](http://nodejs.org) platform, +which installs easily and works well across all major platforms. +Here are the steps to set it up: + + 1. [Download and install Node](http://nodejs.org) + 2. Run the following commands in the command line: + +
npm install -g jake
+npm install
+ +Now that you have everything installed, run `jake build` inside the Leaflet directory. +This will combine and compress the Leaflet source files, saving the build to the `dist` folder. + +### Building a Custom Version of Leaflet + +To make a custom build of the library with only the things you need, +open `build/build.html` page of the Leaflet source code contents, choose the components +(it figures out dependencies for you) and then run the command generated with it. diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 00000000000..20d230d07e7 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,72 @@ +--- +layout: default +title: Tutorials +--- + +## Leaflet Tutorials + +Every tutorial here comes with step-by-step code explanation and is easy enough even for beginner JavaScript developers. + +*** +[][1] + +### [Leaflet Quick Start Guide][1] + +A simple step-by-step guide that will quickly get you started with Leaflet basics, including setting up a Leaflet map (with Mapbox tiles) on your page, working with markers, polylines and popups, and dealing with events. + +*** +[][2] + +### [Leaflet on Mobile][2] + +In this tutorial, you'll learn how to create a fullscreen map tuned for mobile devices like iPhone, iPad or Android phones, and how to easily detect and use the current user location. + +*** +[][3] + +### [Markers with Custom Icons][3] + +In this pretty tutorial, you'll learn how to easily define your own icons for use by the markers you put on the map. + +*** +[][4] + +### [Using GeoJSON with Leaflet][4] + +In this tutorial, you'll learn how to create and interact with map vectors created from [GeoJSON][5] objects. + +*** +[][7] + +### [Interactive Choropleth Map][7] + +A case study of creating a colorful interactive [choropleth map](http://en.wikipedia.org/wiki/Choropleth_map) of US States Population Density with GeoJSON and some custom controls. News websites will love this. + +*** +[][6] + +### [Layer Groups and Layers Control][6] + +A tutorial on how to manage groups of layers and use the layer switching control. + + + +*** +If you find that an important tutorial is missing here, let us know! + + [1]: examples/quick-start.html + [2]: examples/mobile.html + [3]: examples/custom-icons.html + [4]: examples/geojson.html + [5]: http://geojson.org/ + [6]: examples/layers-control.html + [7]: examples/choropleth.html diff --git a/docs/examples/baseball-marker.png b/docs/examples/baseball-marker.png new file mode 100644 index 00000000000..187492b913b Binary files /dev/null and b/docs/examples/baseball-marker.png differ diff --git a/docs/examples/choropleth-example.html b/docs/examples/choropleth-example.html new file mode 100644 index 00000000000..dec2640f5e2 --- /dev/null +++ b/docs/examples/choropleth-example.html @@ -0,0 +1,174 @@ + + + + Leaflet Layers Control Example + + + + + + + + + +
+ + + + + + + diff --git a/docs/examples/choropleth.md b/docs/examples/choropleth.md new file mode 100644 index 00000000000..8eb80c9bd06 --- /dev/null +++ b/docs/examples/choropleth.md @@ -0,0 +1,390 @@ +--- +layout: tutorial +title: Interactive Choropleth Map +css: "#map { + height: 420px; + } + + .info { + padding: 6px 8px; + font: 14px/18px Arial, Helvetica, sans-serif; + background: white; + background: rgba(255,255,255,0.8); + box-shadow: 0 0 15px rgba(0,0,0,0.2); + border-radius: 5px; + } + .info h4 { + margin: 0 0 5px; + color: #777; + } + + .legend { + text-align: left; + line-height: 18px; + color: #555; + } + .legend i { + width: 18px; + height: 18px; + float: left; + margin-right: 8px; + opacity: 0.7; + }" +--- + +## Interactive Choropleth Map + +This is a case study of creating a colorful interactive [choropleth map](http://en.wikipedia.org/wiki/Choropleth_map) of US States Population Density with the help of [GeoJSON](geojson.html) and some [custom controls](../reference.html#icontrol) (that will hopefully convince all the remaining major news and government websites that do not use Leaflet yet to start doing so). + +The tutorial was inspired by the [Texas Tribune US Senate Runoff Results map](http://www.texastribune.org/library/data/us-senate-runoff-results-map/) (also powered by Leaflet), created by [Ryan Murphy](http://www.texastribune.org/about/staff/ryan-murphy/). + +
+ +[View example on a separate page →](choropleth-example.html) + +### Data Source + +We'll be creating a visualization of population density per US state. As the amount of data (state shapes and the density value for each state) is not very big, the most convenient and simple way to store and then display it is [GeoJSON](geojson.html). + +Each feature of our GeoJSON data ([us-states.js](us-states.js)) will look like this: + + { + "type": "Feature", + "properties": { + "name": "Alabama", + "density": 94.65 + }, + "geometry": ... + ... + } + +The GeoJSON with state shapes was kindly shared by [Mike Bostock](http://bost.ocks.org/mike) of [D3](http://d3js.org/) fame, extended with density values from [this Wikipedia article](http://en.wikipedia.org/wiki/List_of_U.S._states_by_population_density) based on July 1st 2011 data from [US Census Bureau](http://www.census.gov/) and assigned to `statesData` JS variable. + +### Basic States Map + +Let's display our states data on a map with a custom Mapbox style for nice grayscale tiles that look perfect as a background for visualizations: + + var map = L.map('map').setView([37.8, -96], 4); + + L.tileLayer('http://{s}.tiles.mapbox.com/{id}/{z}/{x}/{y}.png', { + id: 'examples.map-20v6611k', + attribution: ... + }).addTo(map); + + L.geoJson(statesData).addTo(map); + +
+ + +### Adding Some Color + +Now we need to color the states according to their population density. Choosing nice colors for a map can be tricky, but there's a great tool that can help with it --- [ColorBrewer](http://colorbrewer2.org/). Using the values we got from it, we create a function that returns a color based on population density: + + function getColor(d) { + return d > 1000 ? '#800026' : + d > 500 ? '#BD0026' : + d > 200 ? '#E31A1C' : + d > 100 ? '#FC4E2A' : + d > 50 ? '#FD8D3C' : + d > 20 ? '#FEB24C' : + d > 10 ? '#FED976' : + '#FFEDA0'; + } + +Next we define a styling function for our GeoJSON layer so that its `fillColor` depends on `feature.properties.density` property, also adjusting the appearance a bit and adding a nice touch with dashed stroke. + + function style(feature) { + return { + fillColor: getColor(feature.properties.density), + weight: 2, + opacity: 1, + color: 'white', + dashArray: '3', + fillOpacity: 0.7 + }; + } + + L.geoJson(statesData, {style: style}).addTo(map); + +Looks much better now! + +
+ + +### Adding Interaction + +Now lets make the states highlighted visually in some way when they are hovered with a mouse. First we'll define an event listener for layer `mouseover` event: + + function highlightFeature(e) { + var layer = e.target; + + layer.setStyle({ + weight: 5, + color: '#666', + dashArray: '', + fillOpacity: 0.7 + }); + + if (!L.Browser.ie && !L.Browser.opera) { + layer.bringToFront(); + } + } + +Here we get access to the layer that was hovered through `e.target`, set a thick grey border on the layer as our highlight effect, also bringing it to the front so that the border doesn't clash with nearby states (but not for IE or Opera, since they have problems doing `bringToFront` on `mouseover`). + +Next we'll define what happens on `mouseout`: + + function resetHighlight(e) { + geojson.resetStyle(e.target); + } + +The handy `geojson.resetStyle` method will reset the layer style to its default state (defined by our `style` function). For this to work, make sure our GeoJSON layer is accessible through the `geojson` variable by defining it before our listeners and assigning the layer to it later: + + var geojson; + // ... our listeners + geojson = L.geoJson(...); + +As an additional touch, lets define a `click` listener that zooms to the state: + + function zoomToFeature(e) { + map.fitBounds(e.target.getBounds()); + } + +Now we'll use the `onEachFeature` option to add the listeners on our state layers: + + function onEachFeature(feature, layer) { + layer.on({ + mouseover: highlightFeature, + mouseout: resetHighlight, + click: zoomToFeature + }); + } + + geojson = L.geoJson(statesData, { + style: style, + onEachFeature: onEachFeature + }).addTo(map); + +This makes the states highlight nicely on hover and gives us the ability to add other interactions inside our listeners. + +### Custom Info Control + +We could use the usual popups on click to show information about different states, but we'll choose a different route --- showing it on state hover inside a [custom control](../reference.html#icontrol). + +Here's the code for our control: + + var info = L.control(); + + info.onAdd = function (map) { + this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info" + this.update(); + return this._div; + }; + + // method that we will use to update the control based on feature properties passed + info.update = function (props) { + this._div.innerHTML = '

US Population Density

' + (props ? + '' + props.name + '
' + props.density + ' people / mi2' + : 'Hover over a state'); + }; + + info.addTo(map); + +We need to update the control when the user hovers over a state, so we'll also modify our listeners as follows: + + function highlightFeature(e) { + ... + info.update(layer.feature.properties); + } + + function resetHighlight(e) { + ... + info.update(); + } + +The control also needs some CSS styles to look nice: + +{: .css} + .info { + padding: 6px 8px; + font: 14px/16px Arial, Helvetica, sans-serif; + background: white; + background: rgba(255,255,255,0.8); + box-shadow: 0 0 15px rgba(0,0,0,0.2); + border-radius: 5px; + } + .info h4 { + margin: 0 0 5px; + color: #777; + } + +### Custom Legend Control + +Creating a control with a legend is easier, since it is static and doesn't change on state hover. JavaScript code: + + var legend = L.control({position: 'bottomright'}); + + legend.onAdd = function (map) { + + var div = L.DomUtil.create('div', 'info legend'), + grades = [0, 10, 20, 50, 100, 200, 500, 1000], + labels = []; + + // loop through our density intervals and generate a label with a colored square for each interval + for (var i = 0; i < grades.length; i++) { + div.innerHTML += + ' ' + + grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '
' : '+'); + } + + return div; + }; + + legend.addTo(map); + +CSS styles for the control (we also reuse the `info` class defined earlier): + +{: .css} + .legend { + line-height: 18px; + color: #555; + } + .legend i { + width: 18px; + height: 18px; + float: left; + margin-right: 8px; + opacity: 0.7; + } + +Enjoy the result on [the top of this page](#map) or on a [separate page](choropleth-example.html). + + + diff --git a/docs/examples/custom-icons-example.html b/docs/examples/custom-icons-example.html new file mode 100644 index 00000000000..dec565200f4 --- /dev/null +++ b/docs/examples/custom-icons-example.html @@ -0,0 +1,43 @@ + + + + Leaflet Quick Start Guide Example + + + + + + + +
+ + + + + diff --git a/docs/examples/custom-icons.md b/docs/examples/custom-icons.md new file mode 100644 index 00000000000..f4e4a17f5ab --- /dev/null +++ b/docs/examples/custom-icons.md @@ -0,0 +1,133 @@ +--- +layout: tutorial +title: Markers With Custom Icons +--- + +## Markers With Custom Icons + +In this tutorial, you'll learn how to easily define your own icons for use by the markers you put on the map. + +
+ +[View example on a separate page →](custom-icons-example.html) + + +### Preparing the images + +To make a custom icon, we usually need two images --- the actual icon image and the image of its shadow. For this tutorial, we took the Leaflet logo and created four images out of it --- 3 leaf images of different colors and one shadow image for the three: + +

+ + + + +

+ +Note that the white area in the images is actually transparent. + +### Creating an icon + +Marker icons in Leaflet are defined by [L.Icon](../reference.html#icon) objects, which are passed as an option when creating markers. Let's create a green leaf icon: + + var greenIcon = L.icon({ + iconUrl: 'leaf-green.png', + shadowUrl: 'leaf-shadow.png', + + iconSize: [38, 95], // size of the icon + shadowSize: [50, 64], // size of the shadow + iconAnchor: [22, 94], // point of the icon which will correspond to marker's location + shadowAnchor: [4, 62], // the same for the shadow + popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor + }); + +Now putting a marker with this icon on a map is easy: + + L.marker([51.5, -0.09], {icon: greenIcon}).addTo(map); + +
+ +### Defining an icon class + +What if we need to create several icons that have lots in common? Lets define our own icon class containing the shared options, inheriting from `L.Icon`! It's really easy in Leaflet: + + var LeafIcon = L.Icon.extend({ + options: { + shadowUrl: 'leaf-shadow.png', + iconSize: [38, 95], + shadowSize: [50, 64], + iconAnchor: [22, 94], + shadowAnchor: [4, 62], + popupAnchor: [-3, -76] + } + }); + +Now we can create all three of our leaf icons from this class and use them: + + var greenIcon = new LeafIcon({iconUrl: 'leaf-green.png'}), + redIcon = new LeafIcon({iconUrl: 'leaf-red.png'}), + orangeIcon = new LeafIcon({iconUrl: 'leaf-orange.png'}); + +You may have noticed that we used the `new` keyword for creating LeafIcon instances. So why do all Leaflet classes get created without it? The answer is simple: the real Leaflet classes are named with a capital letter (e.g. `L.Icon`), and they also need to be created with `new`, but there are also shortcuts with lowercase names (`L.icon`), created for convenience like this: + + L.icon = function (options) { + return new L.Icon(options); + }; + +You can do the same with your classes too. OK, lets finally put some markers with these icons on the map: + + L.marker([51.5, -0.09], {icon: greenIcon}).addTo(map).bindPopup("I am a green leaf."); + L.marker([51.495, -0.083], {icon: redIcon}).addTo(map).bindPopup("I am a red leaf."); + L.marker([51.49, -0.1], {icon: orangeIcon}).addTo(map).bindPopup("I am an orange leaf."); + +That's it. Now take a look at the [full example](custom-icons-example.html), the [L.Icon docs](../reference.html#icon), or browse [other examples](../examples.html). + + diff --git a/docs/examples/geojson-example.html b/docs/examples/geojson-example.html new file mode 100644 index 00000000000..0bac9fde97b --- /dev/null +++ b/docs/examples/geojson-example.html @@ -0,0 +1,90 @@ + + + + Leaflet GeoJSON Example + + + + + + + +
+ + + + + + + diff --git a/docs/examples/geojson.html b/docs/examples/geojson.html new file mode 100644 index 00000000000..4ddb2bea6c5 --- /dev/null +++ b/docs/examples/geojson.html @@ -0,0 +1,270 @@ +--- +layout: tutorial +title: Using GeoJSON with Leaflet +--- + +

Using GeoJSON with Leaflet

+ +

GeoJSON is becoming a very popular data format among many GIS technologies and services — it's simple, lightweight, straightforward, and Leaflet is quite good at handling it. In this example, you'll learn how to create and interact with map vectors created from GeoJSON objects.

+ +
+ + + + +

View example on a separate page →

+ +

About GeoJSON

+ +

According to http://geojson.org:

+ +
GeoJSON is a format for encoding a variety of geographic data structures. A GeoJSON object may represent a geometry, a feature, or a collection of features. GeoJSON supports the following geometry types: Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection. Features in GeoJSON contain a geometry object and additional properties, and a feature collection represents a list of features.
+ +

Leaflet supports all of the GeoJSON types above, but Features and FeatureCollections work best as they allow you to describe features with a set of properties. We can even use these properties to style our Leaflet vectors. Here's an example of a simple GeoJSON feature:

+ +
var geojsonFeature = {
+	"type": "Feature",
+	"properties": {
+		"name": "Coors Field",
+		"amenity": "Baseball Stadium",
+		"popupContent": "This is where the Rockies play!"
+	},
+	"geometry": {
+		"type": "Point",
+		"coordinates": [-104.99404, 39.75621]
+	}
+};
+
+ +

The GeoJSON layer

+ +

GeoJSON objects are added to the map through a GeoJSON layer. To create it and add it to a map, we can use the following code:

+ +
L.geoJson(geojsonFeature).addTo(map);
+ +

Alternatively, we could create an empty GeoJSON layer and assign it to a variable so that we can add more features to it later.

+ +
var myLayer = L.geoJson().addTo(map);
+myLayer.addData(geojsonFeature);
+
+ +

Options

+ +

style

+ +

The style option can be used to style features two different ways. First, we can pass a simple object that styles all paths (polylines and polygons) the same way:

+ +
var myLines = [{
+	"type": "LineString",
+	"coordinates": [[-100, 40], [-105, 45], [-110, 55]]
+}, {
+	"type": "LineString",
+	"coordinates": [[-105, 40], [-110, 45], [-115, 55]]
+}];
+
+var myStyle = {
+	"color": "#ff7800",
+	"weight": 5,
+	"opacity": 0.65
+};
+
+L.geoJson(myLines, {
+	style: myStyle
+}).addTo(map);
+ +

Alternatively, we can pass a function that styles individual features based on their properties. In the example below we check the "party" property and style our polygons accordingly:

+ +
var states = [{
+	"type": "Feature",
+	"properties": {"party": "Republican"},
+	"geometry": {
+		"type": "Polygon",
+		"coordinates": [[
+			[-104.05, 48.99],
+			[-97.22,  48.98],
+			[-96.58,  45.94],
+			[-104.03, 45.94],
+			[-104.05, 48.99]
+		]]
+	}
+}, {
+	"type": "Feature",
+	"properties": {"party": "Democrat"},
+	"geometry": {
+		"type": "Polygon",
+		"coordinates": [[
+			[-109.05, 41.00],
+			[-102.06, 40.99],
+			[-102.03, 36.99],
+			[-109.04, 36.99],
+			[-109.05, 41.00]
+		]]
+	}
+}];
+
+L.geoJson(states, {
+	style: function(feature) {
+		switch (feature.properties.party) {
+			case 'Republican': return {color: "#ff0000"};
+			case 'Democrat':   return {color: "#0000ff"};
+		}
+	}
+}).addTo(map);
+ +

pointToLayer

+ +

Points are handled differently than polylines and polygons. By default simple markers are drawn for GeoJSON Points. We can alter this by passing a pointToLayer function in a GeoJSON options object when creating the GeoJSON layer. This function is passed a LatLng and should return an instance of ILayer, in this case likely a Marker or CircleMarker.

+ +

Here we're using the pointToLayer option to create a CircleMarker:

+ +
var geojsonMarkerOptions = {
+	radius: 8,
+	fillColor: "#ff7800",
+	color: "#000",
+	weight: 1,
+	opacity: 1,
+	fillOpacity: 0.8
+};
+
+L.geoJson(someGeojsonFeature, {
+	pointToLayer: function (feature, latlng) {
+		return L.circleMarker(latlng, geojsonMarkerOptions);
+	}
+}).addTo(map);
+ +

We could also set the style property in this example — Leaflet is smart enough to apply styles to GeoJSON points if you create a vector layer like circle inside the pointToLayer function.

+ +

onEachFeature

+ +

The onEachFeature option is a function that gets called on each feature before adding it to a GeoJSON layer. A common reason to use this option is to attach a popup to features when they are clicked.

+ +
function onEachFeature(feature, layer) {
+	// does this feature have a property named popupContent?
+	if (feature.properties && feature.properties.popupContent) {
+		layer.bindPopup(feature.properties.popupContent);
+	}
+}
+
+var geojsonFeature = {
+	"type": "Feature",
+	"properties": {
+		"name": "Coors Field",
+		"amenity": "Baseball Stadium",
+		"popupContent": "This is where the Rockies play!"
+	},
+	"geometry": {
+		"type": "Point",
+		"coordinates": [-104.99404, 39.75621]
+	}
+};
+
+L.geoJson(geojsonFeature, {
+	onEachFeature: onEachFeature
+}).addTo(map);
+ +

filter

+ +

The filter option can be used to control the visibility of GeoJSON features. To accomplish this we pass a function as the filter option. This function gets called for each feature in your GeoJSON layer, and gets passed the feature and the layer. You can then utilise the values in the feature's properties to control the visibility by returning true or false.

+ +

In the example below "Busch Field" will not be shown on the map.

+ +
var someFeatures = [{
+	"type": "Feature",
+	"properties": {
+		"name": "Coors Field",
+		"show_on_map": true
+	},
+	"geometry": {
+		"type": "Point",
+		"coordinates": [-104.99404, 39.75621]
+	}
+}, {
+	"type": "Feature",
+	"properties": {
+		"name": "Busch Field",
+		"show_on_map": false
+	},
+	"geometry": {
+		"type": "Point",
+		"coordinates": [-104.98404, 39.74621]
+	}
+}];
+
+L.geoJson(someFeatures, {
+	filter: function(feature, layer) {
+		return feature.properties.show_on_map;
+	}
+}).addTo(map);
+ +

View the example page to see in detail what is possible with the GeoJSON layer.

diff --git a/docs/examples/layers-control-example.html b/docs/examples/layers-control-example.html new file mode 100644 index 00000000000..5c56cd220f5 --- /dev/null +++ b/docs/examples/layers-control-example.html @@ -0,0 +1,50 @@ + + + + Leaflet Layers Control Example + + + + + + + +
+ + + + + diff --git a/docs/examples/layers-control.md b/docs/examples/layers-control.md new file mode 100644 index 00000000000..a231f9c2877 --- /dev/null +++ b/docs/examples/layers-control.md @@ -0,0 +1,93 @@ +--- +layout: tutorial +title: Layer Groups and Layers Control +--- + +## Layer Groups and Layers Control + +This tutorial will show you how to group several layers into one, and how to use the layers control to allow users to easily switch different layers on your map. + +
+ +[View example on a separate page →](layers-control-example.html) + + +### Layer Groups + +Lets suppose you have a bunch of layers you want to combine into a group to handle them as one in your code: + + var littleton = L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.'), + denver = L.marker([39.74, -104.99]).bindPopup('This is Denver, CO.'), + aurora = L.marker([39.73, -104.8]).bindPopup('This is Aurora, CO.'), + golden = L.marker([39.77, -105.23]).bindPopup('This is Golden, CO.'); + +Instead of adding them directly to the map, you can do the following, using the LayerGroup class: + + var cities = L.layerGroup([littleton, denver, aurora, golden]); + +Easy enough! Now you have a `cities` layer that combines your city markers into one layer you can add or remove from the map at once. + +### Layers Control + +Leaflet has a nice little control that allows your users control what layers they want to see on your map. In addition to showing you how to use it, we'll show another handy use for layer groups. + +There are two types of layers --- base layers that are mutually exclusive (only one can be visible on your map), e.g. tile layers, and overlays --- all the other stuff you put over the base layers. In this example, we want to have two base layers (grayscale and night-style base map) to switch between, and an overlay to switch on and off --- city markers (those we created earlier). Lets create those layers and add the default ones to the map: + +
var grayscale = L.tileLayer(mapboxUrl, {id: 'MapID', attribution: mapboxAttribution}),
+	streets   = L.tileLayer(mapboxUrl, {id: 'MapID', attribution: mapboxAttribution});
+
+var map = L.map('map', {
+	center: [39.73, -104.99],
+	zoom: 10,
+	layers: [grayscale, cities]
+});
+ +Next, we'll create two objects. One will contain our base layers and one will contain our overlays. These are just simple objects with key/value pairs. The key is what sets the text for the layer in the control (e.g. "Streets"). The corresponding value is a reference to the layer (e.g. `streets`). + +
var baseMaps = {
+	"Grayscale": grayscale,
+	"Streets": streets
+};
+
+var overlayMaps = {
+    "Cities": cities
+};
+ +Now, all that's left to do is to create a [Layers Control](../reference.html#control-layers) and add it to the map. The first argument passed when creating the layers control is the base layers object. The second argument is the overlays object. Both arguments are optional --- for example, you can pass just a base layers object by ommiting the second argument, or just an overlays objects by passing `null` as the first argument. + +
L.control.layers(baseMaps, overlayMaps).addTo(map);
+ +Note that we added `grayscale`, `motorways` and `cities` layers to the map but didn't add `streets`. The layers control is smart enough to detect what layers we've already added and have corresponding checkboxes and radioboxes set. + +Also note that when using multiple base layers, only one of them should be added to the map at instantiation, but all of them should be present in the base layers object when creating the layers control. + +Now lets [view the result on a separate page →](layers-control-example.html) + + diff --git a/docs/examples/mobile-example.html b/docs/examples/mobile-example.html new file mode 100644 index 00000000000..9c6f1847f0c --- /dev/null +++ b/docs/examples/mobile-example.html @@ -0,0 +1,55 @@ + + + + Leaflet mobile example + + + + + + + + + + +
+ + + + diff --git a/docs/examples/mobile.md b/docs/examples/mobile.md new file mode 100644 index 00000000000..8309710eee3 --- /dev/null +++ b/docs/examples/mobile.md @@ -0,0 +1,71 @@ +--- +layout: tutorial +title: Leaflet on Mobile +--- + +## Leaflet on Mobile + +In this example, you'll learn how to create a fullscreen map tuned for mobile devices like iPhone, iPad or Android phones, and how to easily detect and use the current user location. + +[View example in fullscreen →](mobile-example.html) + +### Preparing the page + +First we'll take a look at the HTML & CSS code of the page. To make our map `div` element stretch to all available space (fullscreen), we can use the following CSS code: + +{: .css} + body { + padding: 0; + margin: 0; + } + html, body, #map { + height: 100%; + } + +Also, we need to tell the mobile browser to disable unwanted scaling of the page and set it to its actual size by placing the following line in the `head` section or our HTML page: + + + +### Initializing the map + +We'll now initialize the map in the JavaScript code exactly like we did in the [quick start guide](quick-start.html), but won't set the map view yet: + +
var map = L.map('map');
+
+L.tileLayer('http://{s}.tiles.mapbox.com/v3/MapID/997/256/{z}/{x}/{y}.png', {
+	attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
+	maxZoom: 18
+}).addTo(map);
+ +### Geolocation + +Leaflet has a very handy shortcut for zooming the map view to the detected location --- `locate` method with the `setView` option, replacing the usual `setView` method in the code: + + map.locate({setView: true, maxZoom: 16}); + +Here we specify 16 as the maximum zoom when setting the map view automatically. As soon as the user agrees to share its location and it's detected by the browser, the map will set the view to it. Now we have a working fullscreen mobile map! But what if we need to do something after the geolocation completed? Here's what the `locationfound` and `locationerror` events are for. Let's for example add a marker in the detected location, showing accuracy in a popup, by adding an event listener to `locationfound` event before the `locateAndSetView` call: + + function onLocationFound(e) { + var radius = e.accuracy / 2; + + L.marker(e.latlng).addTo(map) + .bindPopup("You are within " + radius + " meters from this point").openPopup(); + + L.circle(e.latlng, radius).addTo(map); + } + + map.on('locationfound', onLocationFound); + +Excellent! But it would also be nice to show an error message if the geolocation failed: + + function onLocationError(e) { + alert(e.message); + } + + map.on('locationerror', onLocationError); + +If you have `setView` option set to true and the geolocation failed, it will set the view to the whole world. + +Now the example is complete --- try it on your mobile phone: [View the full example →](mobile-example.html) + +Next steps would be to take a look at the detailed [documentation](../reference.html) and browse [other examples](../examples.html). diff --git a/docs/examples/quick-start-example.html b/docs/examples/quick-start-example.html new file mode 100644 index 00000000000..ceeee2717fb --- /dev/null +++ b/docs/examples/quick-start-example.html @@ -0,0 +1,57 @@ + + + + Leaflet Quick Start Guide Example + + + + + + + +
+ + + + + diff --git a/docs/examples/quick-start.md b/docs/examples/quick-start.md new file mode 100644 index 00000000000..37c0916cc3a --- /dev/null +++ b/docs/examples/quick-start.md @@ -0,0 +1,217 @@ +--- +layout: tutorial +title: Quick Start Guide +--- + +## Leaflet Quick Start Guide + +This step-by-step guide will quickly get you started on Leaflet basics, including setting up a Leaflet map, working with markers, polylines and popups, and dealing with events. + + +
+ +[View example on a separate page →](quick-start-example.html) + + +### Preparing your page + +Before writing any code for the map, you need to do the following preparation steps on your page: + + * Include Leaflet CSS file in the head section of your document: + + + + * Include Leaflet JavaScript file: + + + + * Put a `div` element with a certain `id` where you want your map to be: + +
+ + * Make sure the map container has a defined height, for example by setting it in CSS: + +
#map { height: 180px; }
+ +Now you're ready to initialize the map and do some stuff with it. + + +### Setting up the map + +
+ + +Let's create a map of the center of London with pretty Mapbox Streets tiles. First we'll initialize the map and set its view to our chosen geographical coordinates and a zoom level: + + var map = L.map('map').setView([51.505, -0.09], 13); + +By default (as we didn't pass any options when creating the map instance), all mouse and touch interactions on the map are enabled, and it has zoom and attribution controls. + +Note that `setView` call also returns the map object --- most Leaflet methods act like this when they don't return an explicit value, which allows convenient jQuery-like method chaining. + +Next we'll add a tile layer to add to our map, in this case it's a Mapbox Streets tile layer. Creating a tile layer usually involves setting the URL template for the tile images (get yours at [Mapbox](http://mapbox.com)), the attribution text and the maximum zoom level of the layer: + +
L.tileLayer('http://{s}.tiles.mapbox.com/v3/MapID/{z}/{x}/{y}.png', {
+	attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://mapbox.com">Mapbox</a>',
+	maxZoom: 18
+}).addTo(map);
+ +Make sure all the code is called after the `div` and `leaflet.js` inclusion. That's it! You have a working Leaflet map now. + +It's worth noting that Leaflet is provider-agnostic, meaning that it doesn't enforce a particular choice of providers for tiles, and it doesn't even contain a single provider-specific line of code, so you're free to use other providers if you need to (we'd recommend Mapbox though, it looks beautiful). + + +### Markers, circles and polygons + +
+ +Besides tile layers, you can easily add other things to your map, including markers, polylines, polygons, circles, and popups. Let's add a marker: + + var marker = L.marker([51.5, -0.09]).addTo(map); + +Adding a circle is the same (except for specifying the radius in meters as a second argument), but lets you control how it looks by passing options as the last argument when creating the object: + + var circle = L.circle([51.508, -0.11], 500, { + color: 'red', + fillColor: '#f03', + fillOpacity: 0.5 + }).addTo(map); + +Adding a polygon is as easy: + + var polygon = L.polygon([ + [51.509, -0.08], + [51.503, -0.06], + [51.51, -0.047] + ]).addTo(map); + + +### Working with popups + +
+ +Popups are usually used when you want to attach some information to a particular object on a map. Leaflet has a very handy shortcut for this: + + marker.bindPopup("Hello world!
I am a popup.").openPopup(); + circle.bindPopup("I am a circle."); + polygon.bindPopup("I am a polygon."); + +Try clicking on our objects. The `bindPopup` method attaches a popup with the specified HTML content to your marker so the popup appears when you click on the object, and the `openPopup` method (for markers only) immediately opens the attached popup. + +You can also use popups as layers (when you need something more than attaching a popup to an object): + + var popup = L.popup() + .setLatLng([51.5, -0.09]) + .setContent("I am a standalone popup.") + .openOn(map); + +Here we use `openOn` instead of `addTo` because it handles automatic closing of a previously opened popup when opening a new one which is good for usability. + + +### Dealing with events + +Every time something happens in Leaflet, e.g. user clicks on a marker or map zoom changes, the corresponding object sends an event which you can subscribe to with a function. It allows you to react to user interaction: + + function onMapClick(e) { + alert("You clicked the map at " + e.latlng); + } + + map.on('click', onMapClick); + +Each object has its own set of events --- see [documentation](../reference.html) for details. The first argument of the listener function is an event object --- it contains useful information about the event that happened. For example, map click event object (`e` in the example above) has `latlng` property which is a location at which the click occured. + +Lets improve our example by using a popup instead of an alert: + + var popup = L.popup(); + + function onMapClick(e) { + popup + .setLatLng(e.latlng) + .setContent("You clicked the map at " + e.latlng.toString()) + .openOn(map); + } + + map.on('click', onMapClick); + +Try clicking on the map and you will see the coordinates in a popup. View the full example → + +Now you've learned Leaflet basics and can start building map apps straight away! Don't forget to take a look at the detailed documentation or other examples. + + diff --git a/docs/examples/sample-geojson.js b/docs/examples/sample-geojson.js new file mode 100644 index 00000000000..2c2471e58cc --- /dev/null +++ b/docs/examples/sample-geojson.js @@ -0,0 +1,248 @@ +var freeBus = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [-105.00341892242432, 39.75383843460583], + [-105.0008225440979, 39.751891803969535] + ] + }, + "properties": { + "popupContent": "This is free bus that will take you across downtown.", + "underConstruction": false + }, + "id": 1 + }, + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [-105.0008225440979, 39.751891803969535], + [-104.99820470809937, 39.74979664004068] + ] + }, + "properties": { + "popupContent": "This is free bus that will take you across downtown.", + "underConstruction": true + }, + "id": 2 + }, + { + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [-104.99820470809937, 39.74979664004068], + [-104.98689651489258, 39.741052354709055] + ] + }, + "properties": { + "popupContent": "This is free bus that will take you across downtown.", + "underConstruction": false + }, + "id": 3 + } + ] +}; + +var lightRailStop = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "popupContent": "18th & California Light Rail Stop" + }, + "geometry": { + "type": "Point", + "coordinates": [-104.98999178409576, 39.74683938093904] + } + },{ + "type": "Feature", + "properties": { + "popupContent": "20th & Welton Light Rail Stop" + }, + "geometry": { + "type": "Point", + "coordinates": [-104.98689115047453, 39.747924136466565] + } + } + ] +}; + +var bicycleRental = { + "type": "FeatureCollection", + "features": [ + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9998241, + 39.7471494 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 51 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9983545, + 39.7502833 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 52 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9963919, + 39.7444271 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 54 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9960754, + 39.7498956 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 55 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9933717, + 39.7477264 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 57 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9913392, + 39.7432392 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 58 + }, + { + "geometry": { + "type": "Point", + "coordinates": [ + -104.9788452, + 39.6933755 + ] + }, + "type": "Feature", + "properties": { + "popupContent": "This is a B-Cycle Station. Come pick up a bike and pay by the hour. What a deal!" + }, + "id": 74 + } + ] +}; + +var campus = { + "type": "Feature", + "properties": { + "popupContent": "This is the Auraria West Campus", + "style": { + weight: 2, + color: "#999", + opacity: 1, + fillColor: "#B0DE5C", + fillOpacity: 0.8 + } + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-105.00432014465332, 39.74732195489861], + [-105.00715255737305, 39.74620006835170], + [-105.00921249389647, 39.74468219277038], + [-105.01067161560059, 39.74362625960105], + [-105.01195907592773, 39.74290029616054], + [-105.00989913940431, 39.74078835902781], + [-105.00758171081543, 39.74059036160317], + [-105.00346183776855, 39.74059036160317], + [-105.00097274780272, 39.74059036160317], + [-105.00062942504881, 39.74072235994946], + [-105.00020027160645, 39.74191033368865], + [-105.00071525573731, 39.74276830198601], + [-105.00097274780272, 39.74369225589818], + [-105.00097274780272, 39.74461619742136], + [-105.00123023986816, 39.74534214278395], + [-105.00183105468751, 39.74613407445653], + [-105.00432014465332, 39.74732195489861] + ],[ + [-105.00361204147337, 39.74354376414072], + [-105.00301122665405, 39.74278480127163], + [-105.00221729278564, 39.74316428375108], + [-105.00283956527711, 39.74390674342741], + [-105.00361204147337, 39.74354376414072] + ] + ],[ + [ + [-105.00942707061768, 39.73989736613708], + [-105.00942707061768, 39.73910536278566], + [-105.00685214996338, 39.73923736397631], + [-105.00384807586671, 39.73910536278566], + [-105.00174522399902, 39.73903936209552], + [-105.00041484832764, 39.73910536278566], + [-105.00041484832764, 39.73979836621592], + [-105.00535011291504, 39.73986436617916], + [-105.00942707061768, 39.73989736613708] + ] + ] + ] + } +}; + +var coorsField = { + "type": "Feature", + "properties": { + "popupContent": "Coors Field" + }, + "geometry": { + "type": "Point", + "coordinates": [-104.99404191970824, 39.756213909328125] + } +}; diff --git a/docs/examples/us-states.js b/docs/examples/us-states.js new file mode 100644 index 00000000000..7f6af2fb34b --- /dev/null +++ b/docs/examples/us-states.js @@ -0,0 +1,54 @@ +var statesData = {"type":"FeatureCollection","features":[ +{"type":"Feature","id":"01","properties":{"name":"Alabama","density":94.65},"geometry":{"type":"Polygon","coordinates":[[[-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696],[-85.069935,32.580372],[-84.960397,32.421541],[-85.004212,32.322956],[-84.889196,32.262709],[-85.058981,32.13674],[-85.053504,32.01077],[-85.141136,31.840985],[-85.042551,31.539753],[-85.113751,31.27686],[-85.004212,31.003013],[-85.497137,30.997536],[-87.600282,30.997536],[-87.633143,30.86609],[-87.408589,30.674397],[-87.446927,30.510088],[-87.37025,30.427934],[-87.518128,30.280057],[-87.655051,30.247195],[-87.90699,30.411504],[-87.934375,30.657966],[-88.011052,30.685351],[-88.10416,30.499135],[-88.137022,30.318396],[-88.394438,30.367688],[-88.471115,31.895754],[-88.241084,33.796253],[-88.098683,34.891641],[-88.202745,34.995703],[-87.359296,35.00118]]]}}, +{"type":"Feature","id":"02","properties":{"name":"Alaska","density":1.264},"geometry":{"type":"MultiPolygon","coordinates":[[[[-131.602021,55.117982],[-131.569159,55.28229],[-131.355558,55.183705],[-131.38842,55.01392],[-131.645836,55.035827],[-131.602021,55.117982]]],[[[-131.832052,55.42469],[-131.645836,55.304197],[-131.749898,55.128935],[-131.832052,55.189182],[-131.832052,55.42469]]],[[[-132.976733,56.437924],[-132.735747,56.459832],[-132.631685,56.421493],[-132.664547,56.273616],[-132.878148,56.240754],[-133.069841,56.333862],[-132.976733,56.437924]]],[[[-133.595627,56.350293],[-133.162949,56.317431],[-133.05341,56.125739],[-132.620732,55.912138],[-132.472854,55.780691],[-132.4619,55.671152],[-132.357838,55.649245],[-132.341408,55.506844],[-132.166146,55.364444],[-132.144238,55.238474],[-132.029222,55.276813],[-131.97993,55.178228],[-131.958022,54.789365],[-132.029222,54.701734],[-132.308546,54.718165],[-132.385223,54.915335],[-132.483808,54.898904],[-132.686455,55.046781],[-132.746701,54.997489],[-132.916486,55.046781],[-132.889102,54.898904],[-132.73027,54.937242],[-132.626209,54.882473],[-132.675501,54.679826],[-132.867194,54.701734],[-133.157472,54.95915],[-133.239626,55.090597],[-133.223195,55.22752],[-133.453227,55.216566],[-133.453227,55.320628],[-133.277964,55.331582],[-133.102702,55.42469],[-133.17938,55.588998],[-133.387503,55.62186],[-133.420365,55.884753],[-133.497042,56.0162],[-133.639442,55.923092],[-133.694212,56.070969],[-133.546335,56.142169],[-133.666827,56.311955],[-133.595627,56.350293]]],[[[-133.738027,55.556137],[-133.546335,55.490413],[-133.414888,55.572568],[-133.283441,55.534229],[-133.420365,55.386352],[-133.633966,55.430167],[-133.738027,55.556137]]],[[[-133.907813,56.930849],[-134.050213,57.029434],[-133.885905,57.095157],[-133.343688,57.002049],[-133.102702,57.007526],[-132.932917,56.82131],[-132.620732,56.667956],[-132.653593,56.55294],[-132.817901,56.492694],[-133.042456,56.520078],[-133.201287,56.448878],[-133.420365,56.492694],[-133.66135,56.448878],[-133.710643,56.684386],[-133.688735,56.837741],[-133.869474,56.843218],[-133.907813,56.930849]]],[[[-134.115936,56.48174],[-134.25286,56.558417],[-134.400737,56.722725],[-134.417168,56.848695],[-134.296675,56.908941],[-134.170706,56.848695],[-134.143321,56.952757],[-133.748981,56.772017],[-133.710643,56.596755],[-133.847566,56.574848],[-133.935197,56.377678],[-133.836612,56.322908],[-133.957105,56.092877],[-134.110459,56.142169],[-134.132367,55.999769],[-134.230952,56.070969],[-134.291198,56.350293],[-134.115936,56.48174]]],[[[-134.636246,56.28457],[-134.669107,56.169554],[-134.806031,56.235277],[-135.178463,56.67891],[-135.413971,56.810356],[-135.331817,56.914418],[-135.424925,57.166357],[-135.687818,57.369004],[-135.419448,57.566174],[-135.298955,57.48402],[-135.063447,57.418296],[-134.849846,57.407343],[-134.844369,57.248511],[-134.636246,56.728202],[-134.636246,56.28457]]],[[[-134.712923,58.223407],[-134.373353,58.14673],[-134.176183,58.157683],[-134.187137,58.081006],[-133.902336,57.807159],[-134.099505,57.850975],[-134.148798,57.757867],[-133.935197,57.615466],[-133.869474,57.363527],[-134.083075,57.297804],[-134.154275,57.210173],[-134.499322,57.029434],[-134.603384,57.034911],[-134.6472,57.226604],[-134.575999,57.341619],[-134.608861,57.511404],[-134.729354,57.719528],[-134.707446,57.829067],[-134.784123,58.097437],[-134.91557,58.212453],[-134.953908,58.409623],[-134.712923,58.223407]]],[[[-135.857603,57.330665],[-135.715203,57.330665],[-135.567326,57.149926],[-135.633049,57.023957],[-135.857603,56.996572],[-135.824742,57.193742],[-135.857603,57.330665]]],[[[-136.279328,58.206976],[-135.978096,58.201499],[-135.780926,58.28913],[-135.496125,58.168637],[-135.64948,58.037191],[-135.59471,57.987898],[-135.45231,58.135776],[-135.107263,58.086483],[-134.91557,57.976944],[-135.025108,57.779775],[-134.937477,57.763344],[-134.822462,57.500451],[-135.085355,57.462112],[-135.572802,57.675713],[-135.556372,57.456635],[-135.709726,57.369004],[-135.890465,57.407343],[-136.000004,57.544266],[-136.208128,57.637374],[-136.366959,57.829067],[-136.569606,57.916698],[-136.558652,58.075529],[-136.421728,58.130299],[-136.377913,58.267222],[-136.279328,58.206976]]],[[[-147.079854,60.200582],[-147.501579,59.948643],[-147.53444,59.850058],[-147.874011,59.784335],[-147.80281,59.937689],[-147.435855,60.09652],[-147.205824,60.271782],[-147.079854,60.200582]]],[[[-147.561825,60.578491],[-147.616594,60.370367],[-147.758995,60.156767],[-147.956165,60.227967],[-147.791856,60.474429],[-147.561825,60.578491]]],[[[-147.786379,70.245291],[-147.682318,70.201475],[-147.162008,70.15766],[-146.888161,70.185044],[-146.510252,70.185044],[-146.099482,70.146706],[-145.858496,70.168614],[-145.622988,70.08646],[-145.195787,69.993352],[-144.620708,69.971444],[-144.461877,70.026213],[-144.078491,70.059075],[-143.914183,70.130275],[-143.497935,70.141229],[-143.503412,70.091936],[-143.25695,70.119321],[-142.747594,70.042644],[-142.402547,69.916674],[-142.079408,69.856428],[-142.008207,69.801659],[-141.712453,69.790705],[-141.433129,69.697597],[-141.378359,69.63735],[-141.208574,69.686643],[-141.00045,69.648304],[-141.00045,60.304644],[-140.53491,60.22249],[-140.474664,60.310121],[-139.987216,60.184151],[-139.696939,60.342983],[-139.088998,60.359413],[-139.198537,60.091043],[-139.045183,59.997935],[-138.700135,59.910304],[-138.623458,59.767904],[-137.604747,59.242118],[-137.445916,58.908024],[-137.265177,59.001132],[-136.827022,59.159963],[-136.580559,59.16544],[-136.465544,59.285933],[-136.476498,59.466672],[-136.301236,59.466672],[-136.25742,59.625503],[-135.945234,59.663842],[-135.479694,59.800766],[-135.025108,59.565257],[-135.068924,59.422857],[-134.959385,59.280456],[-134.701969,59.247595],[-134.378829,59.033994],[-134.400737,58.973748],[-134.25286,58.858732],[-133.842089,58.727285],[-133.173903,58.152206],[-133.075318,57.998852],[-132.867194,57.845498],[-132.560485,57.505928],[-132.253777,57.21565],[-132.368792,57.095157],[-132.05113,57.051341],[-132.127807,56.876079],[-131.870391,56.804879],[-131.837529,56.602232],[-131.580113,56.613186],[-131.087188,56.405062],[-130.78048,56.366724],[-130.621648,56.268139],[-130.468294,56.240754],[-130.424478,56.142169],[-130.101339,56.114785],[-130.002754,55.994292],[-130.150631,55.769737],[-130.128724,55.583521],[-129.986323,55.276813],[-130.095862,55.200136],[-130.336847,54.920812],[-130.687372,54.718165],[-130.785957,54.822227],[-130.917403,54.789365],[-131.010511,54.997489],[-130.983126,55.08512],[-131.092665,55.189182],[-130.862634,55.298721],[-130.928357,55.337059],[-131.158389,55.200136],[-131.284358,55.287767],[-131.426759,55.238474],[-131.843006,55.457552],[-131.700606,55.698537],[-131.963499,55.616383],[-131.974453,55.49589],[-132.182576,55.588998],[-132.226392,55.704014],[-132.083991,55.829984],[-132.127807,55.955953],[-132.324977,55.851892],[-132.522147,56.076446],[-132.642639,56.032631],[-132.719317,56.218847],[-132.527624,56.339339],[-132.341408,56.339339],[-132.396177,56.487217],[-132.297592,56.67891],[-132.450946,56.673433],[-132.768609,56.837741],[-132.993164,57.034911],[-133.51895,57.177311],[-133.507996,57.577128],[-133.677781,57.62642],[-133.639442,57.790728],[-133.814705,57.834544],[-134.072121,58.053622],[-134.143321,58.168637],[-134.586953,58.206976],[-135.074401,58.502731],[-135.282525,59.192825],[-135.38111,59.033994],[-135.337294,58.891593],[-135.140124,58.617746],[-135.189417,58.573931],[-135.05797,58.349376],[-135.085355,58.201499],[-135.277048,58.234361],[-135.430402,58.398669],[-135.633049,58.426053],[-135.91785,58.382238],[-135.912373,58.617746],[-136.087635,58.814916],[-136.246466,58.75467],[-136.876314,58.962794],[-136.931084,58.902547],[-136.586036,58.836824],[-136.317666,58.672516],[-136.213604,58.667039],[-136.180743,58.535592],[-136.043819,58.382238],[-136.388867,58.294607],[-136.591513,58.349376],[-136.59699,58.212453],[-136.859883,58.316515],[-136.947514,58.393192],[-137.111823,58.393192],[-137.566409,58.590362],[-137.900502,58.765624],[-137.933364,58.869686],[-138.11958,59.02304],[-138.634412,59.132579],[-138.919213,59.247595],[-139.417615,59.379041],[-139.746231,59.505011],[-139.718846,59.641934],[-139.625738,59.598119],[-139.5162,59.68575],[-139.625738,59.88292],[-139.488815,59.992458],[-139.554538,60.041751],[-139.801,59.833627],[-140.315833,59.696704],[-140.92925,59.745996],[-141.444083,59.871966],[-141.46599,59.970551],[-141.706976,59.948643],[-141.964392,60.019843],[-142.539471,60.085566],[-142.873564,60.091043],[-143.623905,60.036274],[-143.892275,59.997935],[-144.231845,60.140336],[-144.65357,60.206059],[-144.785016,60.29369],[-144.834309,60.441568],[-145.124586,60.430614],[-145.223171,60.299167],[-145.738004,60.474429],[-145.820158,60.551106],[-146.351421,60.408706],[-146.608837,60.238921],[-146.718376,60.397752],[-146.608837,60.485383],[-146.455483,60.463475],[-145.951604,60.578491],[-146.017328,60.666122],[-146.252836,60.622307],[-146.345944,60.737322],[-146.565022,60.753753],[-146.784099,61.044031],[-146.866253,60.972831],[-147.172962,60.934492],[-147.271547,60.972831],[-147.375609,60.879723],[-147.758995,60.912584],[-147.775426,60.808523],[-148.032842,60.781138],[-148.153334,60.819476],[-148.065703,61.005692],[-148.175242,61.000215],[-148.350504,60.803046],[-148.109519,60.737322],[-148.087611,60.594922],[-147.939734,60.441568],[-148.027365,60.277259],[-148.219058,60.332029],[-148.273827,60.249875],[-148.087611,60.217013],[-147.983549,59.997935],[-148.251919,59.95412],[-148.399797,59.997935],[-148.635305,59.937689],[-148.755798,59.986981],[-149.067984,59.981505],[-149.05703,60.063659],[-149.204907,60.008889],[-149.287061,59.904827],[-149.418508,59.997935],[-149.582816,59.866489],[-149.511616,59.806242],[-149.741647,59.729565],[-149.949771,59.718611],[-150.031925,59.61455],[-150.25648,59.521442],[-150.409834,59.554303],[-150.579619,59.444764],[-150.716543,59.450241],[-151.001343,59.225687],[-151.308052,59.209256],[-151.406637,59.280456],[-151.592853,59.159963],[-151.976239,59.253071],[-151.888608,59.422857],[-151.636669,59.483103],[-151.47236,59.472149],[-151.423068,59.537872],[-151.127313,59.669319],[-151.116359,59.778858],[-151.505222,59.63098],[-151.828361,59.718611],[-151.8667,59.778858],[-151.702392,60.030797],[-151.423068,60.211536],[-151.379252,60.359413],[-151.297098,60.386798],[-151.264237,60.545629],[-151.406637,60.720892],[-151.06159,60.786615],[-150.404357,61.038554],[-150.245526,60.939969],[-150.042879,60.912584],[-149.741647,61.016646],[-150.075741,61.15357],[-150.207187,61.257632],[-150.47008,61.246678],[-150.656296,61.29597],[-150.711066,61.252155],[-151.023251,61.180954],[-151.165652,61.044031],[-151.477837,61.011169],[-151.800977,60.852338],[-151.833838,60.748276],[-152.080301,60.693507],[-152.13507,60.578491],[-152.310332,60.507291],[-152.392486,60.304644],[-152.732057,60.173197],[-152.567748,60.069136],[-152.704672,59.915781],[-153.022334,59.888397],[-153.049719,59.691227],[-153.345474,59.620026],[-153.438582,59.702181],[-153.586459,59.548826],[-153.761721,59.543349],[-153.72886,59.433811],[-154.117723,59.368087],[-154.1944,59.066856],[-153.750768,59.050425],[-153.400243,58.968271],[-153.301658,58.869686],[-153.444059,58.710854],[-153.679567,58.612269],[-153.898645,58.606793],[-153.920553,58.519161],[-154.062953,58.4863],[-153.99723,58.376761],[-154.145107,58.212453],[-154.46277,58.059098],[-154.643509,58.059098],[-154.818771,58.004329],[-154.988556,58.015283],[-155.120003,57.955037],[-155.081664,57.872883],[-155.328126,57.829067],[-155.377419,57.708574],[-155.547204,57.785251],[-155.73342,57.549743],[-156.045606,57.566174],[-156.023698,57.440204],[-156.209914,57.473066],[-156.34136,57.418296],[-156.34136,57.248511],[-156.549484,56.985618],[-156.883577,56.952757],[-157.157424,56.832264],[-157.20124,56.766541],[-157.376502,56.859649],[-157.672257,56.607709],[-157.754411,56.67891],[-157.918719,56.657002],[-157.957058,56.514601],[-158.126843,56.459832],[-158.32949,56.48174],[-158.488321,56.339339],[-158.208997,56.295524],[-158.510229,55.977861],[-159.375585,55.873799],[-159.616571,55.594475],[-159.676817,55.654722],[-159.643955,55.829984],[-159.813741,55.857368],[-160.027341,55.791645],[-160.060203,55.720445],[-160.394296,55.605429],[-160.536697,55.473983],[-160.580512,55.567091],[-160.668143,55.457552],[-160.865313,55.528752],[-161.232268,55.358967],[-161.506115,55.364444],[-161.467776,55.49589],[-161.588269,55.62186],[-161.697808,55.517798],[-161.686854,55.408259],[-162.053809,55.074166],[-162.179779,55.15632],[-162.218117,55.03035],[-162.470057,55.052258],[-162.508395,55.249428],[-162.661749,55.293244],[-162.716519,55.222043],[-162.579595,55.134412],[-162.645319,54.997489],[-162.847965,54.926289],[-163.00132,55.079643],[-163.187536,55.090597],[-163.220397,55.03035],[-163.034181,54.942719],[-163.373752,54.800319],[-163.14372,54.76198],[-163.138243,54.696257],[-163.329936,54.74555],[-163.587352,54.614103],[-164.085754,54.61958],[-164.332216,54.531949],[-164.354124,54.466226],[-164.638925,54.389548],[-164.847049,54.416933],[-164.918249,54.603149],[-164.710125,54.663395],[-164.551294,54.88795],[-164.34317,54.893427],[-163.894061,55.041304],[-163.532583,55.046781],[-163.39566,54.904381],[-163.291598,55.008443],[-163.313505,55.128935],[-163.105382,55.183705],[-162.880827,55.183705],[-162.579595,55.446598],[-162.245502,55.682106],[-161.807347,55.89023],[-161.292514,55.983338],[-161.078914,55.939523],[-160.87079,55.999769],[-160.816021,55.912138],[-160.931036,55.813553],[-160.805067,55.736876],[-160.766728,55.857368],[-160.509312,55.868322],[-160.438112,55.791645],[-160.27928,55.76426],[-160.273803,55.857368],[-160.536697,55.939523],[-160.558604,55.994292],[-160.383342,56.251708],[-160.147834,56.399586],[-159.830171,56.541986],[-159.326293,56.667956],[-158.959338,56.848695],[-158.784076,56.782971],[-158.641675,56.810356],[-158.701922,56.925372],[-158.658106,57.034911],[-158.378782,57.264942],[-157.995396,57.41282],[-157.688688,57.609989],[-157.705118,57.719528],[-157.458656,58.497254],[-157.07527,58.705377],[-157.119086,58.869686],[-158.039212,58.634177],[-158.32949,58.661562],[-158.40069,58.760147],[-158.564998,58.803962],[-158.619768,58.913501],[-158.767645,58.864209],[-158.860753,58.694424],[-158.701922,58.480823],[-158.893615,58.387715],[-159.0634,58.420577],[-159.392016,58.760147],[-159.616571,58.929932],[-159.731586,58.929932],[-159.808264,58.803962],[-159.906848,58.782055],[-160.054726,58.886116],[-160.235465,58.902547],[-160.317619,59.072332],[-160.854359,58.88064],[-161.33633,58.743716],[-161.374669,58.667039],[-161.752577,58.552023],[-161.938793,58.656085],[-161.769008,58.776578],[-161.829255,59.061379],[-161.955224,59.36261],[-161.703285,59.48858],[-161.911409,59.740519],[-162.092148,59.88292],[-162.234548,60.091043],[-162.448149,60.178674],[-162.502918,59.997935],[-162.760334,59.959597],[-163.171105,59.844581],[-163.66403,59.795289],[-163.9324,59.806242],[-164.162431,59.866489],[-164.189816,60.02532],[-164.386986,60.074613],[-164.699171,60.29369],[-164.962064,60.337506],[-165.268773,60.578491],[-165.060649,60.68803],[-165.016834,60.890677],[-165.175665,60.846861],[-165.197573,60.972831],[-165.120896,61.076893],[-165.323543,61.170001],[-165.34545,61.071416],[-165.591913,61.109754],[-165.624774,61.279539],[-165.816467,61.301447],[-165.920529,61.416463],[-165.915052,61.558863],[-166.106745,61.49314],[-166.139607,61.630064],[-165.904098,61.662925],[-166.095791,61.81628],[-165.756221,61.827233],[-165.756221,62.013449],[-165.674067,62.139419],[-165.044219,62.539236],[-164.912772,62.659728],[-164.819664,62.637821],[-164.874433,62.807606],[-164.633448,63.097884],[-164.425324,63.212899],[-164.036462,63.262192],[-163.73523,63.212899],[-163.313505,63.037637],[-163.039658,63.059545],[-162.661749,63.22933],[-162.272887,63.486746],[-162.075717,63.514131],[-162.026424,63.448408],[-161.555408,63.448408],[-161.13916,63.503177],[-160.766728,63.771547],[-160.766728,63.837271],[-160.952944,64.08921],[-160.974852,64.237087],[-161.26513,64.395918],[-161.374669,64.532842],[-161.078914,64.494503],[-160.79959,64.609519],[-160.783159,64.719058],[-161.144637,64.921705],[-161.413007,64.762873],[-161.664946,64.790258],[-161.900455,64.702627],[-162.168825,64.680719],[-162.234548,64.620473],[-162.541257,64.532842],[-162.634365,64.384965],[-162.787719,64.324718],[-162.858919,64.49998],[-163.045135,64.538319],[-163.176582,64.401395],[-163.253259,64.467119],[-163.598306,64.565704],[-164.304832,64.560227],[-164.80871,64.450688],[-165.000403,64.434257],[-165.411174,64.49998],[-166.188899,64.576658],[-166.391546,64.636904],[-166.484654,64.735489],[-166.413454,64.872412],[-166.692778,64.987428],[-166.638008,65.113398],[-166.462746,65.179121],[-166.517516,65.337952],[-166.796839,65.337952],[-167.026871,65.381768],[-167.47598,65.414629],[-167.711489,65.496784],[-168.072967,65.578938],[-168.105828,65.682999],[-167.541703,65.819923],[-166.829701,66.049954],[-166.3313,66.186878],[-166.046499,66.110201],[-165.756221,66.09377],[-165.690498,66.203309],[-165.86576,66.21974],[-165.88219,66.312848],[-165.186619,66.466202],[-164.403417,66.581218],[-163.981692,66.592172],[-163.751661,66.553833],[-163.872153,66.389525],[-163.828338,66.274509],[-163.915969,66.192355],[-163.768091,66.060908],[-163.494244,66.082816],[-163.149197,66.060908],[-162.749381,66.088293],[-162.634365,66.039001],[-162.371472,66.028047],[-162.14144,66.077339],[-161.840208,66.02257],[-161.549931,66.241647],[-161.341807,66.252601],[-161.199406,66.208786],[-161.128206,66.334755],[-161.528023,66.395002],[-161.911409,66.345709],[-161.87307,66.510017],[-162.174302,66.68528],[-162.502918,66.740049],[-162.601503,66.89888],[-162.344087,66.937219],[-162.015471,66.778388],[-162.075717,66.652418],[-161.916886,66.553833],[-161.571838,66.438817],[-161.489684,66.55931],[-161.884024,66.718141],[-161.714239,67.002942],[-161.851162,67.052235],[-162.240025,66.991988],[-162.639842,67.008419],[-162.700088,67.057712],[-162.902735,67.008419],[-163.740707,67.128912],[-163.757138,67.254881],[-164.009077,67.534205],[-164.211724,67.638267],[-164.534863,67.725898],[-165.192096,67.966884],[-165.493328,68.059992],[-165.794559,68.081899],[-166.243668,68.246208],[-166.681824,68.339316],[-166.703731,68.372177],[-166.375115,68.42147],[-166.227238,68.574824],[-166.216284,68.881533],[-165.329019,68.859625],[-164.255539,68.930825],[-163.976215,68.985595],[-163.532583,69.138949],[-163.110859,69.374457],[-163.023228,69.609966],[-162.842489,69.812613],[-162.470057,69.982398],[-162.311225,70.108367],[-161.851162,70.311014],[-161.779962,70.256245],[-161.396576,70.239814],[-160.837928,70.343876],[-160.487404,70.453415],[-159.649432,70.792985],[-159.33177,70.809416],[-159.298908,70.760123],[-158.975769,70.798462],[-158.658106,70.787508],[-158.033735,70.831323],[-157.420318,70.979201],[-156.812377,71.285909],[-156.565915,71.351633],[-156.522099,71.296863],[-155.585543,71.170894],[-155.508865,71.083263],[-155.832005,70.968247],[-155.979882,70.96277],[-155.974405,70.809416],[-155.503388,70.858708],[-155.476004,70.940862],[-155.262403,71.017539],[-155.191203,70.973724],[-155.032372,71.148986],[-154.566832,70.990155],[-154.643509,70.869662],[-154.353231,70.8368],[-154.183446,70.7656],[-153.931507,70.880616],[-153.487874,70.886093],[-153.235935,70.924431],[-152.589656,70.886093],[-152.26104,70.842277],[-152.419871,70.606769],[-151.817408,70.546523],[-151.773592,70.486276],[-151.187559,70.382214],[-151.182082,70.431507],[-150.760358,70.49723],[-150.355064,70.491753],[-150.349588,70.436984],[-150.114079,70.431507],[-149.867617,70.508184],[-149.462323,70.519138],[-149.177522,70.486276],[-148.78866,70.404122],[-148.607921,70.420553],[-148.350504,70.305537],[-148.202627,70.349353],[-147.961642,70.316491],[-147.786379,70.245291]]],[[[-152.94018,58.026237],[-152.945657,57.982421],[-153.290705,58.048145],[-153.044242,58.305561],[-152.819688,58.327469],[-152.666333,58.562977],[-152.496548,58.354853],[-152.354148,58.426053],[-152.080301,58.311038],[-152.080301,58.152206],[-152.480117,58.130299],[-152.655379,58.059098],[-152.94018,58.026237]]],[[[-153.958891,57.538789],[-153.67409,57.670236],[-153.931507,57.69762],[-153.936983,57.812636],[-153.723383,57.889313],[-153.570028,57.834544],[-153.548121,57.719528],[-153.46049,57.796205],[-153.455013,57.96599],[-153.268797,57.889313],[-153.235935,57.998852],[-153.071627,57.933129],[-152.874457,57.933129],[-152.721103,57.993375],[-152.469163,57.889313],[-152.469163,57.599035],[-152.151501,57.620943],[-152.359625,57.42925],[-152.74301,57.505928],[-152.60061,57.379958],[-152.710149,57.275896],[-152.907319,57.325188],[-152.912796,57.128019],[-153.214027,57.073249],[-153.312612,56.991095],[-153.498828,57.067772],[-153.695998,56.859649],[-153.849352,56.837741],[-154.013661,56.744633],[-154.073907,56.969187],[-154.303938,56.848695],[-154.314892,56.919895],[-154.523016,56.991095],[-154.539447,57.193742],[-154.742094,57.275896],[-154.627078,57.511404],[-154.227261,57.659282],[-153.980799,57.648328],[-153.958891,57.538789]]],[[[-154.53397,56.602232],[-154.742094,56.399586],[-154.807817,56.432447],[-154.53397,56.602232]]],[[[-155.634835,55.923092],[-155.476004,55.912138],[-155.530773,55.704014],[-155.793666,55.731399],[-155.837482,55.802599],[-155.634835,55.923092]]],[[[-159.890418,55.28229],[-159.950664,55.068689],[-160.257373,54.893427],[-160.109495,55.161797],[-160.005433,55.134412],[-159.890418,55.28229]]],[[[-160.520266,55.358967],[-160.33405,55.358967],[-160.339527,55.249428],[-160.525743,55.128935],[-160.690051,55.211089],[-160.794113,55.134412],[-160.854359,55.320628],[-160.79959,55.380875],[-160.520266,55.358967]]],[[[-162.256456,54.981058],[-162.234548,54.893427],[-162.349564,54.838658],[-162.437195,54.931766],[-162.256456,54.981058]]],[[[-162.415287,63.634624],[-162.563165,63.536039],[-162.612457,63.62367],[-162.415287,63.634624]]],[[[-162.80415,54.488133],[-162.590549,54.449795],[-162.612457,54.367641],[-162.782242,54.373118],[-162.80415,54.488133]]],[[[-165.548097,54.29644],[-165.476897,54.181425],[-165.630251,54.132132],[-165.685021,54.252625],[-165.548097,54.29644]]],[[[-165.73979,54.15404],[-166.046499,54.044501],[-166.112222,54.121178],[-165.980775,54.219763],[-165.73979,54.15404]]],[[[-166.364161,60.359413],[-166.13413,60.397752],[-166.084837,60.326552],[-165.88219,60.342983],[-165.685021,60.277259],[-165.646682,59.992458],[-165.750744,59.89935],[-166.00816,59.844581],[-166.062929,59.745996],[-166.440838,59.855535],[-166.6161,59.850058],[-166.994009,59.992458],[-167.125456,59.992458],[-167.344534,60.074613],[-167.421211,60.206059],[-167.311672,60.238921],[-166.93924,60.206059],[-166.763978,60.310121],[-166.577762,60.321075],[-166.495608,60.392275],[-166.364161,60.359413]]],[[[-166.375115,54.01164],[-166.210807,53.934962],[-166.5449,53.748746],[-166.539423,53.715885],[-166.117699,53.852808],[-166.112222,53.776131],[-166.282007,53.683023],[-166.555854,53.622777],[-166.583239,53.529669],[-166.878994,53.431084],[-167.13641,53.425607],[-167.306195,53.332499],[-167.623857,53.250345],[-167.793643,53.337976],[-167.459549,53.442038],[-167.355487,53.425607],[-167.103548,53.513238],[-167.163794,53.611823],[-167.021394,53.715885],[-166.807793,53.666592],[-166.785886,53.732316],[-167.015917,53.754223],[-167.141887,53.825424],[-167.032348,53.945916],[-166.643485,54.017116],[-166.561331,53.880193],[-166.375115,54.01164]]],[[[-168.790446,53.157237],[-168.40706,53.34893],[-168.385152,53.431084],[-168.237275,53.524192],[-168.007243,53.568007],[-167.886751,53.518715],[-167.842935,53.387268],[-168.270136,53.244868],[-168.500168,53.036744],[-168.686384,52.965544],[-168.790446,53.157237]]],[[[-169.74891,52.894344],[-169.705095,52.795759],[-169.962511,52.790282],[-169.989896,52.856005],[-169.74891,52.894344]]],[[[-170.148727,57.221127],[-170.28565,57.128019],[-170.313035,57.221127],[-170.148727,57.221127]]],[[[-170.669036,52.697174],[-170.603313,52.604066],[-170.789529,52.538343],[-170.816914,52.636928],[-170.669036,52.697174]]],[[[-171.742517,63.716778],[-170.94836,63.5689],[-170.488297,63.69487],[-170.280174,63.683916],[-170.093958,63.612716],[-170.044665,63.492223],[-169.644848,63.4265],[-169.518879,63.366254],[-168.99857,63.338869],[-168.686384,63.295053],[-168.856169,63.147176],[-169.108108,63.180038],[-169.376478,63.152653],[-169.513402,63.08693],[-169.639372,62.939052],[-169.831064,63.075976],[-170.055619,63.169084],[-170.263743,63.180038],[-170.362328,63.2841],[-170.866206,63.415546],[-171.101715,63.421023],[-171.463193,63.306007],[-171.73704,63.366254],[-171.852055,63.486746],[-171.742517,63.716778]]],[[[-172.432611,52.390465],[-172.41618,52.275449],[-172.607873,52.253542],[-172.569535,52.352127],[-172.432611,52.390465]]],[[[-173.626584,52.14948],[-173.495138,52.105664],[-173.122706,52.111141],[-173.106275,52.07828],[-173.549907,52.028987],[-173.626584,52.14948]]],[[[-174.322156,52.280926],[-174.327632,52.379511],[-174.185232,52.41785],[-173.982585,52.319265],[-174.059262,52.226157],[-174.179755,52.231634],[-174.141417,52.127572],[-174.333109,52.116618],[-174.738403,52.007079],[-174.968435,52.039941],[-174.902711,52.116618],[-174.656249,52.105664],[-174.322156,52.280926]]],[[[-176.469116,51.853725],[-176.288377,51.870156],[-176.288377,51.744186],[-176.518409,51.760617],[-176.80321,51.61274],[-176.912748,51.80991],[-176.792256,51.815386],[-176.775825,51.963264],[-176.627947,51.968741],[-176.627947,51.859202],[-176.469116,51.853725]]],[[[-177.153734,51.946833],[-177.044195,51.897541],[-177.120872,51.727755],[-177.274226,51.678463],[-177.279703,51.782525],[-177.153734,51.946833]]],[[[-178.123152,51.919448],[-177.953367,51.913971],[-177.800013,51.793479],[-177.964321,51.651078],[-178.123152,51.919448]]],[[[-187.107557,52.992929],[-187.293773,52.927205],[-187.304726,52.823143],[-188.90491,52.762897],[-188.642017,52.927205],[-188.642017,53.003883],[-187.107557,52.992929]]]]}}, +{"type":"Feature","id":"04","properties":{"name":"Arizona","density":57.05},"geometry":{"type":"Polygon","coordinates":[[[-109.042503,37.000263],[-109.04798,31.331629],[-111.074448,31.331629],[-112.246513,31.704061],[-114.815198,32.492741],[-114.72209,32.717295],[-114.524921,32.755634],[-114.470151,32.843265],[-114.524921,33.029481],[-114.661844,33.034958],[-114.727567,33.40739],[-114.524921,33.54979],[-114.497536,33.697668],[-114.535874,33.933176],[-114.415382,34.108438],[-114.256551,34.174162],[-114.136058,34.305608],[-114.333228,34.448009],[-114.470151,34.710902],[-114.634459,34.87521],[-114.634459,35.00118],[-114.574213,35.138103],[-114.596121,35.324319],[-114.678275,35.516012],[-114.738521,36.102045],[-114.371566,36.140383],[-114.251074,36.01989],[-114.152489,36.025367],[-114.048427,36.195153],[-114.048427,37.000263],[-110.499369,37.00574],[-109.042503,37.000263]]]}}, +{"type":"Feature","id":"05","properties":{"name":"Arkansas","density":56.43},"geometry":{"type":"Polygon","coordinates":[[[-94.473842,36.501861],[-90.152536,36.496384],[-90.064905,36.304691],[-90.218259,36.184199],[-90.377091,35.997983],[-89.730812,35.997983],[-89.763673,35.811767],[-89.911551,35.756997],[-89.944412,35.603643],[-90.130628,35.439335],[-90.114197,35.198349],[-90.212782,35.023087],[-90.311367,34.995703],[-90.251121,34.908072],[-90.409952,34.831394],[-90.481152,34.661609],[-90.585214,34.617794],[-90.568783,34.420624],[-90.749522,34.365854],[-90.744046,34.300131],[-90.952169,34.135823],[-90.891923,34.026284],[-91.072662,33.867453],[-91.231493,33.560744],[-91.056231,33.429298],[-91.143862,33.347144],[-91.089093,33.13902],[-91.16577,33.002096],[-93.608485,33.018527],[-94.041164,33.018527],[-94.041164,33.54979],[-94.183564,33.593606],[-94.380734,33.544313],[-94.484796,33.637421],[-94.430026,35.395519],[-94.616242,36.501861],[-94.473842,36.501861]]]}}, +{"type":"Feature","id":"06","properties":{"name":"California","density":241.7},"geometry":{"type":"Polygon","coordinates":[[[-123.233256,42.006186],[-122.378853,42.011663],[-121.037003,41.995232],[-120.001861,41.995232],[-119.996384,40.264519],[-120.001861,38.999346],[-118.71478,38.101128],[-117.498899,37.21934],[-116.540435,36.501861],[-115.85034,35.970598],[-114.634459,35.00118],[-114.634459,34.87521],[-114.470151,34.710902],[-114.333228,34.448009],[-114.136058,34.305608],[-114.256551,34.174162],[-114.415382,34.108438],[-114.535874,33.933176],[-114.497536,33.697668],[-114.524921,33.54979],[-114.727567,33.40739],[-114.661844,33.034958],[-114.524921,33.029481],[-114.470151,32.843265],[-114.524921,32.755634],[-114.72209,32.717295],[-116.04751,32.624187],[-117.126467,32.536556],[-117.24696,32.668003],[-117.252437,32.876127],[-117.329114,33.122589],[-117.471515,33.297851],[-117.7837,33.538836],[-118.183517,33.763391],[-118.260194,33.703145],[-118.413548,33.741483],[-118.391641,33.840068],[-118.566903,34.042715],[-118.802411,33.998899],[-119.218659,34.146777],[-119.278905,34.26727],[-119.558229,34.415147],[-119.875891,34.40967],[-120.138784,34.475393],[-120.472878,34.448009],[-120.64814,34.579455],[-120.609801,34.858779],[-120.670048,34.902595],[-120.631709,35.099764],[-120.894602,35.247642],[-120.905556,35.450289],[-121.004141,35.461243],[-121.168449,35.636505],[-121.283465,35.674843],[-121.332757,35.784382],[-121.716143,36.195153],[-121.896882,36.315645],[-121.935221,36.638785],[-121.858544,36.6114],[-121.787344,36.803093],[-121.929744,36.978355],[-122.105006,36.956447],[-122.335038,37.115279],[-122.417192,37.241248],[-122.400761,37.361741],[-122.515777,37.520572],[-122.515777,37.783465],[-122.329561,37.783465],[-122.406238,38.15042],[-122.488392,38.112082],[-122.504823,37.931343],[-122.701993,37.893004],[-122.937501,38.029928],[-122.97584,38.265436],[-123.129194,38.451652],[-123.331841,38.566668],[-123.44138,38.698114],[-123.737134,38.95553],[-123.687842,39.032208],[-123.824765,39.366301],[-123.764519,39.552517],[-123.85215,39.831841],[-124.109566,40.105688],[-124.361506,40.259042],[-124.410798,40.439781],[-124.158859,40.877937],[-124.109566,41.025814],[-124.158859,41.14083],[-124.065751,41.442061],[-124.147905,41.715908],[-124.257444,41.781632],[-124.213628,42.000709],[-123.233256,42.006186]]]}}, +{"type":"Feature","id":"08","properties":{"name":"Colorado","density":49.33},"geometry":{"type":"Polygon","coordinates":[[[-107.919731,41.003906],[-105.728954,40.998429],[-104.053011,41.003906],[-102.053927,41.003906],[-102.053927,40.001626],[-102.042974,36.994786],[-103.001438,37.000263],[-104.337812,36.994786],[-106.868158,36.994786],[-107.421329,37.000263],[-109.042503,37.000263],[-109.042503,38.166851],[-109.058934,38.27639],[-109.053457,39.125316],[-109.04798,40.998429],[-107.919731,41.003906]]]}}, +{"type":"Feature","id":"09","properties":{"name":"Connecticut","density":739.1},"geometry":{"type":"Polygon","coordinates":[[[-73.053528,42.039048],[-71.799309,42.022617],[-71.799309,42.006186],[-71.799309,41.414677],[-71.859555,41.321569],[-71.947186,41.338],[-72.385341,41.261322],[-72.905651,41.28323],[-73.130205,41.146307],[-73.371191,41.102491],[-73.655992,40.987475],[-73.727192,41.102491],[-73.48073,41.21203],[-73.55193,41.294184],[-73.486206,42.050002],[-73.053528,42.039048]]]}}, +{"type":"Feature","id":"10","properties":{"name":"Delaware","density":464.3},"geometry":{"type":"Polygon","coordinates":[[[-75.414089,39.804456],[-75.507197,39.683964],[-75.611259,39.61824],[-75.589352,39.459409],[-75.441474,39.311532],[-75.403136,39.065069],[-75.189535,38.807653],[-75.09095,38.796699],[-75.047134,38.451652],[-75.693413,38.462606],[-75.786521,39.722302],[-75.616736,39.831841],[-75.414089,39.804456]]]}}, +{"type":"Feature","id":"11","properties":{"name":"District of Columbia","density":10065},"geometry":{"type":"Polygon","coordinates":[[[-77.035264,38.993869],[-76.909294,38.895284],[-77.040741,38.791222],[-77.117418,38.933623],[-77.035264,38.993869]]]}}, +{"type":"Feature","id":"12","properties":{"name":"Florida","density":353.4},"geometry":{"type":"Polygon","coordinates":[[[-85.497137,30.997536],[-85.004212,31.003013],[-84.867289,30.712735],[-83.498053,30.647012],[-82.216449,30.570335],[-82.167157,30.356734],[-82.046664,30.362211],[-82.002849,30.564858],[-82.041187,30.751074],[-81.948079,30.827751],[-81.718048,30.745597],[-81.444201,30.707258],[-81.383954,30.27458],[-81.257985,29.787132],[-80.967707,29.14633],[-80.524075,28.461713],[-80.589798,28.41242],[-80.56789,28.094758],[-80.381674,27.738757],[-80.091397,27.021277],[-80.03115,26.796723],[-80.036627,26.566691],[-80.146166,25.739673],[-80.239274,25.723243],[-80.337859,25.465826],[-80.304997,25.383672],[-80.49669,25.197456],[-80.573367,25.241272],[-80.759583,25.164595],[-81.077246,25.120779],[-81.170354,25.224841],[-81.126538,25.378195],[-81.351093,25.821827],[-81.526355,25.903982],[-81.679709,25.843735],[-81.800202,26.090198],[-81.833064,26.292844],[-82.041187,26.517399],[-82.09048,26.665276],[-82.057618,26.878877],[-82.172634,26.917216],[-82.145249,26.791246],[-82.249311,26.758384],[-82.566974,27.300601],[-82.692943,27.437525],[-82.391711,27.837342],[-82.588881,27.815434],[-82.720328,27.689464],[-82.851774,27.886634],[-82.676512,28.434328],[-82.643651,28.888914],[-82.764143,28.998453],[-82.802482,29.14633],[-82.994175,29.179192],[-83.218729,29.420177],[-83.399469,29.518762],[-83.410422,29.66664],[-83.536392,29.721409],[-83.640454,29.885717],[-84.02384,30.104795],[-84.357933,30.055502],[-84.341502,29.902148],[-84.451041,29.929533],[-84.867289,29.743317],[-85.310921,29.699501],[-85.299967,29.80904],[-85.404029,29.940487],[-85.924338,30.236241],[-86.29677,30.362211],[-86.630863,30.395073],[-86.910187,30.373165],[-87.518128,30.280057],[-87.37025,30.427934],[-87.446927,30.510088],[-87.408589,30.674397],[-87.633143,30.86609],[-87.600282,30.997536],[-85.497137,30.997536]]]}}, +{"type":"Feature","id":"13","properties":{"name":"Georgia","density":169.5},"geometry":{"type":"Polygon","coordinates":[[[-83.109191,35.00118],[-83.322791,34.787579],[-83.339222,34.683517],[-83.005129,34.469916],[-82.901067,34.486347],[-82.747713,34.26727],[-82.714851,34.152254],[-82.55602,33.94413],[-82.325988,33.81816],[-82.194542,33.631944],[-81.926172,33.462159],[-81.937125,33.347144],[-81.761863,33.160928],[-81.493493,33.007573],[-81.42777,32.843265],[-81.416816,32.629664],[-81.279893,32.558464],[-81.121061,32.290094],[-81.115584,32.120309],[-80.885553,32.032678],[-81.132015,31.693108],[-81.175831,31.517845],[-81.279893,31.364491],[-81.290846,31.20566],[-81.400385,31.13446],[-81.444201,30.707258],[-81.718048,30.745597],[-81.948079,30.827751],[-82.041187,30.751074],[-82.002849,30.564858],[-82.046664,30.362211],[-82.167157,30.356734],[-82.216449,30.570335],[-83.498053,30.647012],[-84.867289,30.712735],[-85.004212,31.003013],[-85.113751,31.27686],[-85.042551,31.539753],[-85.141136,31.840985],[-85.053504,32.01077],[-85.058981,32.13674],[-84.889196,32.262709],[-85.004212,32.322956],[-84.960397,32.421541],[-85.069935,32.580372],[-85.184951,32.859696],[-85.431413,34.124869],[-85.606675,34.984749],[-84.319594,34.990226],[-83.618546,34.984749],[-83.109191,35.00118]]]}}, +{"type":"Feature","id":"15","properties":{"name":"Hawaii","density":214.1},"geometry":{"type":"MultiPolygon","coordinates":[[[[-155.634835,18.948267],[-155.881297,19.035898],[-155.919636,19.123529],[-155.886774,19.348084],[-156.062036,19.73147],[-155.925113,19.857439],[-155.826528,20.032702],[-155.897728,20.147717],[-155.87582,20.26821],[-155.596496,20.12581],[-155.284311,20.021748],[-155.092618,19.868393],[-155.092618,19.736947],[-154.807817,19.523346],[-154.983079,19.348084],[-155.295265,19.26593],[-155.514342,19.134483],[-155.634835,18.948267]]],[[[-156.587823,21.029505],[-156.472807,20.892581],[-156.324929,20.952827],[-156.00179,20.793996],[-156.051082,20.651596],[-156.379699,20.580396],[-156.445422,20.60778],[-156.461853,20.783042],[-156.631638,20.821381],[-156.697361,20.919966],[-156.587823,21.029505]]],[[[-156.982162,21.210244],[-157.080747,21.106182],[-157.310779,21.106182],[-157.239579,21.221198],[-156.982162,21.210244]]],[[[-157.951581,21.697691],[-157.842042,21.462183],[-157.896811,21.325259],[-158.110412,21.303352],[-158.252813,21.582676],[-158.126843,21.588153],[-157.951581,21.697691]]],[[[-159.468693,22.228955],[-159.353678,22.218001],[-159.298908,22.113939],[-159.33177,21.966061],[-159.446786,21.872953],[-159.764448,21.987969],[-159.726109,22.152277],[-159.468693,22.228955]]]]}}, +{"type":"Feature","id":"16","properties":{"name":"Idaho","density":19.15},"geometry":{"type":"Polygon","coordinates":[[[-116.04751,49.000239],[-116.04751,47.976051],[-115.724371,47.696727],[-115.718894,47.42288],[-115.527201,47.302388],[-115.324554,47.258572],[-115.302646,47.187372],[-114.930214,46.919002],[-114.886399,46.809463],[-114.623506,46.705401],[-114.612552,46.639678],[-114.322274,46.645155],[-114.464674,46.272723],[-114.492059,46.037214],[-114.387997,45.88386],[-114.568736,45.774321],[-114.497536,45.670259],[-114.546828,45.560721],[-114.333228,45.456659],[-114.086765,45.593582],[-113.98818,45.703121],[-113.807441,45.604536],[-113.834826,45.522382],[-113.736241,45.330689],[-113.571933,45.128042],[-113.45144,45.056842],[-113.456917,44.865149],[-113.341901,44.782995],[-113.133778,44.772041],[-113.002331,44.448902],[-112.887315,44.394132],[-112.783254,44.48724],[-112.471068,44.481763],[-112.241036,44.569394],[-112.104113,44.520102],[-111.868605,44.563917],[-111.819312,44.509148],[-111.616665,44.547487],[-111.386634,44.75561],[-111.227803,44.580348],[-111.047063,44.476286],[-111.047063,42.000709],[-112.164359,41.995232],[-114.04295,41.995232],[-117.027882,42.000709],[-117.027882,43.830007],[-116.896436,44.158624],[-116.97859,44.240778],[-117.170283,44.257209],[-117.241483,44.394132],[-117.038836,44.750133],[-116.934774,44.782995],[-116.830713,44.930872],[-116.847143,45.02398],[-116.732128,45.144473],[-116.671881,45.319735],[-116.463758,45.61549],[-116.545912,45.752413],[-116.78142,45.823614],[-116.918344,45.993399],[-116.92382,46.168661],[-117.055267,46.343923],[-117.038836,46.426077],[-117.044313,47.762451],[-117.033359,49.000239],[-116.04751,49.000239]]]}}, +{"type":"Feature","id":"17","properties":{"name":"Illinois","density":231.5},"geometry":{"type":"Polygon","coordinates":[[[-90.639984,42.510065],[-88.788778,42.493634],[-87.802929,42.493634],[-87.83579,42.301941],[-87.682436,42.077386],[-87.523605,41.710431],[-87.529082,39.34987],[-87.63862,39.169131],[-87.512651,38.95553],[-87.49622,38.780268],[-87.62219,38.637868],[-87.655051,38.506421],[-87.83579,38.292821],[-87.950806,38.27639],[-87.923421,38.15042],[-88.000098,38.101128],[-88.060345,37.865619],[-88.027483,37.799896],[-88.15893,37.657496],[-88.065822,37.482234],[-88.476592,37.389126],[-88.514931,37.285064],[-88.421823,37.153617],[-88.547792,37.071463],[-88.914747,37.224817],[-89.029763,37.213863],[-89.183118,37.038601],[-89.133825,36.983832],[-89.292656,36.994786],[-89.517211,37.279587],[-89.435057,37.34531],[-89.517211,37.537003],[-89.517211,37.690357],[-89.84035,37.903958],[-89.949889,37.88205],[-90.059428,38.013497],[-90.355183,38.216144],[-90.349706,38.374975],[-90.179921,38.632391],[-90.207305,38.725499],[-90.10872,38.845992],[-90.251121,38.917192],[-90.470199,38.961007],[-90.585214,38.867899],[-90.661891,38.928146],[-90.727615,39.256762],[-91.061708,39.470363],[-91.368417,39.727779],[-91.494386,40.034488],[-91.50534,40.237135],[-91.417709,40.379535],[-91.401278,40.560274],[-91.121954,40.669813],[-91.09457,40.823167],[-90.963123,40.921752],[-90.946692,41.097014],[-91.111001,41.239415],[-91.045277,41.414677],[-90.656414,41.463969],[-90.344229,41.589939],[-90.311367,41.743293],[-90.179921,41.809016],[-90.141582,42.000709],[-90.168967,42.126679],[-90.393521,42.225264],[-90.420906,42.329326],[-90.639984,42.510065]]]}}, +{"type":"Feature","id":"18","properties":{"name":"Indiana","density":181.7},"geometry":{"type":"Polygon","coordinates":[[[-85.990061,41.759724],[-84.807042,41.759724],[-84.807042,41.694001],[-84.801565,40.500028],[-84.817996,39.103408],[-84.894673,39.059592],[-84.812519,38.785745],[-84.987781,38.780268],[-85.173997,38.68716],[-85.431413,38.730976],[-85.42046,38.533806],[-85.590245,38.451652],[-85.655968,38.325682],[-85.83123,38.27639],[-85.924338,38.024451],[-86.039354,37.958727],[-86.263908,38.051835],[-86.302247,38.166851],[-86.521325,38.040881],[-86.504894,37.931343],[-86.729448,37.893004],[-86.795172,37.991589],[-87.047111,37.893004],[-87.129265,37.788942],[-87.381204,37.93682],[-87.512651,37.903958],[-87.600282,37.975158],[-87.682436,37.903958],[-87.934375,37.893004],[-88.027483,37.799896],[-88.060345,37.865619],[-88.000098,38.101128],[-87.923421,38.15042],[-87.950806,38.27639],[-87.83579,38.292821],[-87.655051,38.506421],[-87.62219,38.637868],[-87.49622,38.780268],[-87.512651,38.95553],[-87.63862,39.169131],[-87.529082,39.34987],[-87.523605,41.710431],[-87.42502,41.644708],[-87.118311,41.644708],[-86.822556,41.759724],[-85.990061,41.759724]]]}}, +{"type":"Feature","id":"19","properties":{"name":"Iowa","density":54.81},"geometry":{"type":"Polygon","coordinates":[[[-91.368417,43.501391],[-91.215062,43.501391],[-91.204109,43.353514],[-91.056231,43.254929],[-91.176724,43.134436],[-91.143862,42.909881],[-91.067185,42.75105],[-90.711184,42.636034],[-90.639984,42.510065],[-90.420906,42.329326],[-90.393521,42.225264],[-90.168967,42.126679],[-90.141582,42.000709],[-90.179921,41.809016],[-90.311367,41.743293],[-90.344229,41.589939],[-90.656414,41.463969],[-91.045277,41.414677],[-91.111001,41.239415],[-90.946692,41.097014],[-90.963123,40.921752],[-91.09457,40.823167],[-91.121954,40.669813],[-91.401278,40.560274],[-91.417709,40.379535],[-91.527248,40.412397],[-91.729895,40.615043],[-91.833957,40.609566],[-93.257961,40.582182],[-94.632673,40.571228],[-95.7664,40.587659],[-95.881416,40.719105],[-95.826646,40.976521],[-95.925231,41.201076],[-95.919754,41.453015],[-96.095016,41.540646],[-96.122401,41.67757],[-96.062155,41.798063],[-96.127878,41.973325],[-96.264801,42.039048],[-96.44554,42.488157],[-96.631756,42.707235],[-96.544125,42.855112],[-96.511264,43.052282],[-96.434587,43.123482],[-96.560556,43.222067],[-96.527695,43.397329],[-96.582464,43.479483],[-96.451017,43.501391],[-91.368417,43.501391]]]}}, +{"type":"Feature","id":"20","properties":{"name":"Kansas","density":35.09},"geometry":{"type":"Polygon","coordinates":[[[-101.90605,40.001626],[-95.306337,40.001626],[-95.207752,39.908518],[-94.884612,39.831841],[-95.109167,39.541563],[-94.983197,39.442978],[-94.824366,39.20747],[-94.610765,39.158177],[-94.616242,37.000263],[-100.087706,37.000263],[-102.042974,36.994786],[-102.053927,40.001626],[-101.90605,40.001626]]]}}, +{"type":"Feature","id":"21","properties":{"name":"Kentucky","density":110},"geometry":{"type":"Polygon","coordinates":[[[-83.903347,38.769315],[-83.678792,38.632391],[-83.519961,38.703591],[-83.142052,38.626914],[-83.032514,38.725499],[-82.890113,38.758361],[-82.846298,38.588575],[-82.731282,38.561191],[-82.594358,38.424267],[-82.621743,38.123036],[-82.50125,37.931343],[-82.342419,37.783465],[-82.293127,37.668449],[-82.101434,37.553434],[-81.969987,37.537003],[-82.353373,37.268633],[-82.720328,37.120755],[-82.720328,37.044078],[-82.868205,36.978355],[-82.879159,36.890724],[-83.070852,36.852385],[-83.136575,36.742847],[-83.673316,36.600446],[-83.689746,36.584015],[-84.544149,36.594969],[-85.289013,36.627831],[-85.486183,36.616877],[-86.592525,36.655216],[-87.852221,36.633308],[-88.071299,36.677123],[-88.054868,36.496384],[-89.298133,36.507338],[-89.418626,36.496384],[-89.363857,36.622354],[-89.215979,36.578538],[-89.133825,36.983832],[-89.183118,37.038601],[-89.029763,37.213863],[-88.914747,37.224817],[-88.547792,37.071463],[-88.421823,37.153617],[-88.514931,37.285064],[-88.476592,37.389126],[-88.065822,37.482234],[-88.15893,37.657496],[-88.027483,37.799896],[-87.934375,37.893004],[-87.682436,37.903958],[-87.600282,37.975158],[-87.512651,37.903958],[-87.381204,37.93682],[-87.129265,37.788942],[-87.047111,37.893004],[-86.795172,37.991589],[-86.729448,37.893004],[-86.504894,37.931343],[-86.521325,38.040881],[-86.302247,38.166851],[-86.263908,38.051835],[-86.039354,37.958727],[-85.924338,38.024451],[-85.83123,38.27639],[-85.655968,38.325682],[-85.590245,38.451652],[-85.42046,38.533806],[-85.431413,38.730976],[-85.173997,38.68716],[-84.987781,38.780268],[-84.812519,38.785745],[-84.894673,39.059592],[-84.817996,39.103408],[-84.43461,39.103408],[-84.231963,38.895284],[-84.215533,38.807653],[-83.903347,38.769315]]]}}, +{"type":"Feature","id":"22","properties":{"name":"Louisiana","density":105},"geometry":{"type":"Polygon","coordinates":[[[-93.608485,33.018527],[-91.16577,33.002096],[-91.072662,32.887081],[-91.143862,32.843265],[-91.154816,32.640618],[-91.006939,32.514649],[-90.985031,32.218894],[-91.105524,31.988862],[-91.341032,31.846462],[-91.401278,31.621907],[-91.499863,31.643815],[-91.516294,31.27686],[-91.636787,31.265906],[-91.565587,31.068736],[-91.636787,30.997536],[-89.747242,30.997536],[-89.845827,30.66892],[-89.681519,30.449842],[-89.643181,30.285534],[-89.522688,30.181472],[-89.818443,30.044549],[-89.84035,29.945964],[-89.599365,29.88024],[-89.495303,30.039072],[-89.287179,29.88024],[-89.30361,29.754271],[-89.424103,29.699501],[-89.648657,29.748794],[-89.621273,29.655686],[-89.69795,29.513285],[-89.506257,29.387316],[-89.199548,29.348977],[-89.09001,29.2011],[-89.002379,29.179192],[-89.16121,29.009407],[-89.336472,29.042268],[-89.484349,29.217531],[-89.851304,29.310638],[-89.851304,29.480424],[-90.032043,29.425654],[-90.021089,29.283254],[-90.103244,29.151807],[-90.23469,29.129899],[-90.333275,29.277777],[-90.563307,29.283254],[-90.645461,29.129899],[-90.798815,29.086084],[-90.963123,29.179192],[-91.09457,29.190146],[-91.220539,29.436608],[-91.445094,29.546147],[-91.532725,29.529716],[-91.620356,29.73784],[-91.883249,29.710455],[-91.888726,29.836425],[-92.146142,29.715932],[-92.113281,29.622824],[-92.31045,29.535193],[-92.617159,29.579009],[-92.97316,29.715932],[-93.2251,29.776178],[-93.767317,29.726886],[-93.838517,29.688547],[-93.926148,29.787132],[-93.690639,30.143133],[-93.767317,30.334826],[-93.696116,30.438888],[-93.728978,30.575812],[-93.630393,30.679874],[-93.526331,30.93729],[-93.542762,31.15089],[-93.816609,31.556184],[-93.822086,31.775262],[-94.041164,31.994339],[-94.041164,33.018527],[-93.608485,33.018527]]]}}, +{"type":"Feature","id":"23","properties":{"name":"Maine","density":43.04},"geometry":{"type":"Polygon","coordinates":[[[-70.703921,43.057759],[-70.824413,43.128959],[-70.807983,43.227544],[-70.966814,43.34256],[-71.032537,44.657025],[-71.08183,45.303304],[-70.649151,45.440228],[-70.720352,45.511428],[-70.556043,45.664782],[-70.386258,45.735983],[-70.41912,45.796229],[-70.260289,45.889337],[-70.309581,46.064599],[-70.210996,46.327492],[-70.057642,46.415123],[-69.997395,46.694447],[-69.225147,47.461219],[-69.044408,47.428357],[-69.033454,47.242141],[-68.902007,47.176418],[-68.578868,47.285957],[-68.376221,47.285957],[-68.233821,47.357157],[-67.954497,47.198326],[-67.790188,47.066879],[-67.779235,45.944106],[-67.801142,45.675736],[-67.456095,45.604536],[-67.505388,45.48952],[-67.417757,45.379982],[-67.488957,45.281397],[-67.346556,45.128042],[-67.16034,45.160904],[-66.979601,44.804903],[-67.187725,44.646072],[-67.308218,44.706318],[-67.406803,44.596779],[-67.549203,44.624164],[-67.565634,44.531056],[-67.75185,44.54201],[-68.047605,44.328409],[-68.118805,44.476286],[-68.222867,44.48724],[-68.173574,44.328409],[-68.403606,44.251732],[-68.458375,44.377701],[-68.567914,44.311978],[-68.82533,44.311978],[-68.830807,44.459856],[-68.984161,44.426994],[-68.956777,44.322932],[-69.099177,44.103854],[-69.071793,44.043608],[-69.258008,43.923115],[-69.444224,43.966931],[-69.553763,43.840961],[-69.707118,43.82453],[-69.833087,43.720469],[-69.986442,43.742376],[-70.030257,43.851915],[-70.254812,43.676653],[-70.194565,43.567114],[-70.358873,43.528776],[-70.369827,43.435668],[-70.556043,43.320652],[-70.703921,43.057759]]]}}, +{"type":"Feature","id":"24","properties":{"name":"Maryland","density":596.3},"geometry":{"type":"MultiPolygon","coordinates":[[[[-75.994645,37.95325],[-76.016553,37.95325],[-76.043938,37.95325],[-75.994645,37.95325]]],[[[-79.477979,39.722302],[-75.786521,39.722302],[-75.693413,38.462606],[-75.047134,38.451652],[-75.244304,38.029928],[-75.397659,38.013497],[-75.671506,37.95325],[-75.885106,37.909435],[-75.879629,38.073743],[-75.961783,38.139466],[-75.846768,38.210667],[-76.000122,38.374975],[-76.049415,38.303775],[-76.257538,38.320205],[-76.328738,38.500944],[-76.263015,38.500944],[-76.257538,38.736453],[-76.191815,38.829561],[-76.279446,39.147223],[-76.169907,39.333439],[-76.000122,39.366301],[-75.972737,39.557994],[-76.098707,39.536086],[-76.104184,39.437501],[-76.367077,39.311532],[-76.443754,39.196516],[-76.460185,38.906238],[-76.55877,38.769315],[-76.514954,38.539283],[-76.383508,38.380452],[-76.399939,38.259959],[-76.317785,38.139466],[-76.3616,38.057312],[-76.591632,38.216144],[-76.920248,38.292821],[-77.018833,38.446175],[-77.205049,38.358544],[-77.276249,38.479037],[-77.128372,38.632391],[-77.040741,38.791222],[-76.909294,38.895284],[-77.035264,38.993869],[-77.117418,38.933623],[-77.248864,39.026731],[-77.456988,39.076023],[-77.456988,39.223901],[-77.566527,39.306055],[-77.719881,39.322485],[-77.834897,39.601809],[-78.004682,39.601809],[-78.174467,39.694917],[-78.267575,39.61824],[-78.431884,39.623717],[-78.470222,39.514178],[-78.765977,39.585379],[-78.963147,39.437501],[-79.094593,39.470363],[-79.291763,39.300578],[-79.488933,39.20747],[-79.477979,39.722302]]]]}}, +{"type":"Feature","id":"25","properties":{"name":"Massachusetts","density":840.2},"geometry":{"type":"Polygon","coordinates":[[[-70.917521,42.887974],[-70.818936,42.871543],[-70.780598,42.696281],[-70.824413,42.55388],[-70.983245,42.422434],[-70.988722,42.269079],[-70.769644,42.247172],[-70.638197,42.08834],[-70.660105,41.962371],[-70.550566,41.929509],[-70.539613,41.814493],[-70.260289,41.715908],[-69.937149,41.809016],[-70.008349,41.672093],[-70.484843,41.5516],[-70.660105,41.546123],[-70.764167,41.639231],[-70.928475,41.611847],[-70.933952,41.540646],[-71.120168,41.496831],[-71.196845,41.67757],[-71.22423,41.710431],[-71.328292,41.781632],[-71.383061,42.01714],[-71.530939,42.01714],[-71.799309,42.006186],[-71.799309,42.022617],[-73.053528,42.039048],[-73.486206,42.050002],[-73.508114,42.08834],[-73.267129,42.745573],[-72.456542,42.729142],[-71.29543,42.696281],[-71.185891,42.789389],[-70.917521,42.887974]]]}}, +{"type":"Feature","id":"26","properties":{"name":"Michigan","density":173.9},"geometry":{"type":"MultiPolygon","coordinates":[[[[-83.454238,41.732339],[-84.807042,41.694001],[-84.807042,41.759724],[-85.990061,41.759724],[-86.822556,41.759724],[-86.619909,41.891171],[-86.482986,42.115725],[-86.357016,42.252649],[-86.263908,42.444341],[-86.209139,42.718189],[-86.231047,43.013943],[-86.526801,43.594499],[-86.433693,43.813577],[-86.499417,44.07647],[-86.269385,44.34484],[-86.220093,44.569394],[-86.252954,44.689887],[-86.088646,44.73918],[-86.066738,44.903488],[-85.809322,44.947303],[-85.612152,45.128042],[-85.628583,44.766564],[-85.524521,44.750133],[-85.393075,44.930872],[-85.387598,45.237581],[-85.305444,45.314258],[-85.031597,45.363551],[-85.119228,45.577151],[-84.938489,45.75789],[-84.713934,45.768844],[-84.461995,45.653829],[-84.215533,45.637398],[-84.09504,45.494997],[-83.908824,45.484043],[-83.596638,45.352597],[-83.4871,45.358074],[-83.317314,45.144473],[-83.454238,45.029457],[-83.322791,44.88158],[-83.273499,44.711795],[-83.333745,44.339363],[-83.536392,44.246255],[-83.585684,44.054562],[-83.82667,43.988839],[-83.958116,43.758807],[-83.908824,43.671176],[-83.667839,43.589022],[-83.481623,43.714992],[-83.262545,43.972408],[-82.917498,44.070993],[-82.747713,43.994316],[-82.643651,43.851915],[-82.539589,43.435668],[-82.523158,43.227544],[-82.413619,42.975605],[-82.517681,42.614127],[-82.681989,42.559357],[-82.687466,42.690804],[-82.797005,42.652465],[-82.922975,42.351234],[-83.125621,42.236218],[-83.185868,42.006186],[-83.437807,41.814493],[-83.454238,41.732339]]],[[[-85.508091,45.730506],[-85.49166,45.610013],[-85.623106,45.588105],[-85.568337,45.75789],[-85.508091,45.730506]]],[[[-87.589328,45.095181],[-87.742682,45.199243],[-87.649574,45.341643],[-87.885083,45.363551],[-87.791975,45.500474],[-87.781021,45.675736],[-87.989145,45.796229],[-88.10416,45.922199],[-88.531362,46.020784],[-88.662808,45.987922],[-89.09001,46.135799],[-90.119674,46.338446],[-90.229213,46.508231],[-90.415429,46.568478],[-90.026566,46.672539],[-89.851304,46.793032],[-89.413149,46.842325],[-89.128348,46.990202],[-88.996902,46.995679],[-88.887363,47.099741],[-88.575177,47.247618],[-88.416346,47.373588],[-88.180837,47.455742],[-87.956283,47.384542],[-88.350623,47.077833],[-88.443731,46.973771],[-88.438254,46.787555],[-88.246561,46.929956],[-87.901513,46.908048],[-87.633143,46.809463],[-87.392158,46.535616],[-87.260711,46.486323],[-87.008772,46.530139],[-86.948526,46.469893],[-86.696587,46.437031],[-86.159846,46.667063],[-85.880522,46.68897],[-85.508091,46.678016],[-85.256151,46.754694],[-85.064458,46.760171],[-85.02612,46.480847],[-84.82895,46.442508],[-84.63178,46.486323],[-84.549626,46.4206],[-84.418179,46.502754],[-84.127902,46.530139],[-84.122425,46.179615],[-83.990978,46.031737],[-83.793808,45.993399],[-83.7719,46.091984],[-83.580208,46.091984],[-83.476146,45.987922],[-83.563777,45.911245],[-84.111471,45.976968],[-84.374364,45.933153],[-84.659165,46.053645],[-84.741319,45.944106],[-84.70298,45.850998],[-84.82895,45.872906],[-85.015166,46.00983],[-85.338305,46.091984],[-85.502614,46.097461],[-85.661445,45.966014],[-85.924338,45.933153],[-86.209139,45.960537],[-86.324155,45.905768],[-86.351539,45.796229],[-86.663725,45.703121],[-86.647294,45.834568],[-86.784218,45.861952],[-86.838987,45.725029],[-87.069019,45.719552],[-87.17308,45.659305],[-87.326435,45.423797],[-87.611236,45.122565],[-87.589328,45.095181]]],[[[-88.805209,47.976051],[-89.057148,47.850082],[-89.188594,47.833651],[-89.177641,47.937713],[-88.547792,48.173221],[-88.668285,48.008913],[-88.805209,47.976051]]]]}}, +{"type":"Feature","id":"27","properties":{"name":"Minnesota","density":67.14},"geometry":{"type":"Polygon","coordinates":[[[-92.014696,46.705401],[-92.091373,46.749217],[-92.29402,46.667063],[-92.29402,46.075553],[-92.354266,46.015307],[-92.639067,45.933153],[-92.869098,45.719552],[-92.885529,45.577151],[-92.770513,45.566198],[-92.644544,45.440228],[-92.75956,45.286874],[-92.737652,45.117088],[-92.808852,44.750133],[-92.545959,44.569394],[-92.337835,44.552964],[-92.233773,44.443425],[-91.927065,44.333886],[-91.877772,44.202439],[-91.592971,44.032654],[-91.43414,43.994316],[-91.242447,43.775238],[-91.269832,43.616407],[-91.215062,43.501391],[-91.368417,43.501391],[-96.451017,43.501391],[-96.451017,45.297827],[-96.681049,45.412843],[-96.856311,45.604536],[-96.582464,45.818137],[-96.560556,45.933153],[-96.598895,46.332969],[-96.719387,46.437031],[-96.801542,46.656109],[-96.785111,46.924479],[-96.823449,46.968294],[-96.856311,47.609096],[-97.053481,47.948667],[-97.130158,48.140359],[-97.16302,48.545653],[-97.097296,48.682577],[-97.228743,49.000239],[-95.152983,49.000239],[-95.152983,49.383625],[-94.955813,49.372671],[-94.824366,49.295994],[-94.69292,48.775685],[-94.588858,48.715438],[-94.260241,48.699007],[-94.221903,48.649715],[-93.838517,48.627807],[-93.794701,48.518268],[-93.466085,48.545653],[-93.466085,48.589469],[-93.208669,48.644238],[-92.984114,48.62233],[-92.726698,48.540176],[-92.655498,48.436114],[-92.50762,48.447068],[-92.370697,48.222514],[-92.304974,48.315622],[-92.053034,48.359437],[-92.009219,48.266329],[-91.713464,48.200606],[-91.713464,48.112975],[-91.565587,48.041775],[-91.264355,48.080113],[-91.083616,48.178698],[-90.837154,48.238944],[-90.749522,48.091067],[-90.579737,48.123929],[-90.377091,48.091067],[-90.141582,48.112975],[-89.873212,47.987005],[-89.615796,48.008913],[-89.637704,47.954144],[-89.971797,47.828174],[-90.437337,47.729589],[-90.738569,47.625527],[-91.171247,47.368111],[-91.357463,47.20928],[-91.642264,47.028541],[-92.091373,46.787555],[-92.014696,46.705401]]]}}, +{"type":"Feature","id":"28","properties":{"name":"Mississippi","density":63.50},"geometry":{"type":"Polygon","coordinates":[[[-88.471115,34.995703],[-88.202745,34.995703],[-88.098683,34.891641],[-88.241084,33.796253],[-88.471115,31.895754],[-88.394438,30.367688],[-88.503977,30.323872],[-88.744962,30.34578],[-88.843547,30.411504],[-89.084533,30.367688],[-89.418626,30.252672],[-89.522688,30.181472],[-89.643181,30.285534],[-89.681519,30.449842],[-89.845827,30.66892],[-89.747242,30.997536],[-91.636787,30.997536],[-91.565587,31.068736],[-91.636787,31.265906],[-91.516294,31.27686],[-91.499863,31.643815],[-91.401278,31.621907],[-91.341032,31.846462],[-91.105524,31.988862],[-90.985031,32.218894],[-91.006939,32.514649],[-91.154816,32.640618],[-91.143862,32.843265],[-91.072662,32.887081],[-91.16577,33.002096],[-91.089093,33.13902],[-91.143862,33.347144],[-91.056231,33.429298],[-91.231493,33.560744],[-91.072662,33.867453],[-90.891923,34.026284],[-90.952169,34.135823],[-90.744046,34.300131],[-90.749522,34.365854],[-90.568783,34.420624],[-90.585214,34.617794],[-90.481152,34.661609],[-90.409952,34.831394],[-90.251121,34.908072],[-90.311367,34.995703],[-88.471115,34.995703]]]}}, +{"type":"Feature","id":"29","properties":{"name":"Missouri","density":87.26},"geometry":{"type":"Polygon","coordinates":[[[-91.833957,40.609566],[-91.729895,40.615043],[-91.527248,40.412397],[-91.417709,40.379535],[-91.50534,40.237135],[-91.494386,40.034488],[-91.368417,39.727779],[-91.061708,39.470363],[-90.727615,39.256762],[-90.661891,38.928146],[-90.585214,38.867899],[-90.470199,38.961007],[-90.251121,38.917192],[-90.10872,38.845992],[-90.207305,38.725499],[-90.179921,38.632391],[-90.349706,38.374975],[-90.355183,38.216144],[-90.059428,38.013497],[-89.949889,37.88205],[-89.84035,37.903958],[-89.517211,37.690357],[-89.517211,37.537003],[-89.435057,37.34531],[-89.517211,37.279587],[-89.292656,36.994786],[-89.133825,36.983832],[-89.215979,36.578538],[-89.363857,36.622354],[-89.418626,36.496384],[-89.484349,36.496384],[-89.539119,36.496384],[-89.533642,36.249922],[-89.730812,35.997983],[-90.377091,35.997983],[-90.218259,36.184199],[-90.064905,36.304691],[-90.152536,36.496384],[-94.473842,36.501861],[-94.616242,36.501861],[-94.616242,37.000263],[-94.610765,39.158177],[-94.824366,39.20747],[-94.983197,39.442978],[-95.109167,39.541563],[-94.884612,39.831841],[-95.207752,39.908518],[-95.306337,40.001626],[-95.552799,40.264519],[-95.7664,40.587659],[-94.632673,40.571228],[-93.257961,40.582182],[-91.833957,40.609566]]]}}, +{"type":"Feature","id":"30","properties":{"name":"Montana","density":6.858},"geometry":{"type":"Polygon","coordinates":[[[-104.047534,49.000239],[-104.042057,47.861036],[-104.047534,45.944106],[-104.042057,44.996596],[-104.058488,44.996596],[-105.91517,45.002073],[-109.080842,45.002073],[-111.05254,45.002073],[-111.047063,44.476286],[-111.227803,44.580348],[-111.386634,44.75561],[-111.616665,44.547487],[-111.819312,44.509148],[-111.868605,44.563917],[-112.104113,44.520102],[-112.241036,44.569394],[-112.471068,44.481763],[-112.783254,44.48724],[-112.887315,44.394132],[-113.002331,44.448902],[-113.133778,44.772041],[-113.341901,44.782995],[-113.456917,44.865149],[-113.45144,45.056842],[-113.571933,45.128042],[-113.736241,45.330689],[-113.834826,45.522382],[-113.807441,45.604536],[-113.98818,45.703121],[-114.086765,45.593582],[-114.333228,45.456659],[-114.546828,45.560721],[-114.497536,45.670259],[-114.568736,45.774321],[-114.387997,45.88386],[-114.492059,46.037214],[-114.464674,46.272723],[-114.322274,46.645155],[-114.612552,46.639678],[-114.623506,46.705401],[-114.886399,46.809463],[-114.930214,46.919002],[-115.302646,47.187372],[-115.324554,47.258572],[-115.527201,47.302388],[-115.718894,47.42288],[-115.724371,47.696727],[-116.04751,47.976051],[-116.04751,49.000239],[-111.50165,48.994762],[-109.453274,49.000239],[-104.047534,49.000239]]]}}, +{"type":"Feature","id":"31","properties":{"name":"Nebraska","density":23.97},"geometry":{"type":"Polygon","coordinates":[[[-103.324578,43.002989],[-101.626726,42.997512],[-98.499393,42.997512],[-98.466531,42.94822],[-97.951699,42.767481],[-97.831206,42.866066],[-97.688806,42.844158],[-97.217789,42.844158],[-96.692003,42.657942],[-96.626279,42.515542],[-96.44554,42.488157],[-96.264801,42.039048],[-96.127878,41.973325],[-96.062155,41.798063],[-96.122401,41.67757],[-96.095016,41.540646],[-95.919754,41.453015],[-95.925231,41.201076],[-95.826646,40.976521],[-95.881416,40.719105],[-95.7664,40.587659],[-95.552799,40.264519],[-95.306337,40.001626],[-101.90605,40.001626],[-102.053927,40.001626],[-102.053927,41.003906],[-104.053011,41.003906],[-104.053011,43.002989],[-103.324578,43.002989]]]}}, +{"type":"Feature","id":"32","properties":{"name":"Nevada","density":24.80},"geometry":{"type":"Polygon","coordinates":[[[-117.027882,42.000709],[-114.04295,41.995232],[-114.048427,37.000263],[-114.048427,36.195153],[-114.152489,36.025367],[-114.251074,36.01989],[-114.371566,36.140383],[-114.738521,36.102045],[-114.678275,35.516012],[-114.596121,35.324319],[-114.574213,35.138103],[-114.634459,35.00118],[-115.85034,35.970598],[-116.540435,36.501861],[-117.498899,37.21934],[-118.71478,38.101128],[-120.001861,38.999346],[-119.996384,40.264519],[-120.001861,41.995232],[-118.698349,41.989755],[-117.027882,42.000709]]]}}, +{"type":"Feature","id":"33","properties":{"name":"New Hampshire","density":147},"geometry":{"type":"Polygon","coordinates":[[[-71.08183,45.303304],[-71.032537,44.657025],[-70.966814,43.34256],[-70.807983,43.227544],[-70.824413,43.128959],[-70.703921,43.057759],[-70.818936,42.871543],[-70.917521,42.887974],[-71.185891,42.789389],[-71.29543,42.696281],[-72.456542,42.729142],[-72.544173,42.80582],[-72.533219,42.953697],[-72.445588,43.008466],[-72.456542,43.150867],[-72.379864,43.572591],[-72.204602,43.769761],[-72.116971,43.994316],[-72.02934,44.07647],[-72.034817,44.322932],[-71.700724,44.41604],[-71.536416,44.585825],[-71.629524,44.750133],[-71.4926,44.914442],[-71.503554,45.013027],[-71.361154,45.270443],[-71.131122,45.243058],[-71.08183,45.303304]]]}}, +{"type":"Feature","id":"34","properties":{"name":"New Jersey","density":1189 },"geometry":{"type":"Polygon","coordinates":[[[-74.236547,41.14083],[-73.902454,40.998429],[-74.022947,40.708151],[-74.187255,40.642428],[-74.274886,40.489074],[-74.001039,40.412397],[-73.979131,40.297381],[-74.099624,39.760641],[-74.411809,39.360824],[-74.614456,39.245808],[-74.795195,38.993869],[-74.888303,39.158177],[-75.178581,39.240331],[-75.534582,39.459409],[-75.55649,39.607286],[-75.561967,39.629194],[-75.507197,39.683964],[-75.414089,39.804456],[-75.145719,39.88661],[-75.129289,39.963288],[-74.82258,40.127596],[-74.773287,40.215227],[-75.058088,40.417874],[-75.069042,40.543843],[-75.195012,40.576705],[-75.205966,40.691721],[-75.052611,40.866983],[-75.134765,40.971045],[-74.882826,41.179168],[-74.828057,41.288707],[-74.69661,41.359907],[-74.236547,41.14083]]]}}, +{"type":"Feature","id":"35","properties":{"name":"New Mexico","density":17.16},"geometry":{"type":"Polygon","coordinates":[[[-107.421329,37.000263],[-106.868158,36.994786],[-104.337812,36.994786],[-103.001438,37.000263],[-103.001438,36.501861],[-103.039777,36.501861],[-103.045254,34.01533],[-103.067161,33.002096],[-103.067161,31.999816],[-106.616219,31.999816],[-106.643603,31.901231],[-106.528588,31.786216],[-108.210008,31.786216],[-108.210008,31.331629],[-109.04798,31.331629],[-109.042503,37.000263],[-107.421329,37.000263]]]}}, +{"type":"Feature","id":"36","properties":{"name":"New York","density":412.3},"geometry":{"type":"Polygon","coordinates":[[[-73.343806,45.013027],[-73.332852,44.804903],[-73.387622,44.618687],[-73.294514,44.437948],[-73.321898,44.246255],[-73.436914,44.043608],[-73.349283,43.769761],[-73.404052,43.687607],[-73.245221,43.523299],[-73.278083,42.833204],[-73.267129,42.745573],[-73.508114,42.08834],[-73.486206,42.050002],[-73.55193,41.294184],[-73.48073,41.21203],[-73.727192,41.102491],[-73.655992,40.987475],[-73.22879,40.905321],[-73.141159,40.965568],[-72.774204,40.965568],[-72.587988,40.998429],[-72.28128,41.157261],[-72.259372,41.042245],[-72.100541,40.992952],[-72.467496,40.845075],[-73.239744,40.625997],[-73.562884,40.582182],[-73.776484,40.593136],[-73.935316,40.543843],[-74.022947,40.708151],[-73.902454,40.998429],[-74.236547,41.14083],[-74.69661,41.359907],[-74.740426,41.431108],[-74.89378,41.436584],[-75.074519,41.60637],[-75.052611,41.754247],[-75.173104,41.869263],[-75.249781,41.863786],[-75.35932,42.000709],[-79.76278,42.000709],[-79.76278,42.252649],[-79.76278,42.269079],[-79.149363,42.55388],[-79.050778,42.690804],[-78.853608,42.783912],[-78.930285,42.953697],[-79.012439,42.986559],[-79.072686,43.260406],[-78.486653,43.375421],[-77.966344,43.369944],[-77.75822,43.34256],[-77.533665,43.233021],[-77.391265,43.276836],[-76.958587,43.271359],[-76.695693,43.34256],[-76.41637,43.523299],[-76.235631,43.528776],[-76.230154,43.802623],[-76.137046,43.961454],[-76.3616,44.070993],[-76.312308,44.196962],[-75.912491,44.366748],[-75.764614,44.514625],[-75.282643,44.848718],[-74.828057,45.018503],[-74.148916,44.991119],[-73.343806,45.013027]]]}}, +{"type":"Feature","id":"37","properties":{"name":"North Carolina","density":198.2},"geometry":{"type":"Polygon","coordinates":[[[-80.978661,36.562108],[-80.294043,36.545677],[-79.510841,36.5402],[-75.868676,36.551154],[-75.75366,36.151337],[-76.032984,36.189676],[-76.071322,36.140383],[-76.410893,36.080137],[-76.460185,36.025367],[-76.68474,36.008937],[-76.673786,35.937736],[-76.399939,35.987029],[-76.3616,35.943213],[-76.060368,35.992506],[-75.961783,35.899398],[-75.781044,35.937736],[-75.715321,35.696751],[-75.775568,35.581735],[-75.89606,35.570781],[-76.147999,35.324319],[-76.482093,35.313365],[-76.536862,35.14358],[-76.394462,34.973795],[-76.279446,34.940933],[-76.493047,34.661609],[-76.673786,34.694471],[-76.991448,34.667086],[-77.210526,34.60684],[-77.555573,34.415147],[-77.82942,34.163208],[-77.971821,33.845545],[-78.179944,33.916745],[-78.541422,33.851022],[-79.675149,34.80401],[-80.797922,34.820441],[-80.781491,34.935456],[-80.934845,35.105241],[-81.038907,35.044995],[-81.044384,35.149057],[-82.276696,35.198349],[-82.550543,35.160011],[-82.764143,35.066903],[-83.109191,35.00118],[-83.618546,34.984749],[-84.319594,34.990226],[-84.29221,35.225734],[-84.09504,35.247642],[-84.018363,35.41195],[-83.7719,35.559827],[-83.498053,35.565304],[-83.251591,35.718659],[-82.994175,35.773428],[-82.775097,35.997983],[-82.638174,36.063706],[-82.610789,35.965121],[-82.216449,36.156814],[-82.03571,36.118475],[-81.909741,36.304691],[-81.723525,36.353984],[-81.679709,36.589492],[-80.978661,36.562108]]]}}, +{"type":"Feature","id":"38","properties":{"name":"North Dakota","density":9.916},"geometry":{"type":"Polygon","coordinates":[[[-97.228743,49.000239],[-97.097296,48.682577],[-97.16302,48.545653],[-97.130158,48.140359],[-97.053481,47.948667],[-96.856311,47.609096],[-96.823449,46.968294],[-96.785111,46.924479],[-96.801542,46.656109],[-96.719387,46.437031],[-96.598895,46.332969],[-96.560556,45.933153],[-104.047534,45.944106],[-104.042057,47.861036],[-104.047534,49.000239],[-97.228743,49.000239]]]}}, +{"type":"Feature","id":"39","properties":{"name":"Ohio","density":281.9},"geometry":{"type":"Polygon","coordinates":[[[-80.518598,41.978802],[-80.518598,40.636951],[-80.666475,40.582182],[-80.595275,40.472643],[-80.600752,40.319289],[-80.737675,40.078303],[-80.830783,39.711348],[-81.219646,39.388209],[-81.345616,39.344393],[-81.455155,39.410117],[-81.57017,39.267716],[-81.685186,39.273193],[-81.811156,39.0815],[-81.783771,38.966484],[-81.887833,38.873376],[-82.03571,39.026731],[-82.221926,38.785745],[-82.172634,38.632391],[-82.293127,38.577622],[-82.331465,38.446175],[-82.594358,38.424267],[-82.731282,38.561191],[-82.846298,38.588575],[-82.890113,38.758361],[-83.032514,38.725499],[-83.142052,38.626914],[-83.519961,38.703591],[-83.678792,38.632391],[-83.903347,38.769315],[-84.215533,38.807653],[-84.231963,38.895284],[-84.43461,39.103408],[-84.817996,39.103408],[-84.801565,40.500028],[-84.807042,41.694001],[-83.454238,41.732339],[-83.065375,41.595416],[-82.933929,41.513262],[-82.835344,41.589939],[-82.616266,41.431108],[-82.479343,41.381815],[-82.013803,41.513262],[-81.739956,41.485877],[-81.444201,41.672093],[-81.011523,41.852832],[-80.518598,41.978802],[-80.518598,41.978802]]]}}, +{"type":"Feature","id":"40","properties":{"name":"Oklahoma","density":55.22},"geometry":{"type":"Polygon","coordinates":[[[-100.087706,37.000263],[-94.616242,37.000263],[-94.616242,36.501861],[-94.430026,35.395519],[-94.484796,33.637421],[-94.868182,33.74696],[-94.966767,33.861976],[-95.224183,33.960561],[-95.289906,33.87293],[-95.547322,33.878407],[-95.602092,33.933176],[-95.8376,33.834591],[-95.936185,33.889361],[-96.149786,33.840068],[-96.346956,33.686714],[-96.423633,33.774345],[-96.631756,33.845545],[-96.850834,33.845545],[-96.922034,33.960561],[-97.173974,33.736006],[-97.256128,33.861976],[-97.371143,33.823637],[-97.458774,33.905791],[-97.694283,33.982469],[-97.869545,33.851022],[-97.946222,33.987946],[-98.088623,34.004376],[-98.170777,34.113915],[-98.36247,34.157731],[-98.488439,34.064623],[-98.570593,34.146777],[-98.767763,34.135823],[-98.986841,34.223454],[-99.189488,34.2125],[-99.260688,34.404193],[-99.57835,34.415147],[-99.698843,34.382285],[-99.923398,34.573978],[-100.000075,34.563024],[-100.000075,36.501861],[-101.812942,36.501861],[-103.001438,36.501861],[-103.001438,37.000263],[-102.042974,36.994786],[-100.087706,37.000263]]]}}, +{"type":"Feature","id":"41","properties":{"name":"Oregon","density":40.33},"geometry":{"type":"Polygon","coordinates":[[[-123.211348,46.174138],[-123.11824,46.185092],[-122.904639,46.08103],[-122.811531,45.960537],[-122.762239,45.659305],[-122.247407,45.549767],[-121.809251,45.708598],[-121.535404,45.725029],[-121.217742,45.670259],[-121.18488,45.604536],[-120.637186,45.746937],[-120.505739,45.697644],[-120.209985,45.725029],[-119.963522,45.823614],[-119.525367,45.911245],[-119.125551,45.933153],[-118.988627,45.998876],[-116.918344,45.993399],[-116.78142,45.823614],[-116.545912,45.752413],[-116.463758,45.61549],[-116.671881,45.319735],[-116.732128,45.144473],[-116.847143,45.02398],[-116.830713,44.930872],[-116.934774,44.782995],[-117.038836,44.750133],[-117.241483,44.394132],[-117.170283,44.257209],[-116.97859,44.240778],[-116.896436,44.158624],[-117.027882,43.830007],[-117.027882,42.000709],[-118.698349,41.989755],[-120.001861,41.995232],[-121.037003,41.995232],[-122.378853,42.011663],[-123.233256,42.006186],[-124.213628,42.000709],[-124.356029,42.115725],[-124.432706,42.438865],[-124.416275,42.663419],[-124.553198,42.838681],[-124.454613,43.002989],[-124.383413,43.271359],[-124.235536,43.55616],[-124.169813,43.8081],[-124.060274,44.657025],[-124.076705,44.772041],[-123.97812,45.144473],[-123.939781,45.659305],[-123.994551,45.944106],[-123.945258,46.113892],[-123.545441,46.261769],[-123.370179,46.146753],[-123.211348,46.174138]]]}}, +{"type":"Feature","id":"42","properties":{"name":"Pennsylvania","density":284.3},"geometry":{"type":"Polygon","coordinates":[[[-79.76278,42.252649],[-79.76278,42.000709],[-75.35932,42.000709],[-75.249781,41.863786],[-75.173104,41.869263],[-75.052611,41.754247],[-75.074519,41.60637],[-74.89378,41.436584],[-74.740426,41.431108],[-74.69661,41.359907],[-74.828057,41.288707],[-74.882826,41.179168],[-75.134765,40.971045],[-75.052611,40.866983],[-75.205966,40.691721],[-75.195012,40.576705],[-75.069042,40.543843],[-75.058088,40.417874],[-74.773287,40.215227],[-74.82258,40.127596],[-75.129289,39.963288],[-75.145719,39.88661],[-75.414089,39.804456],[-75.616736,39.831841],[-75.786521,39.722302],[-79.477979,39.722302],[-80.518598,39.722302],[-80.518598,40.636951],[-80.518598,41.978802],[-80.518598,41.978802],[-80.332382,42.033571],[-79.76278,42.269079],[-79.76278,42.252649]]]}}, +{"type":"Feature","id":"44","properties":{"name":"Rhode Island","density":1006 },"geometry":{"type":"MultiPolygon","coordinates":[[[[-71.196845,41.67757],[-71.120168,41.496831],[-71.317338,41.474923],[-71.196845,41.67757]]],[[[-71.530939,42.01714],[-71.383061,42.01714],[-71.328292,41.781632],[-71.22423,41.710431],[-71.344723,41.726862],[-71.448785,41.578985],[-71.481646,41.370861],[-71.859555,41.321569],[-71.799309,41.414677],[-71.799309,42.006186],[-71.530939,42.01714]]]]}}, +{"type":"Feature","id":"45","properties":{"name":"South Carolina","density":155.4},"geometry":{"type":"Polygon","coordinates":[[[-82.764143,35.066903],[-82.550543,35.160011],[-82.276696,35.198349],[-81.044384,35.149057],[-81.038907,35.044995],[-80.934845,35.105241],[-80.781491,34.935456],[-80.797922,34.820441],[-79.675149,34.80401],[-78.541422,33.851022],[-78.716684,33.80173],[-78.935762,33.637421],[-79.149363,33.380005],[-79.187701,33.171881],[-79.357487,33.007573],[-79.582041,33.007573],[-79.631334,32.887081],[-79.866842,32.755634],[-79.998289,32.613234],[-80.206412,32.552987],[-80.430967,32.399633],[-80.452875,32.328433],[-80.660998,32.246279],[-80.885553,32.032678],[-81.115584,32.120309],[-81.121061,32.290094],[-81.279893,32.558464],[-81.416816,32.629664],[-81.42777,32.843265],[-81.493493,33.007573],[-81.761863,33.160928],[-81.937125,33.347144],[-81.926172,33.462159],[-82.194542,33.631944],[-82.325988,33.81816],[-82.55602,33.94413],[-82.714851,34.152254],[-82.747713,34.26727],[-82.901067,34.486347],[-83.005129,34.469916],[-83.339222,34.683517],[-83.322791,34.787579],[-83.109191,35.00118],[-82.764143,35.066903]]]}}, +{"type":"Feature","id":"46","properties":{"name":"South Dakota","density":98.07},"geometry":{"type":"Polygon","coordinates":[[[-104.047534,45.944106],[-96.560556,45.933153],[-96.582464,45.818137],[-96.856311,45.604536],[-96.681049,45.412843],[-96.451017,45.297827],[-96.451017,43.501391],[-96.582464,43.479483],[-96.527695,43.397329],[-96.560556,43.222067],[-96.434587,43.123482],[-96.511264,43.052282],[-96.544125,42.855112],[-96.631756,42.707235],[-96.44554,42.488157],[-96.626279,42.515542],[-96.692003,42.657942],[-97.217789,42.844158],[-97.688806,42.844158],[-97.831206,42.866066],[-97.951699,42.767481],[-98.466531,42.94822],[-98.499393,42.997512],[-101.626726,42.997512],[-103.324578,43.002989],[-104.053011,43.002989],[-104.058488,44.996596],[-104.042057,44.996596],[-104.047534,45.944106]]]}}, +{"type":"Feature","id":"47","properties":{"name":"Tennessee","density":88.08},"geometry":{"type":"Polygon","coordinates":[[[-88.054868,36.496384],[-88.071299,36.677123],[-87.852221,36.633308],[-86.592525,36.655216],[-85.486183,36.616877],[-85.289013,36.627831],[-84.544149,36.594969],[-83.689746,36.584015],[-83.673316,36.600446],[-81.679709,36.589492],[-81.723525,36.353984],[-81.909741,36.304691],[-82.03571,36.118475],[-82.216449,36.156814],[-82.610789,35.965121],[-82.638174,36.063706],[-82.775097,35.997983],[-82.994175,35.773428],[-83.251591,35.718659],[-83.498053,35.565304],[-83.7719,35.559827],[-84.018363,35.41195],[-84.09504,35.247642],[-84.29221,35.225734],[-84.319594,34.990226],[-85.606675,34.984749],[-87.359296,35.00118],[-88.202745,34.995703],[-88.471115,34.995703],[-90.311367,34.995703],[-90.212782,35.023087],[-90.114197,35.198349],[-90.130628,35.439335],[-89.944412,35.603643],[-89.911551,35.756997],[-89.763673,35.811767],[-89.730812,35.997983],[-89.533642,36.249922],[-89.539119,36.496384],[-89.484349,36.496384],[-89.418626,36.496384],[-89.298133,36.507338],[-88.054868,36.496384]]]}}, +{"type":"Feature","id":"48","properties":{"name":"Texas","density":98.07},"geometry":{"type":"Polygon","coordinates":[[[-101.812942,36.501861],[-100.000075,36.501861],[-100.000075,34.563024],[-99.923398,34.573978],[-99.698843,34.382285],[-99.57835,34.415147],[-99.260688,34.404193],[-99.189488,34.2125],[-98.986841,34.223454],[-98.767763,34.135823],[-98.570593,34.146777],[-98.488439,34.064623],[-98.36247,34.157731],[-98.170777,34.113915],[-98.088623,34.004376],[-97.946222,33.987946],[-97.869545,33.851022],[-97.694283,33.982469],[-97.458774,33.905791],[-97.371143,33.823637],[-97.256128,33.861976],[-97.173974,33.736006],[-96.922034,33.960561],[-96.850834,33.845545],[-96.631756,33.845545],[-96.423633,33.774345],[-96.346956,33.686714],[-96.149786,33.840068],[-95.936185,33.889361],[-95.8376,33.834591],[-95.602092,33.933176],[-95.547322,33.878407],[-95.289906,33.87293],[-95.224183,33.960561],[-94.966767,33.861976],[-94.868182,33.74696],[-94.484796,33.637421],[-94.380734,33.544313],[-94.183564,33.593606],[-94.041164,33.54979],[-94.041164,33.018527],[-94.041164,31.994339],[-93.822086,31.775262],[-93.816609,31.556184],[-93.542762,31.15089],[-93.526331,30.93729],[-93.630393,30.679874],[-93.728978,30.575812],[-93.696116,30.438888],[-93.767317,30.334826],[-93.690639,30.143133],[-93.926148,29.787132],[-93.838517,29.688547],[-94.002825,29.68307],[-94.523134,29.546147],[-94.70935,29.622824],[-94.742212,29.787132],[-94.873659,29.672117],[-94.966767,29.699501],[-95.016059,29.557101],[-94.911997,29.496854],[-94.895566,29.310638],[-95.081782,29.113469],[-95.383014,28.867006],[-95.985477,28.604113],[-96.045724,28.647929],[-96.226463,28.582205],[-96.23194,28.642452],[-96.478402,28.598636],[-96.593418,28.724606],[-96.664618,28.697221],[-96.401725,28.439805],[-96.593418,28.357651],[-96.774157,28.406943],[-96.801542,28.226204],[-97.026096,28.039988],[-97.256128,27.694941],[-97.404005,27.333463],[-97.513544,27.360848],[-97.540929,27.229401],[-97.425913,27.262263],[-97.480682,26.99937],[-97.557359,26.988416],[-97.562836,26.840538],[-97.469728,26.758384],[-97.442344,26.457153],[-97.332805,26.353091],[-97.30542,26.161398],[-97.217789,25.991613],[-97.524498,25.887551],[-97.650467,26.018997],[-97.885976,26.06829],[-98.198161,26.057336],[-98.466531,26.221644],[-98.669178,26.238075],[-98.822533,26.369522],[-99.030656,26.413337],[-99.173057,26.539307],[-99.266165,26.840538],[-99.446904,27.021277],[-99.424996,27.174632],[-99.50715,27.33894],[-99.479765,27.48134],[-99.605735,27.640172],[-99.709797,27.656603],[-99.879582,27.799003],[-99.934351,27.979742],[-100.082229,28.14405],[-100.29583,28.280974],[-100.399891,28.582205],[-100.498476,28.66436],[-100.629923,28.905345],[-100.673738,29.102515],[-100.799708,29.244915],[-101.013309,29.370885],[-101.062601,29.458516],[-101.259771,29.535193],[-101.413125,29.754271],[-101.851281,29.803563],[-102.114174,29.792609],[-102.338728,29.869286],[-102.388021,29.765225],[-102.629006,29.732363],[-102.809745,29.524239],[-102.919284,29.190146],[-102.97953,29.184669],[-103.116454,28.987499],[-103.280762,28.982022],[-103.527224,29.135376],[-104.146119,29.381839],[-104.266611,29.513285],[-104.507597,29.639255],[-104.677382,29.924056],[-104.688336,30.181472],[-104.858121,30.389596],[-104.896459,30.570335],[-105.005998,30.685351],[-105.394861,30.855136],[-105.602985,31.085167],[-105.77277,31.167321],[-105.953509,31.364491],[-106.205448,31.468553],[-106.38071,31.731446],[-106.528588,31.786216],[-106.643603,31.901231],[-106.616219,31.999816],[-103.067161,31.999816],[-103.067161,33.002096],[-103.045254,34.01533],[-103.039777,36.501861],[-103.001438,36.501861],[-101.812942,36.501861]]]}}, +{"type":"Feature","id":"49","properties":{"name":"Utah","density":34.30},"geometry":{"type":"Polygon","coordinates":[[[-112.164359,41.995232],[-111.047063,42.000709],[-111.047063,40.998429],[-109.04798,40.998429],[-109.053457,39.125316],[-109.058934,38.27639],[-109.042503,38.166851],[-109.042503,37.000263],[-110.499369,37.00574],[-114.048427,37.000263],[-114.04295,41.995232],[-112.164359,41.995232]]]}}, +{"type":"Feature","id":"50","properties":{"name":"Vermont","density":67.73},"geometry":{"type":"Polygon","coordinates":[[[-71.503554,45.013027],[-71.4926,44.914442],[-71.629524,44.750133],[-71.536416,44.585825],[-71.700724,44.41604],[-72.034817,44.322932],[-72.02934,44.07647],[-72.116971,43.994316],[-72.204602,43.769761],[-72.379864,43.572591],[-72.456542,43.150867],[-72.445588,43.008466],[-72.533219,42.953697],[-72.544173,42.80582],[-72.456542,42.729142],[-73.267129,42.745573],[-73.278083,42.833204],[-73.245221,43.523299],[-73.404052,43.687607],[-73.349283,43.769761],[-73.436914,44.043608],[-73.321898,44.246255],[-73.294514,44.437948],[-73.387622,44.618687],[-73.332852,44.804903],[-73.343806,45.013027],[-72.308664,45.002073],[-71.503554,45.013027]]]}}, +{"type":"Feature","id":"51","properties":{"name":"Virginia","density":204.5},"geometry":{"type":"MultiPolygon","coordinates":[[[[-75.397659,38.013497],[-75.244304,38.029928],[-75.375751,37.860142],[-75.512674,37.799896],[-75.594828,37.569865],[-75.802952,37.197433],[-75.972737,37.120755],[-76.027507,37.257679],[-75.939876,37.564388],[-75.671506,37.95325],[-75.397659,38.013497]]],[[[-76.016553,37.95325],[-75.994645,37.95325],[-76.043938,37.95325],[-76.016553,37.95325]]],[[[-78.349729,39.464886],[-77.82942,39.130793],[-77.719881,39.322485],[-77.566527,39.306055],[-77.456988,39.223901],[-77.456988,39.076023],[-77.248864,39.026731],[-77.117418,38.933623],[-77.040741,38.791222],[-77.128372,38.632391],[-77.248864,38.588575],[-77.325542,38.446175],[-77.281726,38.342113],[-77.013356,38.374975],[-76.964064,38.216144],[-76.613539,38.15042],[-76.514954,38.024451],[-76.235631,37.887527],[-76.3616,37.608203],[-76.246584,37.389126],[-76.383508,37.285064],[-76.399939,37.159094],[-76.273969,37.082417],[-76.410893,36.961924],[-76.619016,37.120755],[-76.668309,37.065986],[-76.48757,36.95097],[-75.994645,36.923586],[-75.868676,36.551154],[-79.510841,36.5402],[-80.294043,36.545677],[-80.978661,36.562108],[-81.679709,36.589492],[-83.673316,36.600446],[-83.136575,36.742847],[-83.070852,36.852385],[-82.879159,36.890724],[-82.868205,36.978355],[-82.720328,37.044078],[-82.720328,37.120755],[-82.353373,37.268633],[-81.969987,37.537003],[-81.986418,37.454849],[-81.849494,37.285064],[-81.679709,37.20291],[-81.55374,37.208387],[-81.362047,37.339833],[-81.225123,37.235771],[-80.967707,37.290541],[-80.513121,37.482234],[-80.474782,37.421987],[-80.29952,37.509618],[-80.294043,37.690357],[-80.184505,37.849189],[-79.998289,37.997066],[-79.921611,38.177805],[-79.724442,38.364021],[-79.647764,38.594052],[-79.477979,38.457129],[-79.313671,38.413313],[-79.209609,38.495467],[-78.996008,38.851469],[-78.870039,38.763838],[-78.404499,39.169131],[-78.349729,39.464886]]]]}}, +{"type":"Feature","id":"53","properties":{"name":"Washington","density":102.6},"geometry":{"type":"MultiPolygon","coordinates":[[[[-117.033359,49.000239],[-117.044313,47.762451],[-117.038836,46.426077],[-117.055267,46.343923],[-116.92382,46.168661],[-116.918344,45.993399],[-118.988627,45.998876],[-119.125551,45.933153],[-119.525367,45.911245],[-119.963522,45.823614],[-120.209985,45.725029],[-120.505739,45.697644],[-120.637186,45.746937],[-121.18488,45.604536],[-121.217742,45.670259],[-121.535404,45.725029],[-121.809251,45.708598],[-122.247407,45.549767],[-122.762239,45.659305],[-122.811531,45.960537],[-122.904639,46.08103],[-123.11824,46.185092],[-123.211348,46.174138],[-123.370179,46.146753],[-123.545441,46.261769],[-123.72618,46.300108],[-123.874058,46.239861],[-124.065751,46.327492],[-124.027412,46.464416],[-123.895966,46.535616],[-124.098612,46.74374],[-124.235536,47.285957],[-124.31769,47.357157],[-124.427229,47.740543],[-124.624399,47.88842],[-124.706553,48.184175],[-124.597014,48.381345],[-124.394367,48.288237],[-123.983597,48.162267],[-123.704273,48.167744],[-123.424949,48.118452],[-123.162056,48.167744],[-123.036086,48.080113],[-122.800578,48.08559],[-122.636269,47.866512],[-122.515777,47.882943],[-122.493869,47.587189],[-122.422669,47.318818],[-122.324084,47.346203],[-122.422669,47.576235],[-122.395284,47.800789],[-122.230976,48.030821],[-122.362422,48.123929],[-122.373376,48.288237],[-122.471961,48.468976],[-122.422669,48.600422],[-122.488392,48.753777],[-122.647223,48.775685],[-122.795101,48.8907],[-122.756762,49.000239],[-117.033359,49.000239]]],[[[-122.718423,48.310145],[-122.586977,48.35396],[-122.608885,48.151313],[-122.767716,48.227991],[-122.718423,48.310145]]],[[[-123.025132,48.583992],[-122.915593,48.715438],[-122.767716,48.556607],[-122.811531,48.419683],[-123.041563,48.458022],[-123.025132,48.583992]]]]}}, +{"type":"Feature","id":"54","properties":{"name":"West Virginia","density":77.06},"geometry":{"type":"Polygon","coordinates":[[[-80.518598,40.636951],[-80.518598,39.722302],[-79.477979,39.722302],[-79.488933,39.20747],[-79.291763,39.300578],[-79.094593,39.470363],[-78.963147,39.437501],[-78.765977,39.585379],[-78.470222,39.514178],[-78.431884,39.623717],[-78.267575,39.61824],[-78.174467,39.694917],[-78.004682,39.601809],[-77.834897,39.601809],[-77.719881,39.322485],[-77.82942,39.130793],[-78.349729,39.464886],[-78.404499,39.169131],[-78.870039,38.763838],[-78.996008,38.851469],[-79.209609,38.495467],[-79.313671,38.413313],[-79.477979,38.457129],[-79.647764,38.594052],[-79.724442,38.364021],[-79.921611,38.177805],[-79.998289,37.997066],[-80.184505,37.849189],[-80.294043,37.690357],[-80.29952,37.509618],[-80.474782,37.421987],[-80.513121,37.482234],[-80.967707,37.290541],[-81.225123,37.235771],[-81.362047,37.339833],[-81.55374,37.208387],[-81.679709,37.20291],[-81.849494,37.285064],[-81.986418,37.454849],[-81.969987,37.537003],[-82.101434,37.553434],[-82.293127,37.668449],[-82.342419,37.783465],[-82.50125,37.931343],[-82.621743,38.123036],[-82.594358,38.424267],[-82.331465,38.446175],[-82.293127,38.577622],[-82.172634,38.632391],[-82.221926,38.785745],[-82.03571,39.026731],[-81.887833,38.873376],[-81.783771,38.966484],[-81.811156,39.0815],[-81.685186,39.273193],[-81.57017,39.267716],[-81.455155,39.410117],[-81.345616,39.344393],[-81.219646,39.388209],[-80.830783,39.711348],[-80.737675,40.078303],[-80.600752,40.319289],[-80.595275,40.472643],[-80.666475,40.582182],[-80.518598,40.636951]]]}}, +{"type":"Feature","id":"55","properties":{"name":"Wisconsin","density":105.2},"geometry":{"type":"Polygon","coordinates":[[[-90.415429,46.568478],[-90.229213,46.508231],[-90.119674,46.338446],[-89.09001,46.135799],[-88.662808,45.987922],[-88.531362,46.020784],[-88.10416,45.922199],[-87.989145,45.796229],[-87.781021,45.675736],[-87.791975,45.500474],[-87.885083,45.363551],[-87.649574,45.341643],[-87.742682,45.199243],[-87.589328,45.095181],[-87.627666,44.974688],[-87.819359,44.95278],[-87.983668,44.722749],[-88.043914,44.563917],[-87.928898,44.536533],[-87.775544,44.640595],[-87.611236,44.837764],[-87.403112,44.914442],[-87.238804,45.166381],[-87.03068,45.22115],[-87.047111,45.089704],[-87.189511,44.969211],[-87.468835,44.552964],[-87.545512,44.322932],[-87.540035,44.158624],[-87.644097,44.103854],[-87.737205,43.8793],[-87.704344,43.687607],[-87.791975,43.561637],[-87.912467,43.249452],[-87.885083,43.002989],[-87.76459,42.783912],[-87.802929,42.493634],[-88.788778,42.493634],[-90.639984,42.510065],[-90.711184,42.636034],[-91.067185,42.75105],[-91.143862,42.909881],[-91.176724,43.134436],[-91.056231,43.254929],[-91.204109,43.353514],[-91.215062,43.501391],[-91.269832,43.616407],[-91.242447,43.775238],[-91.43414,43.994316],[-91.592971,44.032654],[-91.877772,44.202439],[-91.927065,44.333886],[-92.233773,44.443425],[-92.337835,44.552964],[-92.545959,44.569394],[-92.808852,44.750133],[-92.737652,45.117088],[-92.75956,45.286874],[-92.644544,45.440228],[-92.770513,45.566198],[-92.885529,45.577151],[-92.869098,45.719552],[-92.639067,45.933153],[-92.354266,46.015307],[-92.29402,46.075553],[-92.29402,46.667063],[-92.091373,46.749217],[-92.014696,46.705401],[-91.790141,46.694447],[-91.09457,46.864232],[-90.837154,46.95734],[-90.749522,46.88614],[-90.886446,46.754694],[-90.55783,46.584908],[-90.415429,46.568478]]]}}, +{"type":"Feature","id":"56","properties":{"name":"Wyoming","density":5.851},"geometry":{"type":"Polygon","coordinates":[[[-109.080842,45.002073],[-105.91517,45.002073],[-104.058488,44.996596],[-104.053011,43.002989],[-104.053011,41.003906],[-105.728954,40.998429],[-107.919731,41.003906],[-109.04798,40.998429],[-111.047063,40.998429],[-111.047063,42.000709],[-111.047063,44.476286],[-111.05254,45.002073],[-109.080842,45.002073]]]}}, +{"type":"Feature","id":"72","properties":{"name":"Puerto Rico","density":1082 },"geometry":{"type":"Polygon","coordinates":[[[-66.448338,17.984326],[-66.771478,18.006234],[-66.924832,17.929556],[-66.985078,17.973372],[-67.209633,17.956941],[-67.154863,18.19245],[-67.269879,18.362235],[-67.094617,18.515589],[-66.957694,18.488204],[-66.409999,18.488204],[-65.840398,18.433435],[-65.632274,18.367712],[-65.626797,18.203403],[-65.730859,18.186973],[-65.834921,18.017187],[-66.234737,17.929556],[-66.448338,17.984326]]]}} +]}; diff --git a/docs/features.html b/docs/features.html new file mode 100644 index 00000000000..8c0ab6b7c8d --- /dev/null +++ b/docs/features.html @@ -0,0 +1,149 @@ +--- +layout: default +title: Features +--- + +

Leaflet Features

+ +

Leaflet doesn't try to do everything for everyone. Instead it focuses on making the basic things work perfectly. It should still satisfy the needs of the vast majority of map apps developers while being easily extended by third-party plugins.

+ +
+ +
+
+ +

Available Map Layers

+ +
    +
  • Tile layers
  • +
  • Markers
  • +
  • Popups
  • +
  • Vector layers: polylines, polygons, circles, rectangles, circle markers
  • +
  • GeoJSON layers
  • +
  • Image overlays
  • +
  • WMS layers
  • +
  • Layer groups
  • +
+ + +

Interaction Features

+ +

General

+ +
    +
  • Drag panning with inertia
  • +
+ +

On Desktop Browsers

+ +
    +
  • Scroll wheel zoom
  • +
  • Double click zoom
  • +
  • Zoom to area (shift-drag)
  • +
  • Keyboard navigation (with arrows and +/- keys)
  • +
+ + +

On Mobile Browsers

+ +
    +
  • Multi-touch zoom (iOS, Android 4+, Win8)
  • +
  • Double tap zoom
  • +
+ +

For Layers

+ +
    +
  • Various events: click (tap), mouseover, contextmenu, etc.
  • +
  • Marker dragging
  • +
+ + +
+ +
+ +

Visual Features

+ +
    +
  • Zoom animation (for all layers, including tile layers, markers and vector layers)
  • +
  • Panning animation
  • +
  • Smooth continuous zoom on modern mobile devices
  • +
  • Tile and popup fade animation
  • +
  • Very nice default design for markers, popups and other map controls
  • +
  • Retina resolution support for tile layers and markers
  • +
+ + +

Customization Features

+ +
    +
  • Pure CSS3 popups and controls for easy restyling
  • +
  • Image- and HTML-based markers
  • +
  • A simple interface for implementing custom map layers
  • +
  • The same for custom map controls
  • +
  • Custom map projections (with EPSG:4326, EPSG:3857 and EPSG:3395 out of the box)
  • +
  • Powerful OOP facilities for extending existing classes

    +
+ +

Performance Features

+ +
    +
  • Hardware acceleration on iOS (and other modern browsers) makes it feel as smooth as native apps
  • +
  • Utilizing CSS3 features like Transitions, Transforms, requestAnimationFrame where possible to make panning and zooming really smooth
  • +
  • Smart polyline/polygon rendering with dynamic clipping and simplification makes it responsive even when displaying objects with thousands of points
  • +
  • Modular design and a build system allow you to reduce the library size by leaving out features you don't need
  • +
  • Tap delay elimination on mobile devices makes controls and layers respond to taps immediately
  • +
+ +
+ +
+ +

Map Controls

+ +
    +
  • Zoom buttons
  • +
  • Attribution
  • +
  • Layer switcher
  • +
  • Scale
  • +
+ + +

Browser Support

+ +

On Desktop

+ +
    +
  • Chrome
  • +
  • Firefox
  • +
  • Safari 5+
  • +
  • Opera 12+
  • +
  • IE 7–11
  • +
+ +

On Mobile

+ +
    +
  • Safari for iOS 3–7+
  • +
  • Android browser 2.2+, 3.1+, 4+
  • +
  • Chrome for Android 4+ and iOS
  • +
  • Firefox for Android
  • +
  • Other WebKit browsers (webOS, Blackberry 7+, etc.)
  • +
  • IE10/11 for Win8 devices
  • +
+ + +

Misc

+ +
    +
  • Extremely lightweight — around 34 KB of gzipped JS code
  • +
  • No external dependencies
  • +
  • Keeps your JS environment clean — no global or native prototypes pollution
  • +
+
+
+ +
+ +

If you find some feature really missing in Leaflet, please vote for it on the Leaflet UserVoice page.

diff --git a/docs/highlight/LICENSE b/docs/highlight/LICENSE new file mode 100644 index 00000000000..422deb7350f --- /dev/null +++ b/docs/highlight/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2006, Ivan Sagalaev +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of highlight.js nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/docs/highlight/highlight.pack.js b/docs/highlight/highlight.pack.js new file mode 100644 index 00000000000..d1532b30d48 --- /dev/null +++ b/docs/highlight/highlight.pack.js @@ -0,0 +1 @@ +var hljs=new function(){function m(p){return p.replace(/&/gm,"&").replace(/"}while(y.length||w.length){var v=u().splice(0,1)[0];z+=m(x.substr(q,v.offset-q));q=v.offset;if(v.event=="start"){z+=t(v.node);s.push(v.node)}else{if(v.event=="stop"){var p,r=s.length;do{r--;p=s[r];z+=("")}while(p!=v.node);s.splice(r,1);while(r'+N[0]+""}else{r+=N[0]}P=Q.lR.lastIndex;N=Q.lR.exec(M)}return r+M.substr(P)}function B(M,N){var r;if(N.sL==""){r=g(M)}else{r=d(N.sL,M)}if(N.r>0){y+=r.keyword_count;C+=r.r}return''+r.value+""}function K(r,M){if(M.sL&&e[M.sL]||M.sL==""){return B(r,M)}else{return G(r,M)}}function J(N,r){var M=N.cN?'':"";if(N.rB){z+=M;N.buffer=""}else{if(N.eB){z+=m(r)+M;N.buffer=""}else{z+=M;N.buffer=r}}p.push(N);C+=N.r}function H(O,N,R){var S=p[p.length-1];if(R){z+=K(S.buffer+O,S);return false}var Q=s(N,S);if(Q){z+=K(S.buffer+O,S);J(Q,N);return Q.rB}var M=w(p.length-1,N);if(M){var P=S.cN?"":"";if(S.rE){z+=K(S.buffer+O,S)+P}else{if(S.eE){z+=K(S.buffer+O,S)+P+m(N)}else{z+=K(S.buffer+O+N,S)+P}}while(M>1){P=p[p.length-2].cN?"":"";z+=P;M--;p.length--}var r=p[p.length-1];p.length--;p[p.length-1].buffer="";if(r.starts){J(r.starts,"")}return S.rE}if(x(N,S)){throw"Illegal"}}var F=e[D];var p=[F.dM];var C=0;var y=0;var z="";try{var t,v=0;F.dM.buffer="";do{t=q(E,v);var u=H(t[0],t[1],t[2]);v+=t[0].length;if(!u){v+=t[1].length}}while(!t[2]);return{r:C,keyword_count:y,value:z,language:D}}catch(I){if(I=="Illegal"){return{r:0,keyword_count:0,value:m(E)}}else{throw I}}}function g(t){var p={keyword_count:0,r:0,value:m(t)};var r=p;for(var q in e){if(!e.hasOwnProperty(q)){continue}var s=d(q,t);s.language=q;if(s.keyword_count+s.r>r.keyword_count+r.r){r=s}if(s.keyword_count+s.r>p.keyword_count+p.r){r=p;p=s}}if(r.language){p.second_best=r}return p}function i(r,q,p){if(q){r=r.replace(/^((<[^>]+>|\t)+)/gm,function(t,w,v,u){return w.replace(/\t/g,q)})}if(p){r=r.replace(/\n/g,"
")}return r}function n(t,w,r){var x=h(t,r);var v=a(t);var y,s;if(v=="no-highlight"){return}if(v){y=d(v,x)}else{y=g(x);v=y.language}var q=c(t);if(q.length){s=document.createElement("pre");s.innerHTML=y.value;y.value=k(q,c(s),x)}y.value=i(y.value,w,r);var u=t.className;if(!u.match("(\\s|^)(language-)?"+v+"(\\s|$)")){u=u?(u+" "+v):v}if(/MSIE [678]/.test(navigator.userAgent)&&t.tagName=="CODE"&&t.parentNode.tagName=="PRE"){s=t.parentNode;var p=document.createElement("div");p.innerHTML="
"+y.value+"
";t=p.firstChild.firstChild;p.firstChild.cN=s.cN;s.parentNode.replaceChild(p.firstChild,s)}else{t.innerHTML=y.value}t.className=u;t.result={language:v,kw:y.keyword_count,re:y.r};if(y.second_best){t.second_best={language:y.second_best.language,kw:y.second_best.keyword_count,re:y.second_best.r}}}function o(){if(o.called){return}o.called=true;var r=document.getElementsByTagName("pre");for(var p=0;p|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\.",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE],r:0};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE],r:0};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.inherit=function(r,s){var p={};for(var q in r){p[q]=r[q]}if(s){for(var q in s){p[q]=s[q]}}return p}}();hljs.LANGUAGES.javascript=function(a){return{dM:{k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete",literal:"true false null undefined NaN Infinity"},c:[a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,{cN:"regexp",b:"/",e:"/[gim]*",c:[{b:"\\\\/"}]}],r:0},{cN:"function",bWK:true,e:"{",k:"function",c:[{cN:"title",b:"[A-Za-z$_][0-9A-Za-z$_]*"},{cN:"params",b:"\\(",e:"\\)",c:[a.CLCM,a.CBLCLM],i:"[\"'\\(]"}],i:"\\[|%"}]}}}(hljs);hljs.LANGUAGES.css=function(a){var b={cN:"function",b:a.IR+"\\(",e:"\\)",c:[{eW:true,eE:true,c:[a.NM,a.ASM,a.QSM]}]};return{cI:true,dM:{i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",eE:true,k:"import page media charset",c:[b,a.ASM,a.QSM,a.NM]},{cN:"tag",b:a.IR,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[b,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"\\#[0-9A-F]+"},{cN:"important",b:"!important"}]}}]}]}]}}}(hljs);hljs.LANGUAGES.xml=function(a){var c="[A-Za-z0-9\\._:-]+";var b={eW:true,c:[{cN:"attribute",b:c,r:0},{b:'="',rB:true,e:'"',c:[{cN:"value",b:'"',eW:true}]},{b:"='",rB:true,e:"'",c:[{cN:"value",b:"'",eW:true}]},{b:"=",c:[{cN:"value",b:"[^\\s/>]+"}]}]};return{cI:true,dM:{c:[{cN:"pi",b:"<\\?",e:"\\?>",r:10},{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},{cN:"tag",b:"",c:[{cN:"title",b:"[^ />]+"},b]}]}}}(hljs);hljs.LANGUAGES.json=function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{dM:{c:d,k:e,i:"\\S"}}}(hljs); \ No newline at end of file diff --git a/docs/highlight/styles/default.css b/docs/highlight/styles/default.css new file mode 100644 index 00000000000..8717dd4ced3 --- /dev/null +++ b/docs/highlight/styles/default.css @@ -0,0 +1,133 @@ +/* + +Original style from softwaremaniacs.org (c) Ivan Sagalaev + +*/ + +pre code { + display: block; padding: 0.5em; + background: #F0F0F0; +} + +pre code, +pre .ruby .subst, +pre .tag .title, +pre .lisp .title, +pre .nginx .title { + color: black; +} + +pre .string, +pre .title, +pre .constant, +pre .parent, +pre .tag .value, +pre .rules .value, +pre .rules .value .number, +pre .preprocessor, +pre .ruby .symbol, +pre .ruby .symbol .string, +pre .ruby .symbol .keyword, +pre .ruby .symbol .keymethods, +pre .instancevar, +pre .aggregate, +pre .template_tag, +pre .django .variable, +pre .smalltalk .class, +pre .addition, +pre .flow, +pre .stream, +pre .bash .variable, +pre .apache .tag, +pre .apache .cbracket, +pre .tex .command, +pre .tex .special, +pre .erlang_repl .function_or_atom, +pre .markdown .header { + color: #800; +} + +pre .comment, +pre .annotation, +pre .template_comment, +pre .diff .header, +pre .chunk, +pre .markdown .blockquote { + color: #888; +} + +pre .number, +pre .date, +pre .regexp, +pre .literal, +pre .smalltalk .symbol, +pre .smalltalk .char, +pre .go .constant, +pre .change, +pre .markdown .bullet, +pre .markdown .link_url { + color: #080; +} + +pre .label, +pre .javadoc, +pre .ruby .string, +pre .decorator, +pre .filter .argument, +pre .localvars, +pre .array, +pre .attr_selector, +pre .important, +pre .pseudo, +pre .pi, +pre .doctype, +pre .deletion, +pre .envvar, +pre .shebang, +pre .apache .sqbracket, +pre .nginx .built_in, +pre .tex .formula, +pre .erlang_repl .reserved, +pre .input_number, +pre .markdown .link_label, +pre .vhdl .attribute { + color: #88F +} + +pre .keyword, +pre .id, +pre .phpdoc, +pre .title, +pre .built_in, +pre .aggregate, +pre .css .tag, +pre .javadoctag, +pre .phpdoc, +pre .yardoctag, +pre .smalltalk .class, +pre .winutils, +pre .bash .variable, +pre .apache .tag, +pre .go .typename, +pre .tex .command, +pre .markdown .strong, +pre .request, +pre .status { + font-weight: bold; +} + +pre .markdown .emphasis { + font-style: italic; +} + +pre .nginx .built_in { + font-weight: normal; +} + +pre .coffeescript .javascript, +pre .xml .css, +pre .xml .javascript, +pre .xml .vbscript, +pre .tex .formula { + opacity: 0.5; +} diff --git a/docs/highlight/styles/github.css b/docs/highlight/styles/github.css new file mode 100644 index 00000000000..ba1fabd9198 --- /dev/null +++ b/docs/highlight/styles/github.css @@ -0,0 +1,138 @@ +/* + +github.com style (c) Vasily Polovnyov + +*/ + +pre code { + display: block; padding: 0.5em; + color: #000; + background: #f8f8ff +} + +code .comment, +pre .template_comment, +pre .diff .header, +pre .javadoc { + color: #998; +} + +code .keyword, +pre .css .rule .keyword, +pre .winutils, +/*pre .javascript .title,*/ +pre .lisp .title, +pre .nginx .title, +pre .subst, +pre .request, +pre .status { + color: #026D97; + font-weight: normal; +} + +pre .javascript .title { + color: black; + font-weight: bold; +} + +code .number, +pre .hexcolor, +code .literal { + color: #00A707 +} + +code .string, +pre .tag .value, +pre .phpdoc, +pre .tex .formula { + color: #E43E59; +} + +pre .title, +pre .id { + color: #900; + font-weight: bold +} + +pre .javascript .title, +pre .lisp .title, +pre .subst { + font-weight: normal +} + +pre .class .title, +pre .haskell .type, +pre .vhdl .literal, +pre .tex .command { + color: #458; + font-weight: bold +} + +pre .tag, +pre .tag .title, +pre .rules .property, +pre .django .tag .keyword { + color: #000080; + font-weight: normal +} + +pre .attribute, +pre .variable, +pre .instancevar, +pre .lisp .body { + color: #008080 +} + +pre .regexp { + color: #009926 +} + +pre .class { + color: #458; + font-weight: bold +} + +pre .symbol, +pre .ruby .symbol .string, +pre .ruby .symbol .keyword, +pre .ruby .symbol .keymethods, +pre .lisp .keyword, +pre .tex .special, +pre .input_number { + color: #990073 +} + +pre .builtin, +pre .built_in, +pre .lisp .title { + color: #0086b3 +} + +pre .preprocessor, +pre .pi, +pre .doctype, +pre .shebang, +pre .cdata { + color: #999; + font-weight: bold +} + +pre .deletion { + background: #fdd +} + +pre .addition { + background: #dfd +} + +pre .diff .change { + background: #0086b3 +} + +pre .chunk { + color: #aaa +} + +pre .tex .formula { + opacity: 0.5; +} diff --git a/docs/images/choropleth.png b/docs/images/choropleth.png new file mode 100644 index 00000000000..c61deff68a7 Binary files /dev/null and b/docs/images/choropleth.png differ diff --git a/docs/images/custom-icons.png b/docs/images/custom-icons.png new file mode 100644 index 00000000000..68424029dd6 Binary files /dev/null and b/docs/images/custom-icons.png differ diff --git a/docs/images/favicon.png b/docs/images/favicon.png new file mode 100644 index 00000000000..51c29cd944c Binary files /dev/null and b/docs/images/favicon.png differ diff --git a/docs/images/forum.png b/docs/images/forum.png new file mode 100644 index 00000000000..a4eaaa49982 Binary files /dev/null and b/docs/images/forum.png differ diff --git a/docs/images/geojson.png b/docs/images/geojson.png new file mode 100644 index 00000000000..06de11419f5 Binary files /dev/null and b/docs/images/geojson.png differ diff --git a/docs/images/github.png b/docs/images/github.png new file mode 100644 index 00000000000..129e95ab8ee Binary files /dev/null and b/docs/images/github.png differ diff --git a/docs/images/layers-control.png b/docs/images/layers-control.png new file mode 100644 index 00000000000..3738dbab875 Binary files /dev/null and b/docs/images/layers-control.png differ diff --git a/docs/images/leaf-green.png b/docs/images/leaf-green.png new file mode 100644 index 00000000000..480c9136e8d Binary files /dev/null and b/docs/images/leaf-green.png differ diff --git a/docs/images/leaf-orange.png b/docs/images/leaf-orange.png new file mode 100644 index 00000000000..94ea5c1a7d5 Binary files /dev/null and b/docs/images/leaf-orange.png differ diff --git a/docs/images/leaf-red.png b/docs/images/leaf-red.png new file mode 100644 index 00000000000..8c6b7e13f67 Binary files /dev/null and b/docs/images/leaf-red.png differ diff --git a/docs/images/leaf-shadow.png b/docs/images/leaf-shadow.png new file mode 100644 index 00000000000..119a71dbcfb Binary files /dev/null and b/docs/images/leaf-shadow.png differ diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 00000000000..8c507b68f63 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/images/mobile.png b/docs/images/mobile.png new file mode 100644 index 00000000000..7aa0539ed9f Binary files /dev/null and b/docs/images/mobile.png differ diff --git a/docs/images/paper.png b/docs/images/paper.png new file mode 100644 index 00000000000..59779616676 Binary files /dev/null and b/docs/images/paper.png differ diff --git a/docs/images/quick-start.png b/docs/images/quick-start.png new file mode 100644 index 00000000000..5912a605669 Binary files /dev/null and b/docs/images/quick-start.png differ diff --git a/docs/images/sprite.png b/docs/images/sprite.png new file mode 100644 index 00000000000..bcbb751aad5 Binary files /dev/null and b/docs/images/sprite.png differ diff --git a/docs/images/sprite.svg b/docs/images/sprite.svg new file mode 100644 index 00000000000..847976e6b83 --- /dev/null +++ b/docs/images/sprite.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/docs/images/twitter.png b/docs/images/twitter.png new file mode 100644 index 00000000000..cd2a9162d90 Binary files /dev/null and b/docs/images/twitter.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000000..59b80d4be4e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,98 @@ +--- +layout: default +--- + + + +Leaflet is a modern open-source JavaScript library for mobile-friendly interactive maps. +It is developed by [Vladimir Agafonkin][] with a team of dedicated [contributors][]. +Weighing just about 33 KB of JS, +it has all the [features][] most developers ever need for online maps. + +Leaflet is designed with _simplicity_, _performance_ and _usability_ in mind. +It works efficiently across all major desktop and mobile platforms out of the box, +taking advantage of HTML5 and CSS3 on modern browsers while still being accessible on older ones. +It can be extended with a huge amount of [plugins][], +has a beautiful, easy to use and [well-documented API][] +and a simple, readable [source code][] that is a joy to [contribute][] to. + +{: .usedby} +Used by: +[Flickr](http://flickr.com/map) +[foursquare](https://foursquare.com/) +[Pinterest](http://pinterest.com) +[craigslist](http://t.co/V4EiURIA) +[Data.gov](http://data.gov) +[IGN](http://www.ign.com/wikis/the-elder-scrolls-5-skyrim/interactive-maps/Skyrim) +[Wikimedia](http://blog.wikimedia.org/2012/04/05/new-wikipedia-app-for-ios-and-an-update-for-our-android-app/) +[OSM](http://openstreetmap.org) +[Meetup](http://www.meetup.com/) +[WSJ](http://projects.wsj.com/campaign2012/maps/) +[Mapbox](http://mapbox.com) +[CartoDB](http://cartodb.com) +[GIS Cloud](http://www.giscloud.com/) +... + +
+ +In this basic example, we create a map with tiles of our choice, add a marker and bind a popup with some text to it: + + // create a map in the "map" div, set the view to a given place and zoom + var map = L.map('map').setView([51.505, -0.09], 13); + + // add an OpenStreetMap tile layer + L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + + // add a marker in the given location, attach some popup content to it and open the popup + L.marker([51.5, -0.09]).addTo(map) + .bindPopup('A pretty CSS3 popup.
Easily customizable.') + .openPopup(); + +Learn more with the [quick start guide](examples/quick-start.html), check out [other tutorials](examples.html), or head straight to the [API documentation](reference.html).
+If you have any questions, take a look at the [FAQ](https://github.com/Leaflet/Leaflet/blob/master/FAQ.md) first. + + +## Getting Involved + +Third-party patches are absolutely essential on our quest to create the best mapping library that will ever exist. +However, they're not the only way to get involved with the development of Leaflet. +You can help the project tremendously by discovering and [reporting bugs][], [improving documentation][], +helping others on the [Leaflet forum](https://groups.google.com/forum/#!forum/leaflet-js) +and [GitHub issues](https://github.com/Leaflet/Leaflet/issues), +showing your support for your favorite feature suggestions on [Leaflet UserVoice page](http://leaflet.uservoice.com), +tweeting to [@LeafletJS](http://twitter.com/LeafletJS) +and spreading the word about Leaflet among your colleagues and friends. + +Check out the [contribution guide][contribute] for more information on getting involved with Leaflet development. + + [Vladimir Agafonkin]: http://agafonkin.com/en + [contributors]: https://github.com/Leaflet/Leaflet/graphs/contributors + [features]: features.html + [plugins]: plugins.html + [well-documented API]: reference.html "Leaflet API reference" + [source code]: https://github.com/Leaflet/Leaflet "Leaflet GitHub repository" + [hosted on GitHub]: http://github.com/Leaflet/Leaflet + [contribute]: https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md "A guide to contributing to Leaflet" + [reporting bugs]: https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md#reporting-bugs + [improving documentation]: https://github.com/Leaflet/Leaflet/blob/master/CONTRIBUTING.md#improving-documentation + [@mourner]: http://github.com/mourner + [GitHub issues page]: http://github.com/Leaflet/Leaflet/issues + [Leaflet UserVoice page]: http://leaflet.uservoice.com + [@LeafletJS]: http://twitter.com/LeafletJS + [Leaflet mailing list]: https://groups.google.com/group/leaflet-js + + + diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 00000000000..d7abd8c4b98 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,1788 @@ +--- +layout: default +title: Plugins +--- + +## Notable Leaflet Plugins + +While Leaflet is meant to be as lightweight as possible, and focuses on a core set of features, an easy way to extend its functionality is to use third-party plugins. Thanks to the awesome community behind Leaflet, there are lots of nice plugins to choose from. + +--- + +### Layers and Overlays + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ Leaflet.FreeDraw + + Zoopla inspired freehand polygon creation using Leaflet.js and D3. + + Wildhoney +
+ Leaflet.ellipse + + Leaflet.ellipse place ellipses on map by specifying center point, semi-major axis, + semi-minor axis, and tilt degrees from west. + + JD Fergason +
+ Leaflet.plotter + + leaflet-plotter allows you to create routes using a leaflet powered map. You can click on the mid-points to create a new, draggable point. + + Nathan Mahdavi +
+ Leaflet.markercluster + + Beautiful, sophisticated, high performance marker clustering solution with smooth animations and lots of great features. Recommended! + + Dave Leaver +
+ Leaflet.label + + Adds text labels to map markers and vector layers. + + Jacob Toye +
+ RaphaelLayer + + Allows you to use Raphael as a layer on a Leaflet map for advanced animations and visualizations. + + Dynamic Methods +
+ Overlapping Marker Spiderfier + + Deals with overlapping markers in a Google Earth-inspired way by gracefully springing them apart on click. + + George MacKerron +
+ TileLayer.BoundaryCanvas + + Allows you to draw tile layers with arbitrary polygonal boundary. HTML5 Canvas is used for rendering. + + Alexander Parshin +
+ MaskCanvas + + Canvas layer that can be used to visualize coverage. + + Dominik Moritz +
+ HeatCanvas + + Simple heatmap api based on HTML5 canvas. + + Sun Ning +
+ heatmap.js + + JavaScript Library for HTML5 canvas based heatmaps. + + Its Leaflet layer implementation supports large datasets because it is tile based and uses a quadtree index to store the data. + + Patrick Wied +
+ Leaflet divHeatmap + + Lightweight and versatile heatmap layer based on CSS3 and divIcons + + + Daniele Piccone +
+ WebGL Heatmap + + High performance Javascript heatmap plugin using WebGL. + + + Benjamin J DeLong +
+ Leaflet.MultiTileLayer + + Allows to compose a TileLayer from several tile sources. Each source is active only on a defined set of zoomlevels. + + Mattias Bengtsson +
+ Leaflet.AnimatedMarker + + Animate a marker along a polyline. + + Aaron Ogle +
+ Leaflet-semicircle + + Adds functionality to L.Circle to draw semicircles. + + Jieter +
+ Leaflet.FunctionalTileLayer + + Allows you to define tile layer URLs using a function, with support for jQuery deferreds. + + Ishmael Smyrnow +
+ Leaflet.geoCSV + + Leaflet plugin for loading a CSV file as geoJSON layer. + + Iván Eixarch +
+ Leaflet.PolylineDecorator + + Allows you to draw patterns (like dashes, arrows or evenly spaced Markers) along Polylines or coordinate paths. + + Benjamin Becquet +
+ Leaflet.Sprite + + Use sprite based icons in your markers. + + Calvin Metcalf +
+ Leaflet.BounceMarker + + Make a marker bounce when you add it to a map. + + Maxime Hadjinlian +
+ Leaflet.SmoothMarkerBouncing + + Smooth animation of marker bouncing for Leaflet. + + Alexei KLENIN +
+ Leaflet.TextPath + + Allows you to draw text along Polylines. + + Mathieu Leplatre +
+ Leaflet.Awesome-Markers + + Colorful, iconic & retina-proof markers based on the Font Awesome icons/Twitter Bootstrap icons + + Lennard Voogdt +
+ Leaflet Data Visualization Framework + + New markers, layers, and utility classes for easy thematic mapping and data visualization. + + Scott Fairgrieve +
+ TileLayer.Grayscale + + A regular TileLayer with grayscale makeover. + + Ilya Zverev +
+ TileLayer.GeoJSON + + A TileLayer for GeoJSON tiles. + + Glen Robertson +
+ TileLayer.Zoomify + + A TileLayer for Zoomify images. + + Bjørn Sandvik +
+ TileLayer.DeepZoom + + A TileLayer for DeepZoom images. + + Al Farisi, + Indokreatif Teknologi +
+ TileLayer.Gigapan + + A TileLayer for Gigapan images. + + Dan Sherman +
+ TileLayer.PouchDBCached + + Allows all Leaflet TileLayers to cache into PouchDB for offline use. + + Iván Sánchez Ortega, + MazeMap +
+ Leaflet.Graticule + + Draws a grid of latitude and longitude lines. + + Bjørn Sandvik +
+ Leaflet.SimpleGraticule + + Draws a grid lines for L.CRS.Simple coordinate system. + + Andrew Blakey +
+ leaflet-usermarker + + Plugin for plotting a marker representing a user - or multiple users - on a map, + with support for drawing an accuraccy circle. Can be seen in action on + Longitude.me. + + Jonatan Heyman +
+ Leaflet.EdgeMarker + + Plugin to indicate the existence of Features outside of the current view. + + Gerald Pape +
+ Leaflet.Shapefile + + Put a shapefile onto your map as a layer. + + Calvin Metcalf +
+ Leaflet.FileGDB + + Put an ESRI File GeoDatabase onto your map as a layer. + + Calvin Metcalf +
+ Leaflet.Terminator + Overlay day and night regions on a map. + + Jörg Dietrich +
+ Leaflet.FeatureSelect + Use a configurable centerpoint marker to select any geometry type from a GeoJSON layer. + + Aaron Ogle +
+ Leaflet.MakiMarkers + Create markers using Maki Icons from MapBox. + + James Seppi +
+ Leaflet.Editable.Polyline + Editable polylines: move existing points, add new points and split polylines. + + Tomo Krajina +
+ leaflet.TileLayer.WMTS + Add WMTS (IGN) layering for leaflet. + + Alexandre Melard +
+ leaflet-omnivore + + Loads & converts CSV, KML, GPX, TopoJSON, WKT formats for Leaflet. + + Mapbox +
+ Leaflet.TileLayer.IIP + Add support for IIPImage layers in Leaflet. + + Emmanuel Bertin +
+ Leaflet.ImageTransform + Add support of image overlays with arbitrary perspective transformation. + + Alexander Parshin, + Sergey Alekseev +
+ PruneCluster + + Fast and realtime marker clustering library. + + Antoine Pultier +
+ Leaflet.Indoor + + Create indoor maps. + + Christopher Baines +
+ Leaflet.Geodesic + + Draw geodesic (poly)lines. A geodesic line is the shortest path between two given positions on the earth surface. and You can also calculate the exact distance between two given points on the map. + + Henry Thasler +
+ Leaflet.LineExtremities + + Show symbols at the extremities of polylines, using SVG markers. + + Frédéric Bonifas +
+ Leaflet.VectorMarkers + + Vector SVG markers for Leaflet, with an option for Font Awesome/Twitter Bootstrap icons. + + Mathias Schneider +
+ Leaflet.MovingMarker + + Allow to move markers along a polyline with custom durations. + + Ewoken +
+ Leaflet Realtime + + Put realtime data on a Leaflet map: live tracking GPS units, sensor data or just about anything. + + Per Liedman +
+ Leaflet-IIIF + + A IIIF (International Image Interoperability Framework) viewer for Leaflet. See the demo. + + Jack Reed +
+ Leaflet.D3SvgOverlay + + SVG overlay class for using with D3 library. Supports zoom animation and scaling without need to redraw the layer. + + Kirill Zhuravlev +
+ Leaflet.TransitionedIcon + + Transition in/out markers with CSS3 transitions. It supports jitter + for staggering markers into view to prevent visual overload. See the demo. + + Brian Reavis +
+ Leaflet Layer Overpass + + Easily include data from the overpass api. + + kartenkarsten +
+ Leaflet.pattern + + Add support for pattern fills on Paths. + + Tyler Eastman +
+ + +### Services, Providers and Formats + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ Spectrum4Leaflet + + Tools for using Spectrum Spatial Server services with leaflet. This plugin supports: map service, tile service, feature service. It has layers, legend and feature controls. + + SVoyt, ESTI MAP +
+ Proj4Leaflet + + Proj4js integration plugin, allowing you to use all kinds of weird projections in Leaflet. + + Kartena +
+ Plugins by Pavel Shramov + + A set of plugins for: GPX, KML layers; Bing tile layer; Google and Yandex layers (implemented with their APIs), permalink and distance measurement controls. + + Pavel Shramov, Bruno B +
+ cartodb-leaflet + + Official CartoDB plugin for Leaflet. + + Vizzuality +
+ Leaflet Vector Layers + + Allows to easily create vector layers from a number of geo web services, such as ArcGIS Server, Arc2Earth, GeoIQ, CartoDB and GIS Cloud. + + Jason Sanford +
+ leaflet-tilejson + + Adds support for the TileJSON specification to Leaflet. + + Per Liedman, Kartena +
+ leaflet-providers + + Contains configurations for various free tile providers — OSM, OpenCycleMap, MapQuest, Stamen, Esri, etc. + + leaflet-extras members +
+ azgs-leaflet + + A set of small plugins for Leaflet, including WFS-GeoJSON layer with filtering, a hover control for GeoJSON, and an Esri tile layer. + + AZGS +
+ Leaflet.encoded + + Use encoded polylines in Leaflet. + + Jieter +
+ Leaflet.Pouch + + Use PouchDB to sync CouchDB data to local storage (indexedDB), to just add couchDB data or as just a less confusing implementation of indexedDB. + + Calvin Metcalf +
+ Leaflet Ajax + + Add GeoJSON data via ajax or jsonp. + + Calvin Metcalf +
+ Leaflet GPX + + GPX layer, targeted at sporting activities by providing access to information such as distance, moving time, pace, elevation, heart rate, etc. + + Maxime Petazzoni +
+ Wicket + + A modest library for translating between Well-Known Text (WKT) and Leaflet geometry objects (e.g. between L.marker() instances and "POINT()" strings). + + K. Arthur Endsley +
+ Leaflet.dbpediaLayer + + A layer with Points of interest from Wikipedia - loaded via ajax from DBpedia's SPARQL endpoint. + + Kr1 +
+ Leaflet-2gis + + Adds support for 2GIS tile layer + + Eugene Mikhalev +
+ Leaflet.KoreanTmsProviders + + Contains configurations for various (South) Korean tile providers — Daum, Naver, VWorld, etc. + + Seong Choi +
+ Leaflet.ChineseTmsProviders + + Contains configurations for various Chinese tile providers — TianDiTu, MapABC, GaoDe, etc. + + Tao Huang +
+ Esri Leaflet + + A set of tools for using ArcGIS services with Leaflet. Support for map services, feature layers, ArcGIS Online tiles and more. + + Patrick Arlt +
+ Leaflet.geojsonCSS + + Geojson CSS implementation for Leaflet. + + Alexander Burtsev +
+ leaflet.wms + + Enhanced WMS support for Leaflet, including single-tile/untiled layers, shared WMS sources, and layer identify via GetFeatureInfo. + + S. Andrew Sheppard
(HEI Geo) +
+ + +### Geocoding (Address Lookup) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ Leaflet GeoSearch + + Small geocoding plugin that brings address searching/lookup (aka geosearching) to Leaflet.
+ Comes with support for Google, OpenStreetMap Nominatim, Bing, Esri and Nokia. Easily extensible. +
+ Stephan Meijer +
+ Leaflet Control OSM Geocoder + + A simple geocoder that uses OpenstreetMap Nominatim to locate places by address. + + Karsten Hinz +
+ Leaflet Control Bing Geocoder + + A simple geocoder control that uses Bing to locate places. + + Samuel Piquet +
+ Leaflet Control Geocoder + + A clean and extensible control that uses Nominatim (OSM) or Bing to locate places. Easy to adapt for other providers. + + Per Liedman +
+ Leaflet GeoIP Locator + + A simple plugin that allows finding the approximate location of IP addresses and map centering on said location. + + Jakub Dostal +
+ Esri Leaflet Geocoder + + A geocoding control with suggestions powered by the ArcGIS Online geocoder. + + Patrick Arlt +
+ Leaflet.OpenCage.Search + + A search plugin plugin that uses OpenCage Data's geocoding API. + + The OpenCage team +
+ Leaflet.Geonames + + A lightweight geocoding control powered by GeoNames. Demo + + Brendan Ward +
+ + +### Routing and route search + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ Leaflet Routing Machine + + Control for route search between waypoints, displaying itinerary and alternative routes. Currently + uses OSRM to search routes. + + Per Liedman +
+ Leaflet.Routing + + Leaflet controller and interface for routing paths between waypoints using any user provided routing service. + + Norwegian Trekking Association +
+ Route360° + + Route360° visualizes the area which is reachable from a set of starting points in a given time and gives detailed routing information (walk, bike, car and public transportation) to targets. + + Motion Intelligence GmbH +
+ + +### Controls and Interaction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ Leaflet.AreaSelect + + A fixed positioned, resizable rectangle for selecting an area on the map. + + Jonatan Heyman +
+ Leaflet.draw + + Enables drawing features like polylines, polygons, rectangles, circles and markers through a very nice user-friendly interface with icons and hints. Recommended! + + Jacob Toye +
+ Leaflet.utfgrid + + Provides a utfgrid interaction handler for leaflet a very small footprint. + + Dave Leaver +
+ L.EasyButton + + In one line, add a Font Awesome control button with attached click events. + Demo + + atstp +
+ Leaflet.EditableHandlers + + A set of plugins that includes circle editing, measuring tool, and label for polygon sides. + + Kartena +
+ L.LocationShare + + Allow users to send and receive a marker with a message. + Demo + + atstp +
+ Leaflet.Pancontrol + + A simple panning control. + + Kartena +
+ Leaflet.zoomslider + + A zoom slider control. + + Kartena +
+ Leaflet.Locate + + A customizable locate control. + + Dominik Moritz +
+ Leaflet.fullscreen + + A fullscreen button control by mapbox + + mapbox +
+ leaflet.zoomfs + + A fullscreen button control. + + Eli Dupuis +
+ leaflet.fullscreen + + Another fullscreen button control but for modern browsers, using HTML5 Fullscreen API. + + Bruno B +
+ leaflet-search + + A control for search Markers/Features location by custom property in LayerGroup/GeoJSON. Support AJAX/JSONP, Autocompletion and 3rd party service + + Stefano Cudini +
+ leaflet-fusesearch + + A control that provides a panel to search features in a GeoJSON layer using the lightweight fuzzy search Fuse.js + + Antoine Riche +
+ leaflet-locationfilter + + A draggable/resizable rectangle for selecting an area on the map. + + Robert Kajic +
+ Leaflet.MiniMap + + A small minimap showing the map at a different scale to aid navigation. + + Robert Nordan +
+ Leaflet.Rrose + + A Leaflet Plugin for Edge Cases. For use when you want popups on mouseover, not click, and + you need popup tips to reorient as you get close to the edges of your map. + + Eric Theise +
+ Leaflet.EditInOSM + + Add a control with links to open the current map view on main OSM editors. + + Yohan Boniface +
+ Leaflet.Spin + + Shows a nice spinner on the map using Spin.js, + for asynchronous data load, like with Leaflet Ajax. + + Mathieu Leplatre +
+ Leaflet.RestoreView + + Stores and restores map view using localStorage. + + Mathieu Leplatre +
+ Leaflet.FileLayer + + Loads files (GeoJSON, GPX, KML) into the map using the HTML5 FileReader API (i.e. locally without server). + + Mathieu Leplatre +
+ Leaflet.Snap + + Enables snapping of draggable markers to polylines and other layers. + + Mathieu Leplatre +
+ Leaflet Time-Slider + + The Leaflet Time-Slider enables you to dynamically add and remove Markers on a map by using a JQuery UI slider + + Dennis Wilhelm +
+ Leaflet.RevealOSM + + Very simple but extendable Leaflet plugin to display OSM POIs data on map click. + + Yohan Boniface +
+ Leaflet.MousePosition + + A simple MousePosition control that displays geographic coordinates of the mouse pointer, as it is moved about the map + + Ardhi Lukianto +
+ Leaflet.SelectLayers + + a Leaflet plugin which adds new control to switch between different layers on the map. New control replaces L.Control.Layers radio button panel with select tag. + + vogdb +
+ Leaflet.StyledLayerControl + + A Leaflet plugin that implements the management and control of layers by organization into categories or groups. + + Davi Custodio +
+ Leaflet.Coordinates + + A simple Leaflet plugin viewing the mouse LatLng-coordinates. Also views a marker with coordinate popup on userinput. + + Felix Bache +
+ Leaflet.Elevation + + A Leaflet plugin to view interactive height profiles of GeoJSON lines using d3. + + Felix Bache +
+ Leaflet.Sync + + Synchronized view of two maps. + + Bjørn Sandvik +
+ Leaflet.GroupedLayerControl + + Leaflet layer control with support for grouping overlays together. + + Ishmael Smyrnow +
+ Leaflet.BorderPan + + A Leaflet plugin to pan by clicking on map borders. + + Sebastián Lara +
+ Leaflet.TileLegend + + Create illustrated and interactive legends for your background layers. + + Yohan Boniface +
+ LeafletPlayback + + Play back time-stamped GPS Tracks synchronized to a clock. + + Nicholas Hallahan +
+ Leaflet.loading + + A simple control that adds a loading indicator as tiles and other data are loaded. + + Eric Brelsford +
+ Leaflet.viewcenter + + A simple control that adds a button to change view and zoom to predefinied values in options. + + Dariusz Pawlak +
+ Leaflet.contextmenu + + A context menu for Leaflet. + + Adam Ratcliffe +
+ Leaflet.MeasureControl + + A simple tool to measure distances on maps (*relies on Leaflet.Draw*). + + Makina Corpus +
+ Leaflet.OverIntent + + Adds a new event ``mouseintent``, that differs from ``mouseover`` since it reflects user + intentions to aim a particular layer. + + Mathieu Leplatre +
+ Leaflet.AlmostOver + + Trigger mouse events when cursor is "almost" over a layer. + + Mathieu Leplatre +
+ Leaflet Control Order Layers + + Adds the ability to change overlay order in the layers control. + + Michael Salgado +
+ Leaflet.layerscontrol-minimap + + Extends the default Leaflet layers control with synced minimaps. + + Jieter +
+ leaflet-sidebar + + A responsive sidebar plugin. + + Tobias Bieniek +
+ sidebar-v2 + + Another responsive sidebar plugin. This time with tabs! + + Tobias Bieniek +
+ leaflet-zoom-min + + Adds a button to the zoom control that allows you to zoom to the map minimum zoom level in a single click. + + Alan Shaw +
+ Leaflet.MagnifyingGlass + + Allows you to display a small portion of the map at another zoom level, either at a fixed position or linked to the mouse movement, for a magnifying glass effect. + + Benjamin Becquet +
+ Leaflet.OpacityControls + + Simple Leaflet controls to adjust the opacity of a map layer. + + Jared Dominguez +
+ Leaflet.StyleEditor + + Enables editing the styles of features (lines, polygons, etc) and markers with a GUI. + + Dennis Wilhelm +
+ Leaflet.UniformControl + + Leaflet layer control with stylable checkboxes and radio buttons. + + Chris Calip +
+ Leaflet.SimpleMarkers + + A light-weight Leaflet plugin for adding and deleting markers. + + Jared Dominguez +
+ Leaflet Panel Layers + + Leaflet Control Layers extended for group of layers and icons legend + + Stefano Cudini +
+ Leaflet Categorized Layers + + Leaflet Control Layers extended for groups of categorized layers + + Robbie Trencheny +
+ Leaflet Navigation Toolbar + + Leaflet control for simple back, forward and home navigation. + + David C +
+ Leaflet LimitZoom + + Plugins to limit available zoom levels to a given list, either by + restricting zooming or by interpolating tiles. + + Ilya Zverev +
+ Leaflet Coordinates Control + + Captures mouseclick and displays its coordinates with easy way to copy them. + + Michal Zimmermann +
+ Leaflet GameController + + Interaction handler providing support for gamepads. + + Antoine Pultier +
+ Leaflet.MeasureAreaControl + + Control for measuring element's area. + + Ondrej Zvara +
+ Leaflet.twofingerZoom + + Interaction handler for touch devices enabling zooming out with a two finger tap. + + Adam Ratcliffe +
+ Leaflet Control Compass + + A leaflet control plugin to build a simple rotating compass + + Stefano Cudini +
+ Leaflet.Editable + + Lightweight fully customisable and controlable drawing/editing plugin. + + Yohan Boniface +
+ Leaflet.AccuratePosition + + Leaflet.AccuratePosition aims to provide a desired device location accuracy. + + Michael Schmidt-Voigt +
+ Leaflet Locationlist + + A control to jump between predefined locations and zooms. + + Ivan Ignatyev +
+ Leaflet.defaultextent + + A control that returns to the original start extent of the map. Similar to the HomeButton widget. + + Alex Nguyen +
+ Leaflet.buffer + + Enables buffering of shapes drawn with Leaflet.draw. + + Jonathan Skeate +
+ Leaflet.Bookmarks + + Control for adding and navigating between user-created bookmarks on the map. + + Alexander Milevski +
+ Leaflet.MapPaint + + Bitmap painting plugin designed for touch devices. + + Antoine Pultier +
+ Leaflet.ShowAll + + A control that can show a predefined extent while saving the current one so it can be jumped back to. + + Mor Yariv +
+ Leaflet.timeline + + Display arbitrary GeoJSON on a map with a timeline slider and play button. + + Jonathan Skeate +
+ Leaflet.ZoomBox + + A lightweight zoom box control: draw a box around the area you want to zoom to. Demo + + Brendan Ward +
+ L.Control.LineStringSelect + + Fast LineString(polyline) partial selection tool: select a stretch between two points in a complex path. Demo + + Alexander Milevski +
+ Leaflet.TimeDimension + + Add time dimension capabilities on a Leaflet map. Demos + + ICTS SOCIB +
+ Leaflet.Liveupdate + + Periodically ('live') update something on a map (Demo) + + Martijn Grendelman +
+ Leaflet.Messagebox + + Display a temporary text message on a map (Demo) + + Martijn Grendelman +
+ Leaflet.Path.Drag + + Drag handler and interaction for polygons and polylines (Demo) + + Alexander Milevski +
+ Leaflet.NACCoordinates + + Displays NAC coordinate of the mouse pointer on mouse move (Demo) + + Mahmood Dehghan +
+ Leaflet.zoomhome + + Zoom control with a home button for resetting the view (Demo) + + Florian Brucker +
+ + +### Other Plugins and Libraries + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PluginDescriptionMaintainer
+ OSM Buildings + + Amazing JS library for visualizing 3D OSM building geometry on top of Leaflet. + + Jan Marsch +
+ Maps Marker Pro + + A WordPress plugin that enables users to pin, organize and share their favorite places and tracks through their WordPress powered site. + + Robert Harm +
+ leaflet-hash + + Plugin for persisting map state and browsing history through the URL hash. + + Michael Lawrence Evans +
+ arc.js + + A JS library for drawing great circle routes that can be used with Leaflet. + + Dane Springmeyer +
+ Leaflet.Storage + + Create/update/delete Map, Marker, Polygon, Polyline... and expose them for backend storage with an API. + + Yohan Boniface +
+ Leaflet.CSS + + Add the main Leaflet CSS files (or any css) from within JavaScript, be gone conditional comments. + + Calvin Metcalf +
+ Leaflet.LayerIndex + + An efficient spatial index for features and layers, using RTree.js. + + Mathieu Leplatre +
+ Leaflet-pip + + Simple point in polygon calculation using point-in-polygon. + + Tom MacWright +
+ Leaflet.GeometryUtil + + A collection of utilities for Leaflet geometries (linear referencing, etc.) + + Benjamin Becquet, Mathieu Leplatre +
+ Leaflet.i18n + + Internationalization for Leaflet plugins. + + Yohan Boniface +
+ Leaflet.print + + Implements the Mapfish print protocol allowing a Leaflet map to be printed using either the Mapfish or GeoServer print module. + + Adam Ratcliffe +
+ Leaflet-easyPrint + + A simple leaflet plugin which adds an icon to print the map. The resulting print page will strip ot the other content on the page and only print the leaflet map div. + + Rowan Winsemius +
+ Leaflet-active-area + + This plugin allows you to use a smaller portion of the map as an active area. + All positionning methods (setView, fitBounds, setZoom) will be applied on this portion instead of the all map. + + Mappy +
+ WordPress Leaflet Map + + Interactive and flexible shortcode to create multiple maps in posts and pages, + and to add multiple markers on those maps. + + Benjamin J DeLong +
+ MapBBCode-related leaflet plugins + + Seven plugins for various features, independent of the MapBBCode library. + From circular and popup icons to buttons, layer switcher, better search and attribution. + + Ilya Zverev +
+ Leaflet Yeoman Generator + + Yeoman generator that scaffolds out a basic Leaflet map application. + + Moritz Klack +
+ Leaflet LayerConfig + + Provide a json file or service response with a configuration of layers and markers to automatically set up a Leaflet client. + + Alexander Nossum +
+ Leaflet ZoomLevel CSS Class + + Add zoom level css class to map element for easy style updates based on zoom levels + + Dag Jomar Mersland +
+ Greiner-Hormann + + Greiner-Hormann algorithm for polygon clipping and binary operations, adapted for use with Leaflet. + + Alexander Milevski +
+ Maptiks + + Analytics platform for web maps. Track map activities, layer load times, marker clicks, and more! + + Sparkgeo +
+ +To submit your own Leaflet plugin to this list, just send a pull request with the addition to Leaflet repo's [gh-pages branch](https://github.com/Leaflet/Leaflet/tree/gh-pages) (`plugins.md` file). + + diff --git a/docs/reference-tpl.html b/docs/reference-tpl.html new file mode 100644 index 00000000000..8c789bf7015 --- /dev/null +++ b/docs/reference-tpl.html @@ -0,0 +1,89 @@ + + + + +<Foo> name + + + + +

[class-name]

+ +

[description]

+ +
[code-example]
+ + + + +

Constructor

+ + + + + + + + + + + + +
ConstructorUsageDescription
[constructor]( [arguments] ) + new L.Map()
+ L.map() +
[description]
+ + + + +

Methods

+ + + + + + + + + + + +
MethodReturnsDescription
[method](<type> argument, <type> argument)[return-type][description]
+ + + + +

Options

+ + + + + + + + + + + + + + +
OptionTypeDefault valueDescription
[name][type][default][description]
+ + + +

Events

+ + + + + + + + + + + + +
EventDataDescription
[name][type][description]
diff --git a/docs/reference.html b/docs/reference.html new file mode 100644 index 00000000000..427b2182507 --- /dev/null +++ b/docs/reference.html @@ -0,0 +1,6646 @@ +--- +layout: default +title: Documentation +bodyclass: api-page +--- + +
+ +
+

UI Layers

+ +

Raster Layers

+ +

Vector Layers

+ +
+
+

Other Layers

+ +

Basic Types

+ +

Controls

+ +
+
+

Shared Methods

+ +

Utility

+ +

DOM Utility

+ +
+
+

Base Classes

+ + +

Misc

+ +
+
+ + + +
+

This reference reflects Leaflet 0.7. +Docs for 0.6 are available in the source form (see instructions for running docs).

+ +

Map

+ +

The central class of the API — it is used to create a map on a page and manipulate it.

+ +

Usage example

+ +
// initialize the map on the "map" div with a given center and zoom
+var map = L.map('map', {
+	center: [51.505, -0.09],
+	zoom: 13
+});
+ +

Creation

+ + + + + + + + + + + + +
FactoryDescription
L.map( + <HTMLElement|String> id, + <Map options> options? ) + Instantiates a map object given a div element (or its id) and optionally an object literal with map options described below.
+ + + + +

Options

+ +

Map State Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
centerLatLngnullInitial geographical center of the map.
zoomNumbernullInitial map zoom.
layersILayer[]nullLayers that will be added to the map initially.
minZoomNumbernullMinimum zoom level of the map. Overrides any minZoom set on map layers.
maxZoomNumbernullMaximum zoom level of the map. This overrides any maxZoom set on map layers.
maxBoundsLatLngBoundsnullWhen this option is set, the map restricts the view to the given geographical bounds, bouncing the user back when he tries to pan outside the view. To set the restriction dynamically, use setMaxBounds method
crsCRSL.CRS.
EPSG3857
Coordinate Reference System to use. Don't change this if you're not sure what it means.
rendererRendererdependsThe default method for drawing vector layers on the map. L.SVG or L.Canvas by default depending on browser support.
+ +

Interaction Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
draggingBooleantrueWhether the map be draggable with mouse/touch or not.
touchZoomBooleantrueWhether 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.
scrollWheelZoomBooleantrueWhether the map can be zoomed by using the mouse wheel. If passed 'center', it will zoom to the center of the view regardless of where the mouse was.
wheelDebounceTimeNumber40Limits the rate at which a wheel can fire (in milliseconds). By default user can't zoom via wheel more often than once per 40 ms.
doubleClickZoomBooleantrueWhether the map can be zoomed in by double clicking on it and zoomed out by double clicking while holding shift. If passed 'center', double-click zoom will zoom to the center of the view regardless of where the mouse was.
boxZoomBooleantrueWhether the map can be zoomed to a rectangular area specified by dragging the mouse while pressing shift.
tapBooleantrueEnables mobile hacks for supporting instant taps (fixing 200ms click delay on iOS/Android) and touch holds (fired as contextmenu events).
tapToleranceNumber15The max number of pixels a user can shift his finger during touch for it to be considered a valid tap.
trackResizeBooleantrueWhether the map automatically handles browser window resize to update itself.
worldCopyJumpBooleanfalseWith this option enabled, the map tracks when you pan to another "copy" of the world and seamlessly jumps to the original one so that all overlays like markers and vector layers are still visible.
closePopupOnClickBooleantrueSet it to false if you don't want popups to close when user clicks the map.
bounceAtZoomLimitsBooleantrueSet it to false if you don't want the map to zoom beyond min/max zoom and then bounce back when pinch-zooming.
+ +

Keyboard Navigation Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
keyboardBooleantrueMakes the map focusable and allows users to navigate the map with keyboard arrows and +/- keys.
keyboardPanOffsetNumber80Amount of pixels to pan when pressing an arrow key.
keyboardZoomOffsetNumber1Number of zoom levels to change when pressing + or - key.
+ +

Panning Inertia Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
inertiaBooleantrueIf enabled, panning of the map will have an inertia effect where the map builds momentum while dragging and continues moving in the same direction for some time. Feels especially nice on touch devices.
inertiaDecelerationNumber3000The rate with which the inertial movement slows down, in pixels/second2.
inertiaMaxSpeedNumber1500Max speed of the inertial movement, in pixels/second.
inertiaThresholdNumberdependsNumber of milliseconds that should pass between stopping the movement and releasing the mouse or touch to prevent inertial movement. 32 for touch devices and 14 for the rest by default.
+ +

Control options

+ + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
zoomControlBooleantrueWhether the zoom control is added to the map by default.
attributionControlBooleantrueWhether the attribution control is added to the map by default.
+ +

Animation options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
fadeAnimationBooleandependsWhether the tile fade animation is enabled. By default it's enabled in all browsers that support CSS3 Transitions except Android.
zoomAnimationBooleandependsWhether the tile zoom animation is enabled. By default it's enabled in all browsers that support CSS3 Transitions except Android.
zoomAnimationThresholdNumber4Won't animate zoom if the zoom difference exceeds this value.
markerZoomAnimationBooleandependsWhether markers animate their zoom with the zoom animation, if disabled they will disappear for the length of the animation. By default it's enabled in all browsers that support CSS3 Transitions except Android.
+ + +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
clickMouseEventFired when the user clicks (or taps) the map.
dblclickMouseEventFired when the user double-clicks (or double-taps) the map.
mousedownMouseEventFired when the user pushes the mouse button on the map.
mouseupMouseEventFired when the user releases the mouse button on the map.
mouseoverMouseEventFired when the mouse enters the map.
mouseoutMouseEventFired when the mouse leaves the map.
mousemoveMouseEventFired while the mouse moves over the map.
contextmenuMouseEventFired when the user pushes the right mouse button on the map, prevents default browser context menu from showing if there are listeners on this event. Also fired on mobile when the user holds a single touch for a second (also called long press).
focusEventFired when the user focuses the map either by tabbing to it or clicking/panning.
blurEventFired when the map loses focus.
preclickMouseEventFired before mouse click on the map (sometimes useful when you want something to happen on click before any existing click handlers start running).
loadEventFired when the map is initialized (when its center and zoom are set for the first time).
unloadEventFired when the map is destroyed with remove method.
viewresetEventFired when the map needs to redraw its content (this usually happens on map zoom or load). Very useful for creating custom overlays.
movestartEventFired when the view of the map starts changing (e.g. user starts dragging the map).
moveEventFired on any movement of the map view.
moveendEventFired when the view of the map ends changed (e.g. user stopped dragging the map).
dragstartEventFired when the user starts dragging the map.
dragEventFired repeatedly while the user drags the map.
dragendDragEndEventFired when the user stops dragging the map.
zoomstartEventFired when the map zoom is about to change (e.g. before zoom animation).
zoomendEventFired when the map zoom changes.
zoomlevelschangeEventFired when the number of zoomlevels on the map is changed due to adding or removing a layer.
resizeResizeEventFired when the map is resized.
autopanstartEventFired when the map starts autopanning when opening a popup.
layeraddLayerEventFired when a new layer is added to the map.
layerremoveLayerEventFired when some layer is removed from the map.
baselayerchangeLayerEvent + Fired when the base layer is changed through the layer control.
overlayaddLayerEvent + Fired when an overlay is selected through the layer control.
overlayremoveLayerEvent + Fired when an overlay is deselected through the layer control.
locationfoundLocationEvent + Fired when geolocation (using the locate method) went successfully.
locationerrorErrorEvent + Fired when geolocation (using the locate method) failed.
popupopenPopupEventFired when a popup is opened (using openPopup method).
popupclosePopupEventFired when a popup is closed (using closePopup method).
+ + +

Methods for Modifying Map State

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
setView( + <LatLng> center, + <Number> zoom?, + <zoom/pan options> options? ) + thisSets the view of the map (geographical center and zoom) with the given animation options.
setZoom( + <Number> zoom, + <zoom options> options? ) + thisSets the zoom of the map.
zoomIn( + <Number> delta?, + <zoom options> options? ) + thisIncreases the zoom of the map by delta (1 by default).
zoomOut( + <Number> delta?, + <zoom options> options? ) + thisDecreases the zoom of the map by delta (1 by default).
setZoomAround( + <LatLng> latlng, + <Number> zoom, + <zoom options> options? ) + thisZooms the map while keeping a specified point on the map stationary (e.g. used internally for scroll zoom and double-click zoom).
fitBounds( + <LatLngBounds> bounds, + <fitBounds options> options? ) + thisSets a map view that contains the given geographical bounds with the maximum zoom level possible.
fitWorld( + <fitBounds options> options? ) + thisSets a map view that mostly contains the whole world with the maximum zoom level possible.
panTo( + <LatLng> latlng, + <pan options> options? ) + thisPans the map to a given center. Makes an animated pan if new center is not more than one screen away from the current one.
panInsideBounds( + <LatLngBounds> bounds, + <pan options> options? ) + thisPans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
panBy( + <Point> point, + <pan options> options? ) + thisPans the map by a given number of pixels (animated).
invalidateSize( + <Boolean> animate ) + thisChecks if the map container size changed and updates the map if so — call it after you've changed the map size dynamically, also animating pan by default.
invalidateSize( + <zoom/pan options> options ) + thisChecks if the map container size changed and updates the map if so — call it after you've changed the map size dynamically, also animating pan by default. If options.pan is false, panning will not occur. If options.debounceMoveend is true, it will delay moveend event so that it doesn't happen often even if the method is called many times in a row.
setMaxBounds( + <LatLngBounds> bounds + thisRestricts the map view to the given bounds (see map maxBounds option).
locate( + <Locate options> options? ) + thisTries to locate the user using the Geolocation API, firing a locationfound event with location data on success or a locationerror event on failure, and optionally sets the map view to the user's location with respect to detection accuracy (or to the world view if geolocation failed). See Locate options for more details.
stopLocate()thisStops watching location previously initiated by map.locate({watch: true}) and aborts resetting the map view if map.locate was called with {setView: true}.
remove()thisDestroys the map and clears all related event listeners.
+ +

Methods for Getting Map State

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
getCenter()LatLngReturns the geographical center of the map view.
getZoom()NumberReturns the current zoom of the map view.
getMinZoom()NumberReturns the minimum zoom level of the map.
getMaxZoom()NumberReturns the maximum zoom level of the map.
getBounds()LatLngBoundsReturns the LatLngBounds of the current map view.
getBoundsZoom( + <LatLngBounds> bounds, + <Boolean> inside? ) + NumberReturns the maximum zoom level on which the given bounds fit to the map view in its entirety. If inside (optional) is set to true, the method instead returns the minimum zoom level on which the map view fits into the given bounds in its entirety.
getSize()PointReturns the current size of the map container.
getPixelBounds()BoundsReturns the bounds of the current map view in projected pixel coordinates (sometimes useful in layer and overlay implementations).
getPixelOrigin()PointReturns the projected pixel coordinates of the top left point of the map layer (useful in custom layer and overlay implementations).
+ +

Methods for Layers and Controls

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addLayer( + <ILayer> layer, + thisAdds the given layer to the map.
removeLayer( + <ILayer> layer ) + thisRemoves the given layer from the map.
hasLayer( + <ILayer> layer ) + BooleanReturns true if the given layer is currently added to the map.
eachLayer( + <Function> fn, + <Object> context? ) + thisIterates over the layers of the map, optionally specifying context of the iterator function. +
map.eachLayer(function (layer) {
+	layer.bindPopup('Hello');
+});
+
openPopup( + <Popup> popup ) + thisOpens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
openPopup( + <String> html | <HTMLElement> el, + <LatLng> latlng, + <Popup options> options? ) + thisCreates a popup with the specified options and opens it in the given point on a map.
closePopup( + <Popup> popup? ) + thisCloses the popup previously opened with openPopup (or the given one).
addControl( + <IControl> control ) + thisAdds the given control to the map.
removeControl( + <IControl> control ) + thisRemoves the given control from the map.
getRenderer( + <ILayer> layer) + RendererReturns the renderer for the given layer.
+ + +

Conversion Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
latLngToLayerPoint( + <LatLng> latlng ) + PointReturns the map layer point that corresponds to the given geographical coordinates (useful for placing overlays on the map).
layerPointToLatLng( + <Point> point ) + LatLngReturns the geographical coordinates of a given map layer point.
containerPointToLayerPoint( + <Point> point ) + PointConverts the point relative to the map container to a point relative to the map layer.
layerPointToContainerPoint( + <Point> point ) + PointConverts the point relative to the map layer to a point relative to the map container.
latLngToContainerPoint( + <LatLng> latlng ) + PointReturns the map container point that corresponds to the given geographical coordinates.
containerPointToLatLng( + <Point> point ) + LatLngReturns the geographical coordinates of a given map container point.
project( + <LatLng> latlng, + <Number> zoom? ) + PointProjects the given geographical coordinates to absolute pixel coordinates for the given zoom level (current zoom level by default).
unproject( + <Point> point, + <Number> zoom? ) + LatLngProjects the given absolute pixel coordinates to geographical coordinates for the given zoom level (current zoom level by default).
mouseEventToContainerPoint( + <MouseEvent> event ) + PointReturns the pixel coordinates of a mouse click (relative to the top left corner of the map) given its event object.
mouseEventToLayerPoint( + <MouseEvent> event ) + PointReturns the pixel coordinates of a mouse click relative to the map layer given its event object. +
mouseEventToLatLng( + <MouseEvent> event ) + LatLngReturns the geographical coordinates of the point the mouse clicked on given the click's event object.
+ +

Other Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
getContainer()HTMLElementReturns the container element of the map.
createPane( + <String> name, <HTMLElement> contianer? + )MapPaneCreates a pane with the given name. Created panes will be given a generated class based on the name like .leaflet-pane-name"
getPane( + <String> name + )MapPaneReturns the HTML element representing the named map pane.
getPanes()MapPanesReturns an object with different map panes (to render overlays in).
whenReady( + <Function> fn, + <Object> context? )thisRuns the given callback when the map gets initialized with a place and zoom, or immediately if it happened already, optionally passing a function context.
+ +

Locate options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
watchBooleanfalseIf true, starts continous watching of location changes (instead of detecting it once) using W3C watchPosition method. You can later stop watching using map.stopLocate() method.
setViewBooleanfalseIf true, automatically sets the map view to the user location with respect to detection accuracy, or to world view if geolocation failed.
maxZoomNumberInfinityThe maximum zoom for automatic view setting when using `setView` option.
timeoutNumber10000Number of milliseconds to wait for a response from geolocation before firing a locationerror event.
maximumAgeNumber0Maximum age of detected location. If less than this amount of milliseconds passed since last geolocation response, locate will return a cached location.
enableHighAccuracyBooleanfalseEnables high accuracy, see description in the W3C spec.
+ + +

Zoom/pan options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
resetBooleanfalseIf true, the map view will be completely reset (without any animations).
panpan options-Sets the options for the panning (without the zoom change) if it occurs.
zoomzoom options-Sets the options for the zoom change if it occurs.
animateBoolean-An equivalent of passing animate to both zoom and pan options (see below).
+ +

Pan options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
animateBoolean-If true, panning will always be animated if possible. If false, it will not animate panning, either resetting the map view if panning more than a screen away, or just setting a new offset for the map pane (except for `panBy` which always does the latter).
durationNumber0.25Duration of animated panning.
easeLinearityNumber0.25The curvature factor of panning animation easing (third parameter of the Cubic Bezier curve). 1.0 means linear animation, the less the more bowed the curve.
noMoveStartBooleanfalseIf true, panning won't fire movestart event on start (used internally for panning inertia).
+ +

Zoom options

+ + + + + + + + + + + + + + +
OptionTypeDefaultDescription
animateBoolean-If not specified, zoom animation will happen if the zoom origin is inside the current view. If true, the map will attempt animating zoom disregarding where zoom origin is. Setting false will make it always reset the view completely without animation.
+ +

fitBounds options

+ +

The same as zoom/pan options and additionally:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
paddingTopLeftPoint[0, 0] + Sets the amount of padding in the top left corner of a map container that shouldn't be accounted for when setting the view to fit bounds. Useful if you have some control overlays on the map like a sidebar and you don't want them to obscure objects you're zooming to.
paddingBottomRightPoint[0, 0] + The same for bottom right corner of the map.
paddingPoint[0, 0] + Equivalent of setting both top left and bottom right padding to the same value.
maxZoomNumbernullThe maximum possible zoom to use.
+ + +

Properties

+ +

Map properties include interaction handlers that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging or touch zoom (see IHandler methods). Example:

+ +
map.doubleClickZoom.disable();
+ +

You can also access default map controls like attribution control through map properties:

+ +
map.attributionControl.addAttribution("Earthquake data &copy; GeoNames");
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
draggingIHandlerMap dragging handler (by both mouse and touch).
touchZoomIHandlerTouch zoom handler.
doubleClickZoomIHandlerDouble click zoom handler.
scrollWheelZoomIHandlerScroll wheel zoom handler.
boxZoomIHandlerBox (shift-drag with mouse) zoom handler.
keyboardIHandlerKeyboard navigation handler.
tapIHandlerMobile touch hacks (quick tap and touch hold) handler.
zoomControlControl.ZoomZoom control.
attributionControlControl.AttributionAttribution control.
+ + +

Map Panes

+ +

Panes are DOM elements used to control the ordering of layers on the map. You can access panes with map.getPane or map.getPanesmethods. New panes can be created with the map.createPane method.

+ +

Every map has the following panes that differ only in zIndex.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PaneTypeZ IndexDescription
mapPaneHTMLElement'auto'Pane that contains all other map panes.
tilePaneHTMLElement2Pane for tile layers.
overlayPaneHTMLElement4Pane for overlays like polylines and polygons.
shadowPaneHTMLElement5Pane for overlay shadows (e.g. marker shadows).
markerPaneHTMLElement6Pane for marker icons.
popupPaneHTMLElement7Pane for popups.
+ + +

Marker

+ +

Used to put markers on the map. Extends Layer.

+ +
L.marker([50.5, 30.5]).addTo(map);
+ +

Creation

+ + + + + + + + + + + + +
FactoryDescription
L.marker( + <LatLng> latlng, + <Marker options> options? ) + Instantiates a Marker object given a geographical point and optionally an options object.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
iconL.Icon*Icon class to use for rendering the marker. See Icon documentation for details on how to customize the marker icon. Set to new L.Icon.Default() by default.
interactiveBooleantrueIf false, the marker will not emit mouse events and will act as a part of the underlying map.
draggableBooleanfalseWhether the marker is draggable with mouse/touch or not.
keyboardBooleantrueWhether the marker can be tabbed to with a keyboard and clicked by pressing enter.
titleString''Text for the browser tooltip that appear on marker hover (no tooltip by default).
altString''Text for the alt attribute of the icon image (useful for accessibility).
zIndexOffsetNumber0By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like 1000 (or high negative value, respectively).
opacityNumber1.0The opacity of the marker.
riseOnHoverBooleanfalseIf true, the marker will get on top of others when you hover the mouse over it.
riseOffsetNumber250The z-index offset used for the riseOnHover feature.
paneString'markerPane'Map pane where the markers icon will be added.
shadowPaneString'shadowPane'Map pane where the markers shadow will be added.
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
clickMouseEventFired when the user clicks (or taps) the marker.
dblclickMouseEventFired when the user double-clicks (or double-taps) the marker.
mousedownMouseEventFired when the user pushes the mouse button on the marker.
mouseoverMouseEventFired when the mouse enters the marker.
mouseoutMouseEventFired when the mouse leaves the marker.
contextmenuMouseEvent + Fired when the user right-clicks on the marker.
dragstartEventFired when the user starts dragging the marker.
dragEventFired repeatedly while the user drags the marker.
dragendDragEndEventFired when the user stops dragging the marker.
moveEventFired when the marker is moved via setLatLng. Old and new coordinates are included in event arguments as oldLatLng, latlng.
addEventFired when the marker is added to the map.
removeEventFired when the marker is removed from the map.
popupopenPopupEventFired when a popup bound to the marker is open.
popupclosePopupEventFired when a popup bound to the marker is closed.
+ +

Methods

+ +

In addition to shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
getLatLng()LatLngReturns the current geographical position of the marker.
setLatLng( + <LatLng> latlng ) + thisChanges the marker position to the given point.
setIcon( + <Icon> icon ) + thisChanges the marker icon.
setZIndexOffset( + <Number> offset ) + thisChanges the zIndex offset of the marker.
setOpacity( + <Number> opacity ) + thisChanges the opacity of the marker.
update() + thisUpdates the marker position, useful if coordinates of its latLng object were changed directly.
bindPopup( + <String> html | <HTMLElement> el | <Popup> popup, + <Popup options> options? ) + thisBinds a popup with a particular HTML content to a click on this marker. You can also open the bound popup with the Marker openPopup method.
unbindPopup()thisUnbinds the popup previously bound to the marker with bindPopup.
openPopup()thisOpens the popup previously bound by the bindPopup method.
getPopup()PopupReturns the popup previously bound by the bindPopup method.
closePopup()thisCloses the bound popup of the marker if it's opened.
togglePopup()thisToggles the popup previously bound by the bindPopup method.
setPopupContent( + <String> html | <HTMLElement> el ) + thisSets an HTML content of the popup of this marker.
toGeoJSON()ObjectReturns a GeoJSON representation of the marker (GeoJSON Point Feature).
+ +

Interaction handlers

+ +

Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see IHandler methods). Example:

+ +
marker.dragging.disable();
+ + + + + + + + + + + + +
PropertyTypeDescription
draggingIHandlerMarker dragging handler (by both mouse and touch).
+ + + + + +

Used to open popups in certain places of the map. Use Map#openPopup to open popups while making sure that only one popup is open at one time (recommended for usability), or use Map#addLayer to open as many as you want.

+ +

Usage example

+

If you want to just bind a popup to marker click and then open it, it's really easy:

+
marker.bindPopup(popupContent).openPopup();
+

Path overlays like polylines also have a bindPopup method. Here's a more complicated way to open a popup on a map:

+ +
var popup = L.popup()
+	.setLatLng(latlng)
+	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
+	.openOn(map);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.popup( + <Popup options> options?, + <ILayer> source? ) + 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 ILayer to which it refers.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
maxWidthNumber300Max width of the popup.
minWidthNumber50Min width of the popup.
maxHeightNumbernullIf set, creates a scrollable container of the given height inside a popup if its content exceeds it.
autoPanBooleantrueSet it to false if you don't want the map to do panning animation to fit the opened popup.
keepInViewBooleanfalseSet it to true if you want to prevent users from panning the popup off of the screen while it is open.
closeButtonBooleantrueControls the presense of a close button in the popup.
offsetPointPoint(0, 6) + The offset of the popup position. Useful to control the anchor of the popup when opening it on some overlays.
autoPanPaddingTopLeftPointnullThe margin between the popup and the top left corner of the map view after autopanning was performed.
autoPanPaddingBottomRightPointnullThe margin between the popup and the bottom right corner of the map view after autopanning was performed.
autoPanPaddingPointPoint(5, 5) + Equivalent of setting both top left and bottom right autopan padding to the same value.
zoomAnimationBooleantrueWhether to animate the popup on zoom. Disable it if you have problems with Flash content inside popups.
closeOnClickBooleannullSet it to false if you want to override the default behavior of the popup closing when user clicks the map (set globally by the Map closePopupOnClick option).
classNameString''A custom class name to assign to the popup.
+ +

Events

+ + + + + + + + + + + + + + + + + +
EventDataDescription
addedPopupEventFired when the popup is added to the map.
removedPopupEventFired when the popup is removed from the map.
+ +

Methods

+ +

In addition to shared layer methods like addTo() and remove() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
openOn( + <Map> map ) + thisAdds the popup to the map and closes the previous one. The same as map.openPopup(popup).
setLatLng( + <LatLng> latlng ) + thisSets the geographical point where the popup will open.
getLatLng()LatLngReturns the geographical point of popup.
setContent( + <String|HTMLElement> htmlContent ) + thisSets the HTML content of the popup.
getContent()<String|HTMLElement>Returns the content of the popup.
update()thisUpdates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
+ + + +

TileLayer

+ +

Used to load and display tile layers on the map. Extends Layer.

+ +

Usage example

+ +
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.tileLayer( + <String> urlTemplate, + <TileLayer options> options? ) + Instantiates a tile layer object given a URL template and optionally an options object.
+ +

URL template

+ +

A string of the following form:

+ +
'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
+ +

{s} means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; a, b or c by default, can be omitted), {z} — zoom level, {x} and {y} — tile coordinates. {r} can be used to add @2x to the URL to load retina tiles.

+ +

You can use custom keys in the template, which will be evaluated from TileLayer options, like this:

+ +
L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
minZoomNumber0Minimum zoom number.
maxZoomNumber18Maximum zoom number.
maxNativeZoomNumbernullMaximum zoom number the tiles source has available. If it is specified, the tiles on all zoom levels higher than maxNativeZoom will be loaded from maxZoom level and auto-scaled.
tileSizeNumber256Tile size (width and height in pixels, assuming tiles are square).
subdomainsString or String[]'abc'Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
errorTileUrlString''URL to the tile image to show in place of the tile that failed to load.
attributionString''e.g. "© Mapbox" — the string used by the attribution control, describes the layer data.
tmsBooleanfalseIf true, inverses Y axis numbering for tiles (turn this on for TMS services).
continuousWorldBooleanfalseIf set to true, the tile coordinates won't be wrapped by world width (-180 to 180 longitude) or clamped to lie within world height (-90 to 90). Use this if you use Leaflet for maps that don't reflect the real world (e.g. game, indoor or photo maps).
noWrapBooleanfalseIf set to true, the tiles just won't load outside the world width (-180 to 180 longitude) instead of repeating.
zoomOffsetNumber0The zoom number used in tile URLs will be offset with this value.
zoomReverseBooleanfalseIf set to true, the zoom number used in tile URLs will be reversed (maxZoom - zoom instead of zoom)
opacityNumber1.0The opacity of the tile layer.
zIndexNumbernullThe explicit zIndex of the tile layer. Not set by default.
updateIntervalNumber200Tiles will not update more then once every updateInterval.
unloadInvisibleTilesBooleandependsIf true, all the tiles that are not visible after panning are removed (for better performance). true by default on mobile WebKit, otherwise false.
updateWhenIdleBooleandependsIf false, new tiles are loaded during panning, otherwise only after it (for better performance). true by default on mobile WebKit, otherwise false.
detectRetinaBooleanfalseIf true and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
reuseTilesBooleanfalseIf true, all the tiles that are not visible after panning are placed in a reuse queue from which they will be fetched when new tiles become visible (as opposed to dynamically creating new ones). This will in theory keep memory usage low and eliminate the need for reserving new memory whenever a new tile is needed.
boundsLatLngBoundsnullWhen this option is set, the TileLayer only loads tiles that are in the given geographical bounds.
crossOriginBooleanfalseIf true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
loadingEventFired when the tile layer starts loading tiles.
loadEventFired when the tile layer loaded all visible tiles.
tileloadstartTileEventFired when a tile is requested and starts loading.
tileloadTileEventFired when a tile loads.
tileunloadTileEventFired when a tile is removed (e.g. when you have unloadInvisibleTiles on).
tileerrorTileEventFired when there is an error loading a tile.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addTo( + <Map> map ) + thisAdds the layer to the map.
bringToFront()thisBrings the tile layer to the top of all tile layers.
bringToBack()thisBrings the tile layer to the bottom of all tile layers.
setOpacity( + <Number> opacity ) + thisChanges the opacity of the tile layer.
setZIndex( + <Number> zIndex ) + thisSets the zIndex of the tile layer.
redraw()thisCauses the layer to clear all the tiles and request them again.
setUrl( + <String> urlTemplate ) + thisUpdates the layer's URL template and redraws it.
getContainer() + HTMLElementReturns the HTML element that contains the tiles for this layer.
+ + + +

TileLayer.WMS

+ +

Used to display WMS services as tile layers on the map. Extends TileLayer.

+ +

Usage example

+ +
var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
+	layers: 'nexrad-n0r-900913',
+	format: 'image/png',
+	transparent: true,
+	attribution: "Weather data © 2012 IEM Nexrad"
+});
+ +

Creation

+ + + + + + + + + + +
FactoryDescription
L.tileLayer.wms( + <String> baseUrl, + <TileLayer.WMS options> options ) + + + Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
+ +

Options

+ +

Includes all TileLayer options and additionally:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
layersString''(required) Comma-separated list of WMS layers to show.
stylesString''Comma-separated list of WMS styles.
formatString'image/jpeg'WMS image format (use 'image/png' for layers with transparency).
transparentBooleanfalseIf true, the WMS service will return images with transparency.
versionString'1.1.1'Version of the WMS service to use.
crsCRSnullCoordinate Reference System to use for the WMS requests, defaults to map CRS. Don't change this if you're not sure what it means.
uppercaseBooleanfalseIf true, WMS request parameter keys will be uppercase.
+ +

Methods

+ + + + + + + + + + + + +
MethodReturnsDescription
setParams( + <WMS parameters> params, + <Boolean> noRedraw? ) + thisMerges an object with the new parameters and re-requests tiles on the current screen (unless noRedraw was set to true).
+ + +

ImageOverlay

+ +

Used to load and display a single image over specific bounds of the map. Extends Layer.

+ +

Usage example

+ +
var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
+	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
+
+L.imageOverlay(imageUrl, imageBounds).addTo(map);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.ImageOverlay( + <String> imageUrl, + <LatLngBounds> bounds, + <ImageOverlay options> options? ) + Instantiates an image overlay object given the URL of the image and the geographical bounds it is tied to.
+ +

Options

+ + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
opacityNumber1.0The opacity of the image overlay.
attributionString''The attribution text of the image overlay.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addTo( + <Map> map ) + thisAdds the overlay to the map.
setOpacity( + <Number> opacity ) + thisSets the opacity of the overlay.
setUrl( + <String> imageUrl ) + thisChanges the URL of the image.
bringToFront()thisBrings the layer to the top of all overlays.
bringToBack()thisBrings the layer to the bottom of all overlays.
+ + +

Path

+

An abstract class that contains options and constants shared between vector overlays (Polygon, Polyline, Circle). Do not use it directly. Extends Layer. + +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
strokeBooleantrueWhether to draw stroke along the path. Set it to false to disable borders on polygons or circles.
colorString'#3388ff'Stroke color.
weightNumber3Stroke width in pixels.
opacityNumber1Stroke opacity.
fillBooleandependsWhether to fill the path with color. Set it to false to disable filling on polygons or circles.
fillColorStringsame as colorFill color.
fillOpacityNumber0.2Fill opacity.
dashArrayStringnullA string that defines the stroke dash pattern. Doesn't work on canvas-powered layers (e.g. Android 2).
lineCapString'round'A string that defines shape to be used at the end of the stroke.
lineJoinString'round'A string that defines shape to be used at the corners of the stroke.
interactiveBooleantrueIf false, the vector will not emit mouse events and will act as a part of the underlying map.
rendererRendererdependsL.SVG or L.Canvas by default depending on browser support.
pointerEventsStringnullSets the pointer-events attribute on the path if SVG renderer is used.
classNameString''Custom class name set on an element.
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
clickMouseEventFired when the user clicks (or taps) the object.
dblclickMouseEventFired when the user double-clicks (or double-taps) the object.
mousedownMouseEventFired when the user pushes the mouse button on the object.
mouseoverMouseEventFired when the mouse enters the object.
mouseoutMouseEventFired when the mouse leaves the object.
contextmenuMouseEventFired when the user pushes the right mouse button on the object, prevents default browser context menu from showing if there are listeners on this event.
addEvent + Fired when the path is added to the map.
removeEventFired when the path is removed from the map.
popupopenPopupEventFired when a popup bound to the path is open.
popupclosePopupEventFired when a popup bound to the path is closed.
+ +

Methods

+ +

In addition to shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
setStyle( + <Path options> object ) + thisChanges the appearance of a Path based on the options in the Path options object.
getBounds()LatLngBoundsReturns the LatLngBounds of the path.
bringToFront()thisBrings the layer to the top of all path layers.
bringToBack()thisBrings the layer to the bottom of all path layers.
redraw()thisRedraws the layer. Sometimes useful after you changed the coordinates that the path uses.
+ +

Static properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstantTypeValueDescription
SVGBooleandependsTrue if SVG is used for vector rendering (true for most modern browsers).
VMLBooleandependsTrue if VML is used for vector rendering (IE 6-8).
CANVASBooleandependsTrue if Canvas is used for vector rendering (Android 2). You can also force this by setting global variable L_PREFER_CANVAS to true before the Leaflet include on your page — sometimes it can increase performance dramatically when rendering thousands of circle markers, but currently suffers from a bug that causes removing such layers to be extremely slow.
CLIP_PADDINGNumber0.5 for SVG
0.02 for VML
How much to extend the clip area around the map view (relative to its size, e.g. 0.5 is half the screen in each direction). Smaller values mean that you will see clipped ends of paths while you're dragging the map, and bigger values decrease drawing performance.
+ + +

Polyline

+ +

A class for drawing polyline overlays on a map. Extends Path. Use Map#addLayer to add it to the map.

+ +

Usage example

+
// create a red polyline from an array of LatLng points
+var latlngs = [
+  [-122.68, 45.51],
+  [-122.43, 37.77],
+  [-118.2, 34.04]
+];
+
+var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
+
+// zoom the map to the polyline
+map.fitBounds(polyline.getBounds());
+ +

You can also pass a multi-dimensional array to represent a MultiPolyline shape:

+ +
// create a red polyline from an array of arrays of LatLng points
+var latlngs = [
+  [[-122.68, 45.51],
+   [-122.43, 37.77],
+   [-118.2, 34.04]],
+  [[-73.91, 40.78],
+   [-87.62, 41.83],
+   [-96.72, 32.76]]
+];
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.polyline( + <LatLng[]> latlngs, + <Polyline options> options? ) + Instantiates a polyline object given an array of geographical points and optionally an options object. You can create a Polyline object with multiple separate lines (MultiPolyline) by passing an array of arrays of geographic points.
+ +

Options

+ +

You can use Path options and additionally the following options:

+ + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
smoothFactorNumber1.0How much to simplify the polyline on each zoom level. More means better performance and smoother look, and less means more accurate representation.
noClipBooleanfalseDisabled polyline clipping.
+ +

Methods

+ +

In addition to Path methods like redraw() and bringToFront(), shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addLatLng( + <LatLng> latlng ) + thisAdds a given point to the polyline.
setLatLngs( + <LatLng[]> latlngs ) + thisReplaces all the points in the polyline with the given array of geographical points.
getLatLngs()LatLng[]Returns an array of the points in the path.
spliceLatLngs( + <Number> index, + <Number> pointsToRemove, + <LatLng> latlng?, … ) + LatLng[]Allows adding, removing or replacing points in the polyline. Syntax is the same as in Array#splice. Returns the array of removed points (if any).
getBounds()LatLngBoundsReturns the LatLngBounds of the polyline.
getCenter()LatLngReturns the center (centroid) of the polyline.
toGeoJSON()ObjectReturns a GeoJSON representation of the polyline (GeoJSON LineString Feature).
+ + +

Polygon

+ +

A class for drawing polygon overlays on a map. Extends Polyline. Use Map#addLayer to add it to the map.

+ +

Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.

+ +

Usage example

+
// create a red polygon from an array of LatLng points
+var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
+
+var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
+
+// zoom the map to the polygon
+map.fitBounds(polygon.getBounds());
+ +

You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:

+ +

+var latlngs = [
+  [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
+  [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
+];
+ +

Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.

+ +

+var latlngs = [
+  [ // first polygon
+    [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
+    [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
+  ],
+  [ // second polygon
+    [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
+  ]
+];
+ + +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.polygon( + <LatLng[]> latlngs, + <Polyline options> options? ) + Instantiates a polygon object given an array of geographical points and optionally an options object (the same as for Polyline). You can also create a polygon with holes by passing an array of arrays of latlngs, with the first latlngs array representing the exterior ring while the remaining represent the holes inside. You can create a a Polygon with multiple separate shapes (MultiPolygon) by passing an array of Polygon coordinates.
+ +

Methods

+ +

In addition to Path methods like redraw() and bringToFront(), Polyline mehtods like setLatLngs() and getLatLngs(), shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + +
MethodReturnsDescription
toGeoJSON()ObjectReturns a GeoJSON representation of the polygon (GeoJSON Polygon Feature).
+ + + +

Rectangle

+ +

A class for drawing rectangle overlays on a map. Extends Polygon. Use Map#addLayer to add it to the map.

+ +

Usage example

+
// define rectangle geographical bounds
+var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
+
+// create an orange rectangle
+L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
+
+// zoom the map to the rectangle bounds
+map.fitBounds(bounds);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.rectangle( + <LatLngBounds> bounds, + <Path options> options? ) + Instantiates a rectangle object with the given geographical bounds and optionally an options object.
+ +

Methods

+ +

In addition to Path methods like redraw() and bringToFront(), shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + +
MethodReturnsDescription
setBounds( + <LatLngBounds> bounds ) + thisRedraws the rectangle with the passed bounds.
+ + +

Circle

+ +

A class for drawing circle overlays on a map. Extends CircleMarker. Use Map#addLayer to add it to the map.

+ +
L.circle([50.5, 30.5], 200).addTo(map);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.circle( + <LatLng> latlng, + <Number> radius, + <Path options> options? ) + Instantiates a circle object given a geographical point, a radius in meters and optionally an options object.
+ +

Methods

+ +

In addition to Path methods like redraw() and bringToFront(), shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
getLatLng()LatLngReturns the current geographical position of the circle.
getRadius()NumberReturns the current radius of a circle. Units are in meters.
setLatLng( + <LatLng> latlng ) + thisSets the position of a circle to a new location.
setRadius( + <Number> radius ) + thisSets the radius of a circle. Units are in meters.
toGeoJSON()ObjectReturns a GeoJSON representation of the circle (GeoJSON Point Feature).
+ + + +

CircleMarker

+ +

A circle of a fixed size with radius specified in pixels. Extends Path. Use Map#addLayer to add it to the map.

+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.circleMarker( + <LatLng> latlng, + <Path options> options? ) + Instantiates a circle marker given a geographical point and optionally an options object. The default radius is 10 and can be altered by passing a "radius" member in the path options object.
+ +

Methods

+ +

In addition to Path methods like redraw() and bringToFront(), shared layer methods like addTo() and remove() and popup methods like bindPopup() you can also use the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
setLatLng( + <LatLng> latlng ) + thisSets the position of a circle marker to a new location.
setRadius( + <Number> radius ) + thisSets the radius of a circle marker. Units are in pixels.
toGeoJSON()ObjectReturns a GeoJSON representation of the circle marker (GeoJSON Point Feature).
+ + + +

LayerGroup

+ +

Used to group several layers and handle them as one. If you add it to the map, any layers added or removed from the group will be added/removed on the map as well. Extends Layer.

+ +
L.layerGroup([marker1, marker2])
+	.addLayer(polyline)
+	.addTo(map);
+ +

Creation

+ + + + + + + + + + + + +
FactoryDescription
L.LayerGroup( + <ILayer[]> layers? ) + Create a layer group, optionally given an initial set of layers.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addTo( + <Map> map ) + thisAdds the group of layers to the map.
addLayer( + <ILayer> layer ) + thisAdds a given layer to the group.
removeLayer( + <ILayer> layer ) + thisRemoves a given layer from the group.
removeLayer( + <String> id ) + thisRemoves a given layer of the given id from the group.
hasLayer( + <ILayer> layer ) + BooleanReturns true if the given layer is currently added to the group.
getLayer( + <String> id ) + ILayerReturns the layer with the given id.
getLayers()ArrayReturns an array of all the layers added to the group.
clearLayers()thisRemoves all the layers from the group.
eachLayer( + <Function> fn, + <Object> context? ) + thisIterates over the layers of the group, optionally specifying context of the iterator function. +
group.eachLayer(function (layer) {
+	layer.bindPopup('Hello');
+});
+
toGeoJSON()ObjectReturns a GeoJSON representation of the layer group (GeoJSON FeatureCollection).
+ + + +

FeatureGroup

+ +

Extended layerGroup that also has mouse events (propagated from members of the group) and a shared bindPopup method. Extends Layer.

+ +
L.featureGroup([marker1, marker2, polyline])
+	.bindPopup('Hello world!')
+	.on('click', function() { alert('Clicked on a group!'); })
+	.addTo(map);
+ +

Creation

+ + + + + + + + + + + + + + +
FactoryDescription
L.featureGroup( + <ILayer[]> layers? ) + Create a layer group, optionally given an initial set of layers.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
bindPopup( + <String> htmlContent, + <Popup options> options? ) + thisBinds a popup with a particular HTML content to a click on any layer from the group that has a bindPopup method.
getBounds()LatLngBoundsReturns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
setStyle( + <Path options> style ) + thisSets the given path options to each layer of the group that has a setStyle method.
bringToFront()thisBrings the layer group to the top of all other layers.
bringToBack()thisBrings the layer group to the bottom of all other layers.
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
clickMouseEventFired when the user clicks (or taps) the group.
dblclickMouseEventFired when the user double-clicks (or double-taps) the group.
mouseoverMouseEventFired when the mouse enters the group.
mouseoutMouseEventFired when the mouse leaves the group.
mousemoveMouseEventFired while the mouse moves over the layers of the group.
contextmenuMouseEventFired when the user right-clicks on one of the layers.
layeraddLayerEvent + Fired when a layer is added to the group.
layerremoveLayerEvent + Fired when a layer is removed from the map.
+ + +

GeoJson

+ +

Represents a GeoJSON layer. Allows you to parse GeoJSON data and display it on the map. Extends FeatureGroup.

+ +
L.geoJson(data, {
+	style: function (feature) {
+		return {color: feature.properties.color};
+	},
+	onEachFeature: function (feature, layer) {
+		layer.bindPopup(feature.properties.description);
+	}
+}).addTo(map);
+ +

Each feature layer created by it gets a feature property that links to the GeoJSON feature data the layer was created from (so that you can access its properties later).

+ +

Creation

+ + + + + + + + + + + +
FactoryDescription
L.geoJson( + <Object> geojson?, + <GeoJSON options> options? ) + Creates a GeoJSON layer. Optionally accepts an object in GeoJSON format to display on the map (you can alternatively add it later with addData method) and an options object.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescription
pointToLayer( + <GeoJSON> featureData, + <LatLng> latlng ) + Function that will be used for creating layers for GeoJSON points (if not specified, simple markers will be created).
style( + <GeoJSON> featureData ) + Function that will be used to get style options for vector layers created for GeoJSON features.
onEachFeature( + <GeoJSON> featureData, + <ILayer> layer ) + Function that will be called on each created feature layer. Useful for attaching events and popups to features.
filter( + <GeoJSON> featureData, + <ILayer> layer ) + Function that will be used to decide whether to show a feature or not.
coordsToLatLng( + <Array> coords ) + Function that will be used for converting GeoJSON coordinates to LatLng points (if not specified, coords will be assumed to be WGS84 — standard [longitude, latitude] values in degrees).
+ +

Additionally accepts all Path options for polylines and polygons.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addData( + <GeoJSON> data ) + thisAdds a GeoJSON object to the layer.
setStyle( + <Function> style ) + thisChanges styles of GeoJSON vector layers with the given style function.
resetStyle( + <Path> layer ) + thisResets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
+ +

Static methods

+ + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
geometryToLayer( + <GeoJSON> featureData, + <Function> pointToLayer? ) + ILayerCreates a layer from a given GeoJSON feature.
coordsToLatLng( + <Array> coords, + <Boolean> reverse? ) + LatLngCreates a LatLng object from an array of 2 numbers (latitude, longitude) used in GeoJSON for points. If reverse is set to true, the numbers will be interpreted as (longitude, latitude).
coordsToLatLngs( + <Array> coords, + <Number> levelsDeep?, + <Boolean> reverse? ) + ArrayCreates a multidimensional array of LatLng objects 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). If reverse is set to true, the numbers will be interpreted as (longitude, latitude).
+ +

GridLayer

+ +

Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces TileLayer.Canvas.

+ +

GridLayer can be extended to create a tiled grid of HTML Elements like <canvas>, <img> or <div>.GridLayer will handle creating and animating these DOM elements for you.

+ +

Synchronous usage example

+ +

To create a custom layer, extend GridLayer and impliment the createTile() method, which will be passed a Point object with the x, y, and z (zoom level) coordinates to draw your tile.

+ +
var CanvasLayer = L.GridLayer.extend({
+	createTile: function(coords){
+		// create a <canvas> element for drawing
+		var tile = L.DomUtil.create('canvas', 'leaflet-tile');
+
+		// setup tile width and height according to the options
+		tile.width = tile.height = this.options.tileSize;
+
+		// get a canvas context and draw something on it using coords.x, coords.y and coords.z
+		var ctx = canvas.getContext('2d');
+
+		// return the tile so it can be rendered on screen
+		return tile;
+	}
+});
+ +

Asyncronous usage example

+ +

Tile creation can also be asyncronous, this is useful when using a third-party drawing library. Once the tile is finsihed drawing it can be passed to the done() callback.

+ +
var CanvasLayer = L.GridLayer.extend({
+	createTile: function(coords, done){
+		var error;
+
+		// create a <canvas> element for drawing
+		var tile = L.DomUtil.create('canvas', 'leaflet-tile');
+
+		// setup tile width and height according to the options
+		tile.width = tile.height = this.options.tileSize;
+
+		// draw something and pass the tile to the done() callback
+		done(error, tile);
+	}
+});
+ +

Constructor

+ + + + + + + + + + +
FactoryDescription
L.gridLayer(<GridLayer options> options?)Creates a new instance of GridLayer with the supplied options.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefault valueDescription
maxZoomNumber'tilePane'The map pane the layer will be added to.
tileSizeNumber256Width and height of tiles in the grid. Can be used in the createTile() function.
opacityNumber1Opacity of the tiles. Can be used in the createTile() function.
unloadInvisibleTilesBooleandependsIf true, all the tiles that are not visible after panning are removed (for better performance). true by default on mobile WebKit, otherwise false.
updateWhenIdleBooleandependsIf false, new tiles are loaded during panning, otherwise only after it (for better performance). true by default on mobile WebKit, otherwise false.
updateIntervalNumber200Tiles will not update more then once every updateInterval.
zIndexNumbernullThe explicit zIndex of the tile layer. Not set by default.
boundsLatLngBoundsnullIf set tiles will only be loaded inside inside the set LatLngBounds.
boundsLatLngBoundsnullIf set tiles will only be loaded inside inside the set LatLngBounds.
minZoomNumber0The minimum zoom level that tiles will be loaded at. By default the entire map.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
bringToFront()thisBrings the tile layer to the top of all tile layers.
bringToBack()thisBrings the tile layer to the bottom of all tile layers.
setOpacity( + <Number> opacity ) + thisChanges the opacity of the tile layer.
setZIndex( + <Number> zIndex ) + thisSets the zIndex of the tile layer.
redraw()thisCauses the layer to clear all the tiles and request them again.
getContainer() + HTMLElementReturns the HTML element that contains the tiles for this layer.
+ +

Events

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
loadingEventFired when the tile layer starts loading tiles.
loadEventFired when the tile layer loaded all visible tiles.
tileloadstartTileEventFired when a tile is requested and starts loading.
tileloadTileEventFired when a tile loads.
tileunloadTileEventFired when a tile is removed (e.g. when you have unloadInvisibleTiles on).
tileerrorTileEventFired when there is an error loading a tile.
+ +

LatLng

+ +

Represents a geographical point with a certain latitude and longitude.

+
var latlng = L.latLng(50.5, 30.5);
+ +

All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:

+ +
map.panTo([50, 30]);
+map.panTo({lon: 30, lat: 50});
+map.panTo({lat: 50, lng: 30});
+map.panTo(L.latLng(50, 30));
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.latLng( + <Number> latitude, + <Number> longitude, + <Number> altitude? ) + Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
+ +

Properties

+ + + + + + + + + + + + + + + + + +
PropertyTypeDescription
latNumberLatitude in degrees.
lngNumberLongitude in degrees.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
distanceTo( + <LatLng> otherLatlng ) + NumberReturns the distance (in meters) to the given LatLng calculated using the Haversine formula. See description on wikipedia
equals( + <LatLng> otherLatlng ) + BooleanReturns true if the given LatLng point is at the same position (within a small margin of error).
toString()StringReturns a string representation of the point (for debugging purposes).
wrap( + <Number> left, + <Number> right ) + LatLngReturns a new LatLng object with the longitude wrapped around left and right boundaries (-180 to 180 by default).
+ +

Constants

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ConstantTypeValueDescription
DEG_TO_RADNumberMath.PI / 180A multiplier for converting degrees into radians.
RAD_TO_DEGNumber180 / Math.PIA multiplier for converting radians into degrees.
MAX_MARGINNumber1.0E-9Max margin of error for the equality check.
+ + + + +

LatLngBounds

+ +

Represents a rectangular geographical area on a map.

+
var southWest = L.latLng(40.712, -74.227),
+	northEast = L.latLng(40.774, -74.125),
+	bounds = L.latLngBounds(southWest, northEast);
+ +

All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:

+ +
map.fitBounds([
+	[40.712, -74.227],
+	[40.774, -74.125]
+]);
+ +

Creation

+ + + + + + + + + + + + + + + + + + +
FactoryDescription
+ L.latLngBounds( + <LatLng> southWest, + <LatLng> northEast ) + Creates a latLngBounds object by defining south-west and north-east corners of the rectangle.
L.latLngBounds( + <LatLng[]> latlngs ) + 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.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
extend( + <LatLng|LatLngBounds> latlng ) + thisExtends the bounds to contain the given point or bounds.
getSouthWest()LatLngReturns the south-west point of the bounds.
getNorthEast()LatLngReturns the north-east point of the bounds.
getNorthWest()LatLngReturns the north-west point of the bounds.
getSouthEast()LatLngReturns the south-east point of the bounds.
getWest()NumberReturns the west longitude of the bounds.
getSouth()NumberReturns the south latitude of the bounds.
getEast()NumberReturns the east longitude of the bounds.
getNorth()NumberReturns the north latitude of the bounds.
getCenter()LatLngReturns the center point of the bounds.
contains( + <LatLngBounds> otherBounds ) + BooleanReturns true if the rectangle contains the given one.
contains( + <LatLng> latlng ) + BooleanReturns true if the rectangle contains the given point.
intersects( + <LatLngBounds> otherBounds ) + BooleanReturns true if the rectangle intersects the given bounds.
equals( + <LatLngBounds> otherBounds ) + BooleanReturns true if the rectangle is equivalent (within a small margin of error) to the given bounds.
toBBoxString()StringReturns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
pad( + <Number> bufferRatio ) + LatLngBoundsReturns bigger bounds created by extending the current bounds by a given percentage in each direction.
isValid() + BooleanReturns true if the bounds are properly initialized.
+ + + + +

Point

+ +

Represents a point with x and y coordinates in pixels.

+ +
var point = L.point(200, 300);
+ +

All Leaflet methods and options that accept Point objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:

+ +
map.panBy([200, 300]);
+map.panBy(L.point(200, 300));
+ +

Creation

+ + + + + + + + + + + +
FactoryDescription
L.point( + <Number> x, <Number> y, + <Boolean> round? ) + Creates a Point object with the given x and y coordinates. If optional round is set to true, rounds the x and y values.
+ +

Properties

+ + + + + + + + + + + + + + + + + +
PropertyTypeDescription
xNumberThe x coordinate.
yNumberThe y coordinate.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
add( + <Point> otherPoint ) + PointReturns the result of addition of the current and the given points.
subtract( + <Point> otherPoint ) + PointReturns the result of subtraction of the given point from the current.
multiplyBy( + <Number> number ) + PointReturns the result of multiplication of the current point by the given number.
divideBy( + <Number> number, + <Boolean> round? ) + PointReturns the result of division of the current point by the given number. If optional round is set to true, returns a rounded result.
distanceTo( + <Point> otherPoint ) + NumberReturns the distance between the current and the given points.
clone()PointReturns a copy of the current point.
round()PointReturns a copy of the current point with rounded coordinates.
floor()PointReturns a copy of the current point with floored coordinates (rounded down).
equals( + <Point> otherPoint ) + BooleanReturns true if the given point has the same coordinates.
contains( + <Point> otherPoint ) + BooleanReturns true if the both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
toString()StringReturns a string representation of the point for debugging purposes.
+ + + +

Bounds

+ +

Represents a rectangular area in pixel coordinates.

+
var p1 = L.point(10, 10),
+	p2 = L.point(40, 60),
+	bounds = L.bounds(p1, p2);
+ +

All Leaflet methods that accept Bounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:

+ +
otherBounds.intersects([[10, 10], [40, 60]]);
+ +

Creation

+ + + + + + + + + + + + + + + + + + + + +
FactoryDescription
L.bounds( + <Point> topLeft, + <Point> bottomRight ) + Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
L.bounds( + <Point[]> points ) + Creates a Bounds object defined by the points it contains.
+ +

Properties

+ + + + + + + + + + + + + + + + + +
PropertyTypeDescription
minPointThe top left corner of the rectangle.
maxPointThe bottom right corner of the rectangle.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
extend( + <Point> point ) + -Extends the bounds to contain the given point.
getCenter()PointReturns the center point of the bounds.
contains( + <Bounds> otherBounds ) + BooleanReturns true if the rectangle contains the given one.
contains( + <Point> point ) + BooleanReturns true if the rectangle contains the given point.
intersects( + <Bounds> otherBounds ) + BooleanReturns true if the rectangle intersects the given bounds.
isValid()BooleanReturns true if the bounds are properly initialized.
getSize()PointReturns the size of the given bounds.
+ + +

Icon

+ +

Represents an icon to provide when creating a marker.

+ +
var myIcon = L.icon({
+	iconUrl: 'my-icon.png',
+	iconRetinaUrl: 'my-icon@2x.png',
+	iconSize: [38, 95],
+	iconAnchor: [22, 94],
+	popupAnchor: [-3, -76],
+	shadowUrl: 'my-icon-shadow.png',
+	shadowRetinaUrl: 'my-icon-shadow@2x.png',
+	shadowSize: [68, 95],
+	shadowAnchor: [22, 94]
+});
+
+L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
+ +

L.Icon.Default extends L.Icon and is the blue icon Leaflet uses for markers by default.

+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.icon( + <Icon options> options ) + Creates an icon instance with the given options.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDescription
iconUrlString + (required) The URL to the icon image (absolute or relative to your script path).
iconRetinaUrlString + The URL to a retina sized version of the icon image (absolute or relative to your script path). Used for Retina screen devices.
iconSizePointSize of the icon image in pixels.
iconAnchorPointThe coordinates of the "tip" of the icon (relative to its top left corner). The icon will be aligned so that this point is at the marker's geographical location. Centered by default if size is specified, also can be set in CSS with negative margins.
shadowUrlString + The URL to the icon shadow image. If not specified, no shadow image will be created.
shadowRetinaUrlString + The URL to the retina sized version of the icon shadow image. If not specified, no shadow image will be created. Used for Retina screen devices.
shadowSizePointSize of the shadow image in pixels.
shadowAnchorPointThe coordinates of the "tip" of the shadow (relative to its top left corner) (the same as iconAnchor if not specified).
popupAnchorPointThe coordinates of the point from which popups will "open", relative to the icon anchor.
classNameString + A custom class name to assign to both icon and shadow images. Empty by default.
+ + +

DivIcon

+ +

Represents a lightweight icon for markers that uses a simple div element instead of an image.

+ +
var myIcon = L.divIcon({className: 'my-div-icon'});
+// you can set .my-div-icon styles in CSS
+
+L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
+ +

By default, it has a 'leaflet-div-icon' class and is styled as a little white square with a shadow.

+ +

Creation

+ + + + + + + + + + + + + + +
FactoryDescription
L.divIcon( + <DivIcon options> options ) + Creates a div icon instance with the given options.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDescription
iconSizePointSize of the icon in pixels. Can be also set through CSS.
iconAnchorPointThe coordinates of the "tip" of the icon (relative to its top left corner). The icon will be aligned so that this point is at the marker's geographical location. Centered by default if size is specified, also can be set in CSS with negative margins.
popupAnchorPointThe coordinates of the point from which popups will "open", relative to the icon anchor.
classNameString + A custom class name to assign to the icon. 'leaflet-div-icon' by default.
htmlString + A custom HTML code to put inside the div element, empty by default.
+ +

Control.zoom

+ +

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 to false. Extends Control.

+ +

Creation

+ + + + + + + + + + + + +
FactoryDescription
L.control.zoom( + <Control.Zoom options> options? ) + Creates a zoom control.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
positionString'topleft'The position of the control (one of the map corners). See control positions.
zoomInTextString'+'The text set on the zoom in button.
zoomOutTextString'-'The text set on the zoom out button.
zoomInTitleString'Zoom in'The title set on the zoom in button.
zoomOutTitleString'Zoom out'The title set on the zoom out button.
+ + + +

Control.Attribution

+ +

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 to false, and it fetches attribution texts from layers with getAttribution method automatically. Extends Control.

+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.control.attribution( + <Control.Attribution options> options? ) + Creates an attribution control.
+ +

Options

+ + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
positionString'bottomright'The position of the control (one of the map corners). See control positions.
prefixString'Leaflet'The HTML text shown before the attributions. Pass false to disable.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
setPrefix( + <String> prefix ) + thisSets the text before the attributions.
addAttribution( + <String> text ) + thisAdds an attribution text (e.g. 'Vector data &copy; Mapbox').
removeAttribution( + <String> text ) + thisRemoves an attribution text.
+ + +

Control.Layers

+ +

The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the detailed example). Extends Control.

+ +
var baseLayers = {
+	"Mapbox": mapbox,
+	"OpenStreetMap": osm
+};
+
+var overlays = {
+	"Marker": marker,
+	"Roads": roadsLayer
+};
+
+L.control.layers(baseLayers, overlays).addTo(map);
+ +

Creation

+ + + + + + + + + + +
FactoryDescription
L.control.layers( + <Layer Config> baseLayers?, + <Layer Config> overlays?, + <Control.Layers options> 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.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addBaseLayer( + <ILayer> layer, + <String> name ) + thisAdds a base layer (radio button entry) with the given name to the control.
addOverlay( + <ILayer> layer, + <String> name ) + thisAdds an overlay (checkbox entry) with the given name to the control.
removeLayer( + <ILayer> layer ) + thisRemove the given layer from the control.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
positionString'topright'The position of the control (one of the map corners). See control positions.
collapsedBooleantrueIf true, the control will be collapsed into an icon and expanded on mouse hover or touch.
autoZIndexBooleantrueIf true, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
+ + +

Layer Config

+ +

An object literal with layer names as keys and layer objects as values:

+ +
{
+	"<someName1>": layer1,
+	"<someName2>": layer2
+}
+ +

The layer names can contain HTML, which allows you to add additional styling to the items:

+ +
{"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
+ + +

Events

+ +

You can subscribe to the following events on the Map object using these methods.

+ + + + + + + + + + + + + + + + + + + +
EventDataDescription
baselayerchangeLayersControlEvent + Fired when the base layer is changed through the control.
overlayaddLayersControlEvent + Fired when an overlay is selected through the control.
overlayremoveLayersControlEvent + Fired when an overlay is deselected through the control.
+ + +

Control.Scale

+ +

A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends Control.

+ +
L.control.scale().addTo(map);
+ +

Creation

+ + + + + + + + + + + + + +
FactoryDescription
L.control.scale( + <Control.Scale options> options? ) + Creates an scale control with the given options.
+ +

Options

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionTypeDefaultDescription
positionString'bottomleft'The position of the control (one of the map corners). See control positions.
maxWidthNumber100Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
metricBooleantrueWhether to show the metric scale line (m/km).
imperialBooleantrueWhether to show the imperial scale line (mi/ft).
updateWhenIdleBooleanfalseIf true, the control is updated on moveend, otherwise it's always up-to-date (updated on move).
+ + +

Events methods

+ +

A set of methods shared between event-powered classes (like Map and Marker). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire 'click' event).

+ +

Example

+ +
map.on('click', function(e) {
+	alert(e.latlng);
+});
+ +

Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:

+ +
function onClick(e) { ... }
+
+map.on('click', onClick);
+map.off('click', onClick);
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addEventListener( + <String> type, + <Function> fn, + <Object> context? ) + thisAdds 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').
addOneTimeEventListener( + <String> type, + <Function> fn, + <Object> context? ) + thisThe same as above except the listener will only get fired once and then removed.
addEventListener( + <Object> eventMap, + <Object> context? ) + thisAdds a set of type/listener pairs, e.g. {click: onClick, mousemove: onMouseMove}
removeEventListener( + <String> type, + <Function> fn?, + <Object> context? ) + thisRemoves a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to addEventListener, you must pass the same context to removeEventListener in order to remove the listener.
removeEventListener( + <Object> eventMap, + <Object> context? ) + thisRemoves a set of type/listener pairs.
removeEventListener()thisRemoves all listeners. An alias to clearAllEventListeners when you use it without arguments.
hasEventListeners( + <String> type ) + BooleanReturns true if a particular event type has some listeners attached to it.
fireEvent( + <String> type, + <Object> data? ) + thisFires an event of the specified type. You can optionally provide an data object — the first argument of the listener function will contain its properties.
clearAllEventListeners()thisRemoves all listeners to all events on the object.
on( … )thisAlias to addEventListener.
once( … )thisAlias to addOneTimeEventListener.
off( … )thisAlias to removeEventListener.
fire( … )thisAlias to fireEvent.
+ + +

Layer methods

+ +A set of methods inherited from the Layer base class that all Leaflet layers use. + +
var layer = L.Marker(latlng).addTo(map);
+layer.addTo(map);
+layer.remove();
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addTo(<Map> map)thisAdds the layer to the given map.
removeFrom(<Map> map)thisRemoves the layer to the given map.
remove()thisRemoves the layer from the map it is currently active on.
getPane(<String> name?)HTMLElementReturns the HTMLElement representing the named pane on the map. Or if name is omitted the pane for this layer.
+ +

Popup methods

+ +A set of methods inherited from the Layer base class that all Leaflet layers use. These methods provide convieniant ways of binding popups to any layer. + +
var layer = L.Polgon(latlngs).bindPopup('Hi There!').addTo(map);
+layer.openPopup();
+layer.closePopup();
+
+ +Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened. + +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
bindPopup(<String|HTMLElement|Popup> content, <PopupOptions> options?)thisBinds the passed content to the layer and sets up the neccessary event listeners.
unbindPopup()thisRemoves the popup previously bound with bindPopup.
openPopup(LatLng latlng?)thisOpens the bound popup at the specificed latlng or at the default popup anchor if no latlng is passed.
closePopup()thisCloses the popup if it is open.
togglePopup()thisOpens or closes the popup depending on its current state.
setPopupContent(<String|HTMLElement|Popup> content, <PopupOptions> options?)thisSets the content of the popup.
getPopup()PopupReturns the popup bound to this layer.
+ +

Browser

+ +

A namespace with properties for browser/feature detection used by Leaflet internally.

+ +
if (L.Browser.ie6) {
+	alert('Upgrade your browser, dude!');
+}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
propertytypedescription
ieBooleantrue for all Internet Explorer versions.
ie6Booleantrue for Internet Explorer 6.
ie7Booleantrue for Internet Explorer 7.
webkitBooleantrue for webkit-based browsers like Chrome and Safari (including mobile versions).
webkit3dBooleantrue for webkit-based browsers that support CSS 3D transformations.
androidBooleantrue for Android mobile browser.
android23Booleantrue for old Android stock browsers (2 and 3).
mobileBooleantrue for modern mobile browsers (including iOS Safari and different Android browsers).
mobileWebkitBooleantrue for mobile webkit-based browsers.
mobileOperaBooleantrue for mobile Opera.
touchBooleantrue for all browsers on touch devices.
msTouchBooleantrue for browsers with Microsoft touch model (e.g. IE10).
retinaBooleantrue for devices with Retina screens.
+ + +

Util

+ +

Various utility functions, used by Leaflet internally.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
extend( + <Object> dest, + <Object> src?.. ) + ObjectMerges the properties of the src object (or multiple objects) into dest object and returns the latter. Has an L.extend shortcut.
bind( + <Function> fn, + <Object> obj ) + FunctionReturns a function which executes function fn with the given scope obj (so that this keyword refers to obj inside the function code). Has an L.bind shortcut.
stamp( <Object> obj )StringApplies a unique key to the object and returns that key. Has an L.stamp shortcut.
requestAnimFrame( + <Function> fn, + <Object> context?, + <Boolean> immediate?, + <HTMLElement> element? ) + NumberSchedules fn to be executed when the browser repaints. When immediate is set, fn is called immediately if the browser doesn't have native support for requestAnimationFrame, otherwise it's delayed. Returns an id that can be used to cancel the request
cancelAnimFrame( + <Number> id ) + -Cancels a previous request to requestAnimFrame.
limitExecByInterval( + <Function> fn, + <Number> time, + <Object> context? ) + FunctionReturns a wrapper around the function fn that makes sure it's called not more often than a certain time interval time, but as fast as possible otherwise (for example, it is used for checking and requesting new tiles while dragging the map), optionally passing the scope (context) in which the function will be called.
falseFn()FunctionReturns a function which always returns false.
formatNum( + <Number> num, + <Number> digits ) + NumberReturns the number num rounded to digits decimals.
splitWords( + <String> str ) + String[]Trims and splits the string on whitespace and returns the array of parts.
setOptions( + <Object> obj, + <Object> options ) + ObjectMerges the given properties to the options of the obj object, returning the resulting options. See Class options. Has an L.setOptions shortcut.
getParamString( + <Object> obj ) + StringConverts an object into a parameter URL string, e.g. {a: "foo", b: "bar"} translates to '?a=foo&b=bar'.
template( + <String> str, <Object> data ) + StringSimple 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.
isArray( + <Object> obj ) + BooleanReturns true if the given object is an array.
trim( + <String> str ) + StringTrims the whitespace from both ends of the string and returns the result.
+ +

Properties

+ + + + + + + + + + + + +
PropertyTypeDescription
emptyImageUrlStringData 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).
+ + + +

Transformation

+ +

Represents an affine transformation: a set of coefficients a, b, c, d for transforming a point of a form (x, y) into (a*x + b, c*y + d) and doing the reverse. Used by Leaflet in its projections code.

+ +
var transformation = new L.Transformation(2, 5, -1, 10),
+	p = L.point(1, 2),
+	p2 = transformation.transform(p), //  L.point(7, 8)
+	p3 = transformation.untransform(p2); //  L.point(1, 2)
+
+ +

Creation

+ + + + + + + + + + + +
CreationDescription
new L.Transformation( + <Number> a, + <Number> b, + <Number> c, + <Number> d ) + Creates a transformation object with the given coefficients.
+ +

Methods

+ + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
transform( + <Point> point, + <Number> scale? ) + PointReturns a transformed point, optionally multiplied by the given scale. Only accepts real L.Point instances, not arrays.
untransform( + <Point> point, + <Number> scale? ) + PointReturns the reverse transformation of the given point, optionally divided by the given scale. Only accepts real L.Point instances, not arrays.
+ + + + +

LineUtil

+ +

Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
simplify( + <Point[]> points, + <Number> tolerance ) + Point[]Dramatically reduces the number of points in a polyline while retaining its shape and returns a new array of simplified points. 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.
pointToSegmentDistance( + <Point> p, + <Point> p1, + <Point> p2 ) + NumberReturns the distance between point p and segment p1 to p2.
closestPointOnSegment( + <Point> p, + <Point> p1, + <Point> p2 ) + PointReturns the closest point from a point p on a segment p1 to p2.
clipSegment( + <Point> a, + <Point> b, + <Bounds> bounds ) + -Clips the segment a to b by rectangular bounds (modifying the segment points directly!). Used by Leaflet to only show polyline points that are on the screen or near, increasing performance.
+ + + +

PolyUtil

+ +

Various utility functions for polygon geometries.

+ +

Methods

+ + + + + + + + + + + + + + +
MethodReturnsDescription
clipPolygon( + <Point[]> points, + <Bounds> bounds ) + Point[]Clips the polygon geometry defined by the given points by rectangular bounds. 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.
+ + + + +

DomEvent

+ +

Utility functions to work with the DOM events, used by Leaflet internally.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addListener( + <HTMLElement> el, + <String> type, + <Function> fn, + <Object> context? ) + thisAdds a listener fn to the element's DOM event of the specified type. this keyword inside the listener will point to context, or to the element if not specified.
removeListener( + <HTMLElement> el, + <String> type, + <Function> fn ) + thisRemoves an event listener from the element.
stopPropagation( + <DOMEvent> e ) + thisStop the given event from propagation to parent elements. Used inside the listener functions: +
L.DomEvent.addListener(div, 'click', function (e) {
+	L.DomEvent.stopPropagation(e);
+});
+
preventDefault( + <DOMEvent> e ) + thisPrevents the default action of the event from happening (such as following a link in the href of the a element, or doing a POST request with page reload when form is submitted). Use it inside listener functions. +
stop( + <DOMEvent> e ) + thisDoes stopPropagation and preventDefault at the same time.
disableClickPropagation( + <HTMLElement> el ) + thisAdds stopPropagation to the element's 'click', 'doubleclick', 'mousedown' and 'touchstart' events.
getMousePosition( + <DOMEvent> e, + <HTMLElement> container? ) + PointGets normalized mouse position from a DOM event relative to the container or to the whole page if not specified.
getWheelDelta( + <DOMEvent> e ) + NumberGets normalized wheel delta from a mousewheel DOM event.
+ + + + +

DomUtil

+ +

Utility functions to work with the DOM tree, used by Leaflet internally.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
get( + <String or HTMLElement> id ) + HTMLElementReturns an element with the given id if a string was passed, or just returns the element if it was passed directly.
getStyle( + <HTMLElement> el, + <String> style ) + StringReturns the value for a certain style attribute on an element, including computed values or values set through CSS.
create( + <String> tagName, + <String> className, + <HTMLElement> container? ) + HTMLElementCreates an element with tagName, sets the className, and optionally appends it to container element.
disableTextSelection()-Makes sure text cannot be selected, for example during dragging.
enableTextSelection()-Makes text selection possible again.
hasClass( + <HTMLElement> el, + <String> name ) + BooleanReturns true if the element class attribute contains name.
addClass( + <HTMLElement> el, + <String> name ) + -Adds name to the element's class attribute.
removeClass( + <HTMLElement> el, + <String> name ) + -Removes name from the element's class attribute.
setOpacity( + <HTMLElement> el, + <Number> value ) + -Set the opacity of an element (including old IE support). Value must be from 0 to 1.
testProp( + <String[]> props ) + String or falseGoes 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.
getTranslateString( + <Point> point ) + StringReturns a CSS transform string to move an element by the offset provided in the given point. Uses 3D translate on WebKit for hardware-accelerated transforms and 2D on other browsers.
getScaleString( + <Number> scale, + <Point> origin ) + StringReturns a CSS transform string to scale an element (with the given scale origin).
setPosition( + <HTMLElement> el, + <Point> point, + <Boolean> disable3D? ) + -Sets the position of an element to coordinates specified by point, using CSS translate or top/left positioning depending on the browser (used by Leaflet internally to position its layers). Forces top/left positioning if disable3D is true.
getPosition( + <HTMLElement> el ) + PointReturns the coordinates of an element previously positioned with setPosition.
+ +

Properties

+ + + + + + + + + + + + + + + + + +
PropertyTypeDescription
TRANSITION + StringVendor-prefixed transition style name (e.g. 'webkitTransition' for WebKit).
TRANSFORM + StringVendor-prefixed transform style name.
+ + + +

PosAnimation

+ +

Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.

+ +
var fx = new L.PosAnimation();
+fx.run(el, [300, 500], 0.5);
+ +

Creation

+ + + + + + + + + + + +
CreationDescription
new L.PosAnimation()Creates a PosAnimation object.
+ +

Methods

+ + + + + + + + + + + + + +
MethodReturnsDescription
run( + <HTMLElement> element, + <Point> newPos, + <Number> duration?, + <Number> easeLinearity? ) + thisRun an animation of a given element to a new position, optionally setting duration in seconds (0.25 by default) and easing linearity factor (3rd argument of the cubic bezier curve, 0.5 by default)
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
startEventFired when the animation starts.
stepEventFired continuously during the animation.
endEventFired when the animation ends.
+ + + +

Draggable

+ +

A class for making DOM elements draggable (including touch support). Used internally for map and marker dragging. Only works for elements that were positioned with DomUtil#setPosition

+ +
var draggable = new L.Draggable(elementToDrag);
+draggable.enable();
+
+ +

Creation

+ + + + + + + + + + + +
CreationDescription
new L.Draggable( + <HTMLElement> element, + <HTMLElement> dragHandle? ) + Creates a Draggable object for moving the given element when you start dragging the dragHandle element (equals the element itself by default).
+ +

Events

+ +

You can subscribe to the following events using these methods.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventDataDescription
dragstartEventFired when the dragging starts.
predragEventFired continuously during dragging before each corresponding update of the element position.
dragEventFired continuously during dragging.
dragendDragEndEventFired when the dragging ends.
+ +

Methods

+ + + + + + + + + + + + + + + + + +
MethodReturnsDescription
enable()-Enables the dragging ability.
disable()-Disables the dragging ability.
+ + + +

Class

+ +

L.Class powers the OOP facilities of Leaflet and is used to create almost all of the Leaflet classes documented here.

+

In addition to implementing a simple classical inheritance model, it introduces several special properties for convenient code organization — options, includes and statics.

+ +
var MyClass = L.Class.extend({
+	initialize: function (greeter) {
+		this.greeter = greeter;
+		// class constructor
+	},
+
+	greet: function (name) {
+		alert(this.greeter + ', ' + name)
+	}
+});
+
+// create instance of MyClass, passing "Hello" to the constructor
+var a = new MyClass("Hello");
+
+// call greet method, alerting "Hello, World"
+a.greet("World");
+
+ + +

Class Factories

+ +

You may have noticed that Leaflet objects are created without using the new keyword. This is achieved by complementing each class with a lowercase factory method:

+ +
new L.Map('map'); // becomes:
+L.map('map');
+ +

The factories are implemented very easily, and you can do this for your own classes:

+ +
L.map = function (id, options) {
+	return new L.Map(id, options);
+};
+ + +

Inheritance

+ +

You use L.Class.extend to define new classes, but you can use the same method on any class to inherit from it:

+ +
var MyChildClass = MyClass.extend({
+	// ... new properties and methods
+});
+ +

This will create a class that inherits all methods and properties of the parent class (through a proper prototype chain), adding or overriding the ones you pass to extend. It will also properly react to instanceof:

+ +
var a = new MyChildClass();
+a instanceof MyChildClass; // true
+a instanceof MyClass; // true
+
+ +

You can call parent methods (including constructor) from corresponding child ones (as you do with super calls in other languages) by accessing parent class prototype and using JavaScript's call or apply:

+ +
var MyChildClass = MyClass.extend({
+	initialize: function () {
+		MyClass.prototype.initialize.call("Yo");
+	},
+
+	greet: function (name) {
+		MyClass.prototype.greet.call(this, 'bro ' + name + '!');
+	}
+});
+
+var a = new MyChildClass();
+a.greet('Jason'); // alerts "Yo, bro Jason!"
+ +

Options

+ +

options is a special property that unlike other objects that you pass to extend will be merged with the parent one instead of overriding it completely, which makes managing configuration of objects and default values convenient:

+ +
var MyClass = L.Class.extend({
+	options: {
+		myOption1: 'foo',
+		myOption2: 'bar'
+	}
+});
+
+var MyChildClass = L.Class.extend({
+	options: {
+		myOption1: 'baz',
+		myOption3: 5
+	}
+});
+
+var a = new MyChildClass();
+a.options.myOption1; // 'baz'
+a.options.myOption2; // 'bar'
+a.options.myOption3; // 5
+ +

There's also L.Util.setOptions, a method for conveniently merging options passed to constructor with the defauls defines in the class:

+ +
var MyClass = L.Class.extend({
+	options: {
+		foo: 'bar',
+		bla: 5
+	},
+
+	initialize: function (options) {
+		L.Util.setOptions(this, options);
+		...
+	}
+});
+
+var a = new MyClass({bla: 10});
+a.options; // {foo: 'bar', bla: 10}
+ +

Includes

+ +

includes is a special class property that merges all specified objects into the class (such objects are called mixins). + +

 var MyMixin = {
+	foo: function () { ... },
+	bar: 5
+};
+
+var MyClass = L.Class.extend({
+	includes: MyMixin
+});
+
+var a = new MyClass();
+a.foo();
+ +

You can also do such includes in runtime with the include method:

+ +
MyClass.include(MyMixin);
+ +

Statics

+ +

statics is just a convenience property that injects specified object properties as the static properties of the class, useful for defining constants:

+ +
var MyClass = L.Class.extend({
+	statics: {
+		FOO: 'bar',
+		BLA: 5
+	}
+});
+
+MyClass.FOO; // 'bar'
+ + +

Constructor Hooks

+ +

If you're a plugin developer, you often need to add additional initialization code to existing classes (e.g. editing hooks for L.Polyline). Leaflet comes with a way to do it easily using the addInitHook method:

+ +
MyClass.addInitHook(function () {
+	// ... do something in constructor additionally
+	// e.g. add event listeners, set custom properties etc.
+});
+ +

You can also use the following shortcut when you just need to make one additional method call:

+ +
MyClass.addInitHook('methodName', arg1, arg2, …);
+ +

Evented

+ +

When creating a plugin you may want your code to have access to the event methods. By extending the Evented class you can create a class which inherits event-related methods like on, off and fire

+ +
MyEventedClass = L.Evented.extend({
+	fire: function(){
+		this.fire('custom', {
+			// you can attach optional data to an event as an object
+		});
+	}
+});
+
+var myEvents = new MyEventedClass();
+
+myEvents.on('custom', function(e){
+	// e.type will  be 'custom'
+	// anything else you passed in the
+});
+
+myEvents.fire();
+ +You can still use the old-style `L.Mixin.Events` for backward compatibility. + +
// this class will include all event methods
+MyEventedClass = L.Class.extend({
+	includes: L.Mixin.Events
+});
+ +

Layer

+ +

The base class for all Leaflet layers that implements basic shared methods and functionality. Can be extended to create custom layers by extending L.Layer and implementing the onAdd and onRemove methods.

+ +

Implementing Custom Layers

+ +

Custom layers should extend the L.Layer base class. L.Layer provides convenience methods for your layer like addTo(map), removeFrom(map) and getPane().

+ +

The most important things know about when implementing custom layers are Map viewreset event and latLngToLayerPoint method. viewreset is fired when the map needs to reposition its layers (e.g. on zoom), and latLngToLayerPoint is used to get coordinates for the layer's new position.

+ +

Another event often used in layer implementations is moveend which fires after any movement of the map (panning, zooming, etc.).

+ +

Another thing to note is that you'll usually need to add leaflet-zoom-hide class to the DOM elements you create for the layer so that it hides during zoom animation. Implementing zoom animation for custom layers is a complex topic and will be documented separately in future, but meanwhile you can take a look at how it's done for Leaflet layers (e.g. ImageOverlay) in the source.

+ +

Methods

+ +

Every layer should extend from L.Layer and implement the following methods:

+ + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
onAdd(<Map> map)thisShould contain code that creates DOM elements for the overlay, adds them to map panes where they should belong and puts listeners on relevant map events. Called on map.addLayer(layer).
onRemove(<Map> map)thisShould contain all clean up code that removes the overlay's elements from the DOM and removes listeners previously added in onAdd. Called on map.removeLayer(layer).
getEvents()ObjectThis optional method should return an object like { viewreset: this._reset }for addEventListener. These events will be automatically added and removed from the map with your layer.
+ +

Custom Layer Example

+ +

Here's how a custom layer implementation usually looks:

+ +
var MyCustomLayer = L.Layer.extend({
+
+	initialize: function (latlng) {
+		// save position of the layer or any options from the constructor
+		this._latlng = latlng;
+	},
+
+	// these events will be added and removed from the map with the layer
+	getEvents: function(){
+		return {
+			viewreset: this._reset
+		}
+	},
+
+	onAdd: function (map) {
+		// create a DOM element and put it into one of the map panes, by default the overlayPane
+		this._el = L.DomUtil.create('div', 'my-custom-layer leaflet-zoom-hide');
+		this.getPane().appendChild(this._el);
+
+		// add a viewreset event listener for updating layer's position, do the latter
+		this._reset();
+	},
+
+	onRemove: function (map) {
+		// remove layer's DOM elements and listeners
+		this.getPane().removeChild(this._el);
+	},
+
+	_reset: function () {
+		// update layer's position
+		var pos = this._map.latLngToLayerPoint(this._latlng);
+		L.DomUtil.setPosition(this._el, pos);
+	}
+});
+
+var myLayer = new MyCustomLayer(latlng).addTo(map);
+
+ +

Inherited Options

+ +

Classes extending from L.Layer will also inherit the following options:

+ + + + + + + + + + + + + + +
OptionTypeDefault valueDescription
paneString'overlayPane'By default the layer will be added to the maps overlay pane. Overriding this option will cause the layer to be placed on another pane by default.
+ +

Inherited Events

+ +

Classes extending from L.Layer will also fire the following events:

+ + + + + + + + + + + + + + + + + +
EventDataDescription
addEventFired after the layer is added to a map.
removeEventFired after the layer is removed from a map.
+ +

Inherited Methods

+ +

Classes extending from L.Layer will also inherit the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
addTo(<Map> map)thisAdds the layer to the given map.
removeFrom(<Map> map)thisRemoves the layer to the given map.
remove()thisRemoves the layer from the map it is currently active on.
getPane(<String> name?)HTMLElementReturns the HTMLElement representing the named pane on the map. Or if name is omitted the pane for this layer.
+ +

Control

+ +

Controls represents a UI element in one of the corners of the map. Implemented by zoom, attribution, scale and layers controls. Can be used to create custom controls by extending L.Control and implementing the onAdd and onRemove methods.

+ +

Methods

+ +

Every control in Leaflet should extend from Control class and additionally have the following methods:

+ + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
onAdd( + <Map> map ) + HTMLElementShould contain code that creates all the neccessary DOM elements for the control, adds listeners on relevant map events, and returns the element containing the control. Called on map.addControl(control) or control.addTo(map).
onRemove( + <Map> map ) + -Optional, should contain all clean up code (e.g. removes control's event listeners). Called on map.removeControl(control) or control.removeFrom(map). The control's DOM container is removed automatically.
+ +

Custom Control Example

+ +
var MyControl = L.Control.extend({
+	options: {
+		position: 'topright'
+	},
+
+	onAdd: function (map) {
+		// create the control container with a particular class name
+		var container = L.DomUtil.create('div', 'my-custom-control');
+
+		// ... initialize other DOM elements, add listeners, etc.
+
+		return container;
+	}
+});
+
+map.addControl(new MyControl());
+
+ +

If specify your own constructor for the control, you'll also probably want to process options properly:

+ +
var MyControl = L.Control.extend({
+	initialize: function (foo, options) {
+		// ...
+		L.Util.setOptions(this, options);
+	},
+	// ...
+});
+ +

This will allow you to pass options like position when creating the control instances:

+ +
map.addControl(new MyControl('bar', {position: 'bottomleft'}));
+ +

Options

+ +

Classes extending from L.Control will also inherit the following options:

+ + + + + + + + + + + + + + +
OptionTypeDefaultDescription
positionString'topright'The initial position of the control (one of the map corners). See control positions.
+ +

Inherited Methods

+ +

Classes extending from L.Control will also inherit the following methods:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
setPosition( + <String> position ) + thisSets the position of the control. See control positions.
getPosition()StringReturns the current position of the control.
addTo( + <Map> map ) + thisAdds the control to the map.
removeFrom( + <Map> map ) + thisRemoves the control from the map.
getContainer()HTMLElementReturns the HTML container of the control.
+ +

Control Positions

+ +

Control positions (map corner to put a control to) are set using strings. Margins between controls and the map border are set with CSS, so that you can easily override them.

+ + + + + + + + + + + + + + + + + + + + + + +
PositionDescription
'topleft'Top left of the map.
'topright'Top right of the map.
'bottomleft'Bottom left of the map.
'bottomright'Bottom right of the map.
+ + +

Handler

+

Implemented by map interaction handlers.

+ + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
enable()-Enables the handler.
disable()-Disables the handler.
enabled()BooleanReturns true if the handler is enabled.
+ +

Projection

+ +

An object with methods for projecting geographical coordinates of the world onto a flat surface (and back). See Map projection.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
project( + <LatLng> latlng ) + PointProjects geographical coordinates into a 2D point.
unproject( + <Point> point ) + LatLngThe inverse of project. Projects a 2D point into geographical location.
+ +

Defined Projections

+ +

Leaflet comes with a set of already defined projections out of the box:

+ + + + + + + + + + + + + + + + + + + + + +
ProjectionDescription
L.Projection.SphericalMercatorSpherical Mercator projection — the most common projection for online maps, used by almost all free and commercial tile providers. Assumes that Earth is a sphere. Used by the EPSG:3857 CRS.
L.Projection.MercatorElliptical 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.LonLatEquirectangular, or Plate Carree projection — the most simple projection, mostly used by GIS enthusiasts. Directly maps x as longitude, and y as latitude. Also suitable for flat worlds, e.g. game maps. Used by the EPSG:3395 and Simple CRS.
+ + + + +

CRS

+ +

Defines coordinate reference systems for projecting geographical points into pixel (screen) coordinates and back (and to coordinates in other units for WMS services). See Spatial reference system.

+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodReturnsDescription
latLngToPoint( + <LatLng> latlng, + <Number> zoom ) + PointProjects geographical coordinates on a given zoom into pixel coordinates.
pointToLatLng( + <Point> point, + <Number> zoom ) + LatLngThe inverse of latLngToPoint. Projects pixel coordinates on a given zoom into geographical coordinates.
project( + <LatLng> latlng ) + PointProjects geographical coordinates into coordinates in units accepted for this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
scale( + <Number> zoom ) + NumberReturns the scale used when transforming projected coordinates into pixel coordinates for a particular zoom. For example, it returns 256 * 2^zoom for Mercator-based CRS.
getSize( + <Number> zoom ) + PointReturns the size of the world in pixels for a particular zoom.
+ +

Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
projectionIProjectionProjection that this CRS uses.
transformationTransformationTransformation that this CRS uses to turn projected coordinates into screen coordinates for a particular tile service.
codeStringStandard code name of the CRS passed into WMS services (e.g. 'EPSG:3857').
+ +

Defined CRS

+ +

Leaflet comes with a set of already defined CRS to use out of the box:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectionDescription
L.CRS.EPSG3857The most common CRS for online maps, used by almost all free and commercial tile providers. Uses Spherical Mercator projection. Set in by default in Map's crs option.
L.CRS.EPSG4326A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
L.CRS.EPSG3395Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
L.CRS.SimpleA simple CRS that maps longitude and latitude into x and y directly. May be used for maps of flat surfaces (e.g. game maps). Note that the y axis should still be inverted (going from bottom to top).
+ +

If you want to use some obscure CRS not listed here, take a look at the Proj4Leaflet plugin.

+ +

Event objects

+ +

Event object is an object that you recieve as an argument in a listener function when some event is fired, containing useful information about that event. For example:

+ +
map.on('click', function(e) {
+	alert(e.latlng); // e is an event object (MouseEvent in this case)
+});
+ +

Event

+ +

The base event object. All other event objects contain these properties too.

+ + + + + + + + + + + + + + + + + +
propertytypedescription
typeStringThe event type (e.g. 'click').
targetObjectThe object that fired the event.
+ +

MouseEvent

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
propertytypedescription
latlngLatLngThe geographical point where the mouse event occured.
layerPointPointPixel coordinates of the point where the mouse event occured relative to the map layer.
containerPointPointPixel coordinates of the point where the mouse event occured relative to the map сontainer.
originalEventDOMMouseEventThe original DOM mouse event fired by the browser.
+ +

LocationEvent

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
propertytypedescription
latlngLatLngDetected geographical location of the user.
boundsLatLngBoundsGeographical bounds of the area user is located in (with respect to the accuracy of location).
accuracyNumberAccuracy of location in meters.
altitudeNumberHeight of the position above the WGS84 ellipsoid in meters.
altitudeAccuracyNumberAccuracy of altitude in meters.
headingNumberThe direction of travel in degrees counting clockwise from true North.
speedNumberCurrent velocity in meters per second.
timestampNumberThe time when the position was acquired.
+ +

ErrorEvent

+ + + + + + + + + + + + + + + + + +
propertytypedescription
messageStringError message.
codeNumberError code (if applicable).
+ +

LayerEvent

+ + + + + + + + + + + + +
propertytypedescription
layerILayerThe layer that was added or removed.
+ +

LayersControlEvent

+ + + + + + + + + + + + + + + + + +
propertytypedescription
layerILayerThe layer that was added or removed.
nameStringThe name of the layer that was added or removed.
+ +

TileEvent

+ + + + + + + + + + + + +
propertytypedescription
tileHTMLElementThe tile element (image).
+ +

TileErrorEvent

+ + + + + + + + + + + + +
propertytypedescription
tileHTMLElementThe tile element (image).
+ +

ResizeEvent

+ + + + + + + + + + + + + + + + + +
propertytypedescription
oldSizePointThe old size before resize event.
newSizePointThe new size after the resize event.
+ +

GeoJSON event

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
propertytypedescription
layerILayerThe layer for the GeoJSON feature that is being added to the map.
propertiesObjectGeoJSON properties of the feature.
geometryTypeStringGeoJSON geometry type of the feature.
idStringGeoJSON ID of the feature (if present).
+ + + + + + + + + + + + + + +
propertytypedescription
popupPopupThe popup that was opened or closed.
+ +

DragEndEvent

+ + + + + + + + + + + + +
propertytypedescription
distanceNumberThe distance in pixels the draggable element was moved by.
+ +

Global Switches

+ +

Global switches are created for rare cases and generally make Leaflet to not detect a particular browser feature even if it's there. You need to set the switch as a global variable to true before including Leaflet on the page, like this:

+ +
<script>L_PREFER_CANVAS = true;</script>
+<script src="leaflet.js"></script>
+ + + + + + + + + + + + + + + + + + +
SwitchDescription
L_PREFER_CANVASForces Leaflet to use the Canvas back-end (if available) for vector layers instead of SVG. This can increase performance considerably in some cases (e.g. many thousands of circle markers on the map).
L_NO_TOUCHForces Leaflet to not use touch events even if it detects them.
L_DISABLE_3DForces Leaflet to not use hardware-accelerated CSS 3D transforms for positioning (which may cause glitches in some rare environments) even if they're supported.
+ +

noConflict

+ +

This method restores the L global variable to the original value it had before Leaflet inclusion, and returns the real Leaflet namespace so you can put it elsewhere, like this:

+ +

// L points to some other library
+...
+// you include Leaflet, it replaces the L variable to Leaflet namespace
+
+var Leaflet = L.noConflict();
+// now L points to that other library again, and you can use Leaflet.Map etc.
+ + +

version

+ +

A constant that represents the Leaflet version in use.

+ +

L.version // returns "0.5" (or whatever version is currently in use)
+ + diff --git a/package.json b/package.json index b8325e0c2e1..b68c6b17dc3 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,113 @@ { "name": "leaflet", - "version": "1.0.0-rc.1", + "version": "1.6.0", + "homepage": "https://leafletjs.com/", "description": "JavaScript library for mobile-friendly interactive maps", "devDependencies": { - "eslint": "^2.8.0", - "eslint-config-mourner": "^2.0.1", - "git-rev": "^0.2.1", - "happen": "~0.3.1", +<<<<<<< HEAD + "eslint": "^1.5.1", + "eslint-config-mourner": "^1.0.0", + "happen": "~0.2.0", "jake": "~8.0.12", - "karma": "~0.13.22", - "karma-chrome-launcher": "^0.2.3", - "karma-coverage": "~0.5.5", - "karma-firefox-launcher": "~0.1.7", - "karma-mocha": "~0.2.2", - "karma-phantomjs-launcher": "^1.0.0", + "karma": "^0.13.19", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "~0.5.1", + "karma-firefox-launcher": "~0.1.6", + "karma-mocha": "~0.2.0", + "karma-phantomjs-launcher": "^0.2.0", "karma-safari-launcher": "~0.1.1", - "leafdoc": "^1.2.2", - "mocha": "~2.4.5", - "phantomjs-prebuilt": "^2.1.7", - "prosthetic-hand": "^1.3.0", - "source-map": "^0.5.3", - "uglify-js": "~2.6.2" + "karma-slimerjs-launcher": "^0.2.0", + "magic-string": "^0.7.0", + "mocha": "~2.3.0", + "phantomjs": "^1.9.18", + "slimerjs": "^0.9.6-2", + "uglify-js": "~2.4.23" +======= + "eslint": "^4.19.1", + "eslint-config-mourner": "^2.0.1", + "git-rev-sync": "^1.12.0", + "happen": "~0.3.2", + "karma": "^4.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-expect": "^1.1.3", + "karma-firefox-launcher": "~1.1.0", + "karma-mocha": "^1.3.0", + "karma-phantomjs-launcher": "^1.0.4", + "karma-rollup-preprocessor": "^5.0.1", + "karma-safari-launcher": "~1.0.0", + "karma-sinon": "^1.0.5", + "leafdoc": "^1.4.1", + "mocha": "^6.1.4", + "phantomjs-prebuilt": "^2.1.16", + "prosthetic-hand": "^1.3.1", + "rollup": "0.51.8", + "rollup-plugin-git-version": "0.2.1", + "rollup-plugin-json": "^4.0.0", + "sinon": "^7.3.2", + "ssri": "^6.0.1", + "uglify-js": "~3.5.10" +>>>>>>> master }, "main": "dist/leaflet-src.js", "style": "dist/leaflet.css", + "files": [ + "dist", + "src", + "!dist/leaflet.zip" + ], "scripts": { - "test": "jake test", - "build": "jake build", - "release": "./build/publish.sh" + "docs": "node ./build/docs.js", + "pretest": "npm run lint", + "test": "npm run test-nolint", + "test-nolint": "karma start ./spec/karma.conf.js", + "build": "npm run rollup && npm run uglify", + "release": "./build/publish.sh", + "lint": "eslint src spec/suites docs/docs/js", + "lintfix": "npm run lint -- --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 --source-map filename=dist/leaflet.js.map --in-source-map dist/leaflet-src.js.map --source-map-url leaflet.js.map --comments", + "integrity": "node ./build/integrity.js" + }, + "eslintConfig": { + "root": true, + "globals": { + "L": true + }, + "env": { + "commonjs": true, + "amd": true, + "node": false + }, + "extends": "mourner", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "rules": { + "linebreak-style": [ + 0, + "unix" + ], + "no-mixed-spaces-and-tabs": [ + 2, + "smart-tabs" + ], + "indent": [ + 2, + "tab", + { + "VariableDeclarator": 0, + "flatTernaryExpressions": true + } + ], + "curly": 2, + "spaced-comment": 2, + "strict": 0, + "wrap-iife": 0, + "key-spacing": 0, + "consistent-return": 0 + } }, "repository": { "type": "git", diff --git a/spec/karma.conf.js b/spec/karma.conf.js index d73fe0467d9..acb9a501e2c 100644 --- a/spec/karma.conf.js +++ b/spec/karma.conf.js @@ -23,6 +23,7 @@ module.exports = function (config) { 'karma-mocha', 'karma-coverage', 'karma-phantomjs-launcher', + 'karma-slimerjs-launcher', 'karma-chrome-launcher', 'karma-safari-launcher', 'karma-firefox-launcher'], diff --git a/spec/suites/layer/TileLayerSpec.js b/spec/suites/layer/TileLayerSpec.js new file mode 100644 index 00000000000..d4949f95397 --- /dev/null +++ b/spec/suites/layer/TileLayerSpec.js @@ -0,0 +1,102 @@ + +describe('TileLayer', function () { + var tileUrl = '', + map; + + beforeEach(function () { + map = L.map(document.createElement('div')); + }); + + describe("#onAdd", function () { + it('is called after viewreset on first map load', function () { + var layer = L.tileLayer(tileUrl).addTo(map); + layer.onAdd = sinon.spy(); + + var onReset = sinon.spy(); + map.on('viewreset', onReset); + map.setView([0, 0], 0); + + expect(onReset.calledBefore(layer.onAdd)).to.be.ok(); + }); + }); + + describe("#getMaxZoom, #getMinZoom", function () { + describe("when a tilelayer is added to a map with no other layers", function () { + it("has the same zoomlevels as the tilelayer", function () { + var maxZoom = 10, + minZoom = 5; + + map.setView([0, 0], 1); + + L.tileLayer(tileUrl, { + maxZoom: maxZoom, + minZoom: minZoom + }).addTo(map); + + expect(map.getMaxZoom()).to.be(maxZoom); + expect(map.getMinZoom()).to.be(minZoom); + }); + }); + + describe("accessing a tilelayer's properties", function () { + it('provides a container', function () { + map.setView([0, 0], 1); + + var layer = L.tileLayer(tileUrl).addTo(map); + expect(layer.getContainer()).to.be.ok(); + }); + }); + + describe("when a tilelayer is added to a map that already has a tilelayer", function () { + it("has its zoomlevels updated to fit the new layer", function () { + map.setView([0, 0], 1); + + L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 15}).addTo(map); + expect(map.getMinZoom()).to.be(10); + expect(map.getMaxZoom()).to.be(15); + + L.tileLayer(tileUrl, {minZoom: 5, maxZoom: 10}).addTo(map); + expect(map.getMinZoom()).to.be(5); // changed + expect(map.getMaxZoom()).to.be(15); // unchanged + + L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 20}).addTo(map); + expect(map.getMinZoom()).to.be(5); // unchanged + expect(map.getMaxZoom()).to.be(20); // changed + + + L.tileLayer(tileUrl, {minZoom: 0, maxZoom: 25}).addTo(map); + expect(map.getMinZoom()).to.be(0); // changed + expect(map.getMaxZoom()).to.be(25); // changed + }); + }); + describe("when a tilelayer is removed from a map", function () { + it("has its zoomlevels updated to only fit the layers it currently has", function () { + var tiles = [ L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 15}).addTo(map), + L.tileLayer(tileUrl, {minZoom: 5, maxZoom: 10}).addTo(map), + L.tileLayer(tileUrl, {minZoom: 10, maxZoom: 20}).addTo(map), + L.tileLayer(tileUrl, {minZoom: 0, maxZoom: 25}).addTo(map) + ]; + map.whenReady(function () { + expect(map.getMinZoom()).to.be(0); + expect(map.getMaxZoom()).to.be(25); + + map.removeLayer(tiles[0]); + expect(map.getMinZoom()).to.be(0); + expect(map.getMaxZoom()).to.be(25); + + map.removeLayer(tiles[3]); + expect(map.getMinZoom()).to.be(5); + expect(map.getMaxZoom()).to.be(20); + + map.removeLayer(tiles[2]); + expect(map.getMinZoom()).to.be(5); + expect(map.getMaxZoom()).to.be(10); + + map.removeLayer(tiles[1]); + expect(map.getMinZoom()).to.be(0); + expect(map.getMaxZoom()).to.be(Infinity); + }); + }); + }); + }); +}); diff --git a/src/Leaflet.js b/src/Leaflet.js index 1c3f093569f..ac301f8350f 100644 --- a/src/Leaflet.js +++ b/src/Leaflet.js @@ -1,29 +1,27 @@ - -var L = { - version: 'dev' -}; - -function expose() { - var oldL = window.L; - - L.noConflict = function () { - window.L = oldL; - return this; - }; - - 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(); -} + +import {version} from '../package.json'; +export {version}; + +// control +export * from './control/index'; + +// core +export * from './core/index'; + +// dom +export * from './dom/index'; + +// geometry +export * from './geometry/index'; + +// geo +export * from './geo/index'; + +// layer +export * from './layer/index'; + +// map +export * from './map/index'; + +import {freeze} from './core/Util'; +Object.freeze = freeze; diff --git a/src/control/Control.Layers.js b/src/control/Control.Layers.js index 1aa5410c3a7..bc3db8c0ff6 100644 --- a/src/control/Control.Layers.js +++ b/src/control/Control.Layers.js @@ -1,384 +1,252 @@ -/* - * @class Control.Layers - * @aka L.Control.Layers - * @inherits Control - * - * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`. - * - * @example - * - * ```js - * var baseLayers = { - * "Mapbox": mapbox, - * "OpenStreetMap": osm - * }; - * - * var overlays = { - * "Marker": marker, - * "Roads": roadsLayer - * }; - * - * L.control.layers(baseLayers, overlays).addTo(map); - * ``` - * - * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values: - * - * ```js - * { - * "": layer1, - * "": layer2 - * } - * ``` - * - * The layer names can contain HTML, which allows you to add additional styling to the items: - * - * ```js - * {" My Layer": myLayer} - * ``` - */ - - -L.Control.Layers = L.Control.extend({ - // @section - // @aka Control.Layers options - options: { - // @option collapsed: Boolean = true - // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch. - collapsed: true, - position: 'topright', - - // @option autoZIndex: Boolean = true - // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. - autoZIndex: true, - - // @option hideSingleBase: Boolean = false - // If `true`, the base layers in the control will be hidden when there is only one. - hideSingleBase: false - }, - - initialize: function (baseLayers, overlays, options) { - L.setOptions(this, options); - - this._layers = []; - this._lastZIndex = 0; - this._handlingClick = false; - - for (var i in baseLayers) { - this._addLayer(baseLayers[i], i); - } - - for (i in overlays) { - this._addLayer(overlays[i], i, true); - } - }, - - onAdd: function (map) { - this._initLayout(); - this._update(); - - this._map = map; - map.on('zoomend', this._checkDisabledLayers, this); - - return this._container; - }, - - onRemove: function () { - this._map.off('zoomend', this._checkDisabledLayers, this); - - for (var id in this._layers) { - this._layers[id].layer.off('add remove', this._onLayerChange, this); - } - }, - - // @method addBaseLayer(layer: Layer, name: String): this - // Adds a base layer (radio button entry) with the given name to the control. - addBaseLayer: function (layer, name) { - this._addLayer(layer, name); - return (this._map) ? this._update() : this; - }, - - // @method addOverlay(layer: Layer, name: String): this - // Adds an overlay (checkbox entry) with the given name to the control. - addOverlay: function (layer, name) { - this._addLayer(layer, name, true); - return (this._map) ? this._update() : this; - }, - - // @method removeLayer(layer: Layer): this - // Remove the given layer from the control. - removeLayer: function (layer) { - layer.off('add remove', this._onLayerChange, this); - - var obj = this._getLayer(L.stamp(layer)); - if (obj) { - this._layers.splice(this._layers.indexOf(obj), 1); - } - return (this._map) ? this._update() : this; - }, - - // @method expand(): this - // Expand the control container if collapsed. - expand: function () { - L.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'); - this._form.style.height = acceptableHeight + 'px'; - } else { - L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar'); - } - this._checkDisabledLayers(); - return this; - }, - - // @method collapse(): this - // Collapse the control container if expanded. - collapse: function () { - L.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); - - // 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); - } - - var form = this._form = L.DomUtil.create('form', className + '-list'); - - if (this.options.collapsed) { - if (!L.Browser.android) { - L.DomEvent.on(container, { - mouseenter: this.expand, - mouseleave: this.collapse - }, this); - } - - var link = this._layersLink = L.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); - } else { - L.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); - }, this); - - this._map.on('click', this.collapse, this); - // TODO keyboard accessibility - } else { - 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); - - container.appendChild(form); - }, - - _getLayer: function (id) { - for (var i = 0; i <= this._layers.length; i++) { - - if (this._layers[i] && L.stamp(this._layers[i].layer) === id) { - return this._layers[i]; - } - } - }, - - _addLayer: function (layer, name, overlay) { - layer.on('add remove', this._onLayerChange, this); - - this._layers.push({ - layer: layer, - name: name, - overlay: overlay - }); - - if (this.options.autoZIndex && layer.setZIndex) { - this._lastZIndex++; - layer.setZIndex(this._lastZIndex); - } - }, - - _update: function () { - if (!this._container) { return this; } - - L.DomUtil.empty(this._baseLayersList); - L.DomUtil.empty(this._overlaysList); - - var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; - - for (i in this._layers) { - obj = this._layers[i]; - this._addItem(obj); - overlaysPresent = overlaysPresent || obj.overlay; - baseLayersPresent = baseLayersPresent || !obj.overlay; - baseLayersCount += !obj.overlay ? 1 : 0; - } - - // Hide base layers section if there's only one layer. - if (this.options.hideSingleBase) { - baseLayersPresent = baseLayersPresent && baseLayersCount > 1; - this._baseLayersList.style.display = baseLayersPresent ? '' : 'none'; - } - - this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; - - return this; - }, - - _onLayerChange: function (e) { - if (!this._handlingClick) { - this._update(); - } - - var obj = this._getLayer(L.stamp(e.target)); - - // @namespace Map - // @section Layer events - // @event baselayerchange: LayersControlEvent - // Fired when the base layer is changed through the [layer control](#control-layers). - // @event overlayadd: LayersControlEvent - // Fired when an overlay is selected through the [layer control](#control-layers). - // @event overlayremove: LayersControlEvent - // Fired when an overlay is deselected through the [layer control](#control-layers). - // @namespace Control.Layers - var type = obj.overlay ? - (e.type === 'add' ? 'overlayadd' : 'overlayremove') : - (e.type === 'add' ? 'baselayerchange' : null); - - if (type) { - this._map.fire(type, obj); - } - }, - - // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) - _createRadioElement: function (name, checked) { - - var radioHtml = ''; - - var radioFragment = document.createElement('div'); - radioFragment.innerHTML = radioHtml; - - return radioFragment.firstChild; - }, - - _addItem: function (obj) { - var label = document.createElement('label'), - checked = this._map.hasLayer(obj.layer), - input; - - if (obj.overlay) { - input = document.createElement('input'); - input.type = 'checkbox'; - input.className = 'leaflet-control-layers-selector'; - input.defaultChecked = checked; - } else { - input = this._createRadioElement('leaflet-base-layers', checked); - } - - input.layerId = L.stamp(obj.layer); - - L.DomEvent.on(input, 'click', this._onInputClick, this); - - var name = document.createElement('span'); - name.innerHTML = ' ' + obj.name; - - // Helps from preventing layer control flicker when checkboxes are disabled - // https://github.com/Leaflet/Leaflet/issues/2771 - var holder = document.createElement('div'); - - label.appendChild(holder); - holder.appendChild(input); - holder.appendChild(name); - - var container = obj.overlay ? this._overlaysList : this._baseLayersList; - container.appendChild(label); - - this._checkDisabledLayers(); - return label; - }, - - _onInputClick: function () { - var inputs = this._form.getElementsByTagName('input'), - input, layer, hasLayer; - var addedLayers = [], - removedLayers = []; - - this._handlingClick = true; - - for (var i = inputs.length - 1; i >= 0; i--) { - input = inputs[i]; - layer = this._getLayer(input.layerId).layer; - hasLayer = this._map.hasLayer(layer); - - if (input.checked && !hasLayer) { - addedLayers.push(layer); - - } else if (!input.checked && hasLayer) { - removedLayers.push(layer); - } - } - - // Bugfix issue 2318: Should remove all old layers before readding new ones - for (i = 0; i < removedLayers.length; i++) { - this._map.removeLayer(removedLayers[i]); - } - for (i = 0; i < addedLayers.length; i++) { - this._map.addLayer(addedLayers[i]); - } - - this._handlingClick = false; - - this._refocusOnMap(); - }, - - _checkDisabledLayers: function () { - var inputs = this._form.getElementsByTagName('input'), - input, - layer, - zoom = this._map.getZoom(); - - for (var i = inputs.length - 1; i >= 0; i--) { - input = inputs[i]; - layer = this._getLayer(input.layerId).layer; - input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) || - (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom); - - } - }, - - _expand: function () { - // Backward compatibility, remove me in 1.1. - return this.expand(); - }, - - _collapse: function () { - // Backward compatibility, remove me in 1.1. - return this.collapse(); - } - -}); - - -// @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); -}; +/* + * L.Control.Layers is a control to allow users to switch between different layers on the map. + */ + +L.Control.Layers = L.Control.extend({ + options: { + collapsed: true, + position: 'topright', + autoZIndex: true + }, + + initialize: function (baseLayers, overlays, options) { + L.setOptions(this, options); + + this._layers = {}; + this._lastZIndex = 0; + this._handlingClick = false; + + for (var i in baseLayers) { + this._addLayer(baseLayers[i], i); + } + + for (i in overlays) { + this._addLayer(overlays[i], i, true); + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + map + .on('layeradd', this._onLayerChange, this) + .on('layerremove', this._onLayerChange, this); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerChange, this) + .off('layerremove', this._onLayerChange, this); + }, + + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + this._update(); + return this; + }, + + addOverlay: function (layer, name) { + this._addLayer(layer, name, true); + this._update(); + return this; + }, + + removeLayer: function (layer) { + var id = L.stamp(layer); + delete this._layers[id]; + this._update(); + return this; + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = L.DomUtil.create('div', className); + + //Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + if (!L.Browser.touch) { + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + } else { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } + + var form = this._form = L.DomUtil.create('form', className + '-list'); + + if (this.options.collapsed) { + if (!L.Browser.android) { + L.DomEvent + .on(container, 'mouseover', this._expand, this) + .on(container, 'mouseout', this._collapse, this); + } + var link = this._layersLink = L.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); + } + else { + L.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); + }, this); + + this._map.on('click', this._collapse, this); + // TODO keyboard accessibility + } else { + 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); + + container.appendChild(form); + }, + + _addLayer: function (layer, name, overlay) { + var id = L.stamp(layer); + + this._layers[id] = { + layer: layer, + name: name, + overlay: overlay + }; + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + }, + + _update: function () { + if (!this._container) { + return; + } + + this._baseLayersList.innerHTML = ''; + this._overlaysList.innerHTML = ''; + + var baseLayersPresent = false, + overlaysPresent = false, + i, obj; + + for (i in this._layers) { + obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + } + + this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; + }, + + _onLayerChange: function (e) { + var obj = this._layers[L.stamp(e.layer)]; + + if (!obj) { return; } + + if (!this._handlingClick) { + this._update(); + } + + var type = obj.overlay ? + (e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') : + (e.type === 'layeradd' ? 'baselayerchange' : null); + + if (type) { + this._map.fire(type, obj); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + + var radioHtml = ' 1 - }; - -}()); +/* + * L.Browser handles different browser and feature detections for internal Leaflet use. + */ + +(function () { + + var ie = 'ActiveXObject' in window, + ielt9 = ie && !document.addEventListener, + + // terrible browser detection to work around Safari / iOS / Android browser bugs + ua = navigator.userAgent.toLowerCase(), + webkit = ua.indexOf('webkit') !== -1, + chrome = ua.indexOf('chrome') !== -1, + phantomjs = ua.indexOf('phantom') !== -1, + android = ua.indexOf('android') !== -1, + android23 = ua.search('android [23]') !== -1, + gecko = ua.indexOf('gecko') !== -1, + + mobile = typeof orientation !== undefined + '', + msPointer = !window.PointerEvent && window.MSPointerEvent, + pointer = (window.PointerEvent && window.navigator.pointerEnabled) || + msPointer, + retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || + ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && + window.matchMedia('(min-resolution:144dpi)').matches), + + doc = document.documentElement, + ie3d = ie && ('transition' in doc.style), + webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, + gecko3d = 'MozPerspective' in doc.style, + opera3d = 'OTransition' in doc.style, + any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs; + + var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window || + (window.DocumentTouch && document instanceof window.DocumentTouch)); + + L.Browser = { + ie: ie, + ielt9: ielt9, + webkit: webkit, + gecko: gecko && !webkit && !window.opera && !ie, + + android: android, + android23: android23, + + chrome: chrome, + + ie3d: ie3d, + webkit3d: webkit3d, + gecko3d: gecko3d, + opera3d: opera3d, + any3d: any3d, + + mobile: mobile, + mobileWebkit: mobile && webkit, + mobileWebkit3d: mobile && webkit3d, + mobileOpera: mobile && window.opera, + + touch: touch, + msPointer: msPointer, + pointer: pointer, + + retina: retina + }; + +}()); diff --git a/src/dom/DomEvent.Pointer.js b/src/dom/DomEvent.Pointer.js index 81cff1f0387..254696b2163 100644 --- a/src/dom/DomEvent.Pointer.js +++ b/src/dom/DomEvent.Pointer.js @@ -4,128 +4,153 @@ 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', + //static + 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'], - _pointers: {}, - _pointersCount: 0, + _pointers: [], + _pointerDocumentListener: 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 + // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019 + //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 addPointerListener: function (obj, type, handler, id) { - if (type === 'touchstart') { - this._addPointerStart(obj, handler, id); + switch (type) { + case 'touchstart': + return this.addPointerListenerStart(obj, type, handler, id); + case 'touchend': + return this.addPointerListenerEnd(obj, type, handler, id); + case 'touchmove': + return this.addPointerListenerMove(obj, type, handler, id); + default: + throw 'Unknown touch event type'; + } + }, - } else if (type === 'touchmove') { - this._addPointerMove(obj, handler, id); + addPointerListenerStart: function (obj, type, handler, id) { + var pre = '_leaflet_', + pointers = this._pointers; - } else if (type === 'touchend') { - this._addPointerEnd(obj, handler, id); - } + var cb = function (e) { + if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { + L.DomEvent.preventDefault(e); + } - return this; - }, + var alreadyInArray = false; + for (var i = 0; i < pointers.length; i++) { + if (pointers[i].pointerId === e.pointerId) { + alreadyInArray = true; + break; + } + } + if (!alreadyInArray) { + pointers.push(e); + } - removePointerListener: function (obj, type, id) { - var handler = obj['_leaflet_' + type + id]; + e.touches = pointers.slice(); + e.changedTouches = [e]; - if (type === 'touchstart') { - obj.removeEventListener(this.POINTER_DOWN, handler, false); + handler(e); + }; - } else if (type === 'touchmove') { - obj.removeEventListener(this.POINTER_MOVE, handler, false); + obj[pre + 'touchstart' + id] = cb; + obj.addEventListener(this.POINTER_DOWN, cb, false); - } else if (type === 'touchend') { - obj.removeEventListener(this.POINTER_UP, handler, false); - obj.removeEventListener(this.POINTER_CANCEL, handler, false); + // need to also listen for end events to keep the _pointers list accurate + // this needs to be on the body and never go away + if (!this._pointerDocumentListener) { + var internalCb = function (e) { + for (var i = 0; i < pointers.length; i++) { + if (pointers[i].pointerId === e.pointerId) { + pointers.splice(i, 1); + break; + } + } + }; + //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there + document.documentElement.addEventListener(this.POINTER_UP, internalCb, false); + document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false); + + this._pointerDocumentListener = true; } return this; }, - _addPointerStart: function (obj, handler, id) { - var onDown = L.bind(function (e) { - if (e.pointerType !== '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; - } - } + addPointerListenerMove: function (obj, type, handler, id) { + var pre = '_leaflet_', + touches = this._pointers; - this._handlePointer(e, handler); - }, this); + function cb(e) { - obj['_leaflet_touchstart' + id] = onDown; - obj.addEventListener(this.POINTER_DOWN, onDown, false); + // don't fire touch moves when mouse isn't down + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } - // 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); + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + touches[i] = e; + break; + } + } - // 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); + e.touches = touches.slice(); + e.changedTouches = [e]; - this._pointerDocListener = true; + handler(e); } - }, - _globalPointerDown: function (e) { - this._pointers[e.pointerId] = e; - this._pointersCount++; - }, + obj[pre + 'touchmove' + id] = cb; + obj.addEventListener(this.POINTER_MOVE, cb, false); - _globalPointerMove: function (e) { - if (this._pointers[e.pointerId]) { - this._pointers[e.pointerId] = e; - } + return this; }, - _globalPointerUp: function (e) { - delete this._pointers[e.pointerId]; - this._pointersCount--; - }, + addPointerListenerEnd: function (obj, type, handler, id) { + var pre = '_leaflet_', + touches = this._pointers; - _handlePointer: function (e, handler) { - e.touches = []; - for (var i in this._pointers) { - e.touches.push(this._pointers[i]); - } - e.changedTouches = [e]; + var cb = function (e) { + for (var i = 0; i < touches.length; i++) { + if (touches[i].pointerId === e.pointerId) { + touches.splice(i, 1); + break; + } + } - handler(e); - }, + e.touches = touches.slice(); + e.changedTouches = [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; } + handler(e); + }; - this._handlePointer(e, handler); - }, this); + obj[pre + 'touchend' + id] = cb; + obj.addEventListener(this.POINTER_UP, cb, false); + obj.addEventListener(this.POINTER_CANCEL, cb, false); - obj['_leaflet_touchmove' + id] = onMove; - obj.addEventListener(this.POINTER_MOVE, onMove, false); + return this; }, - _addPointerEnd: function (obj, handler, id) { - var onUp = L.bind(function (e) { - this._handlePointer(e, handler); - }, this); + removePointerListener: function (obj, type, id) { + var pre = '_leaflet_', + cb = obj[pre + type + id]; + + switch (type) { + case 'touchstart': + obj.removeEventListener(this.POINTER_DOWN, cb, false); + break; + case 'touchmove': + obj.removeEventListener(this.POINTER_MOVE, cb, false); + break; + case 'touchend': + obj.removeEventListener(this.POINTER_UP, cb, false); + obj.removeEventListener(this.POINTER_CANCEL, cb, false); + break; + } - obj['_leaflet_touchend' + id] = onUp; - obj.addEventListener(this.POINTER_UP, onUp, false); - obj.addEventListener(this.POINTER_CANCEL, onUp, false); + return this; } }); diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index fd088329a38..dd2f80bd15a 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -1,204 +1,141 @@ -/* - * @class Draggable - * @aka L.Draggable - * @inherits Evented - * - * A class for making DOM elements draggable (including touch support). - * Used internally for map and marker dragging. Only works for elements - * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition). - * - * @example - * ```js - * var draggable = new L.Draggable(elementToDrag); - * draggable.enable(); - * ``` - */ - -L.Draggable = L.Evented.extend({ - - options: { - // @option clickTolerance: Number = 3 - // The max number of pixels a user can shift the mouse pointer during a click - // for it to be considered a valid click (as opposed to a mouse drag). - 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) { - this._element = element; - this._dragStartTarget = dragStartTarget || element; - this._preventOutline = preventOutline; - }, - - // @method enable() - // Enables the dragging ability - enable: function () { - if (this._enabled) { return; } - - L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); - - this._enabled = true; - }, - - // @method disable() - // Disables the dragging ability - disable: function () { - if (!this._enabled) { return; } - - L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this); - - this._enabled = false; - this._moved = false; - }, - - _onDown: function (e) { - // Ignore simulated events, since we handle both touch and - // mouse explicitly; otherwise we risk getting duplicates of - // touch events, see #4315. - if (e._simulated) { return; } - - this._moved = false; - - if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; } - - if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; } - L.Draggable._dragging = true; // Prevent dragging multiple objects at once. - - if (this._preventOutline) { - L.DomUtil.preventOutline(this._element); - } - - L.DomUtil.disableImageDrag(); - L.DomUtil.disableTextSelection(); - - if (this._moving) { return; } - - // @event down: Event - // Fired when a drag is about to start. - this.fire('down'); - - var first = e.touches ? e.touches[0] : e; - - this._startPoint = new L.Point(first.clientX, first.clientY); - this._startPos = this._newPos = L.DomUtil.getPosition(this._element); - - L.DomEvent - .on(document, L.Draggable.MOVE[e.type], this._onMove, this) - .on(document, L.Draggable.END[e.type], this._onUp, this); - }, - - _onMove: function (e) { - // Ignore simulated events, since we handle both touch and - // mouse explicitly; otherwise we risk getting duplicates of - // touch events, see #4315. - if (e._simulated) { return; } - - if (e.touches && e.touches.length > 1) { - this._moved = true; - return; - } - - var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), - newPoint = new L.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); - - if (!this._moved) { - // @event dragstart: Event - // Fired when a drag starts - this.fire('dragstart'); - - this._moved = true; - this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); - - L.DomUtil.addClass(document.body, 'leaflet-dragging'); - - this._lastTarget = e.target || e.srcElement; - // IE and Edge do not give the element, so fetch it - // if necessary - if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) { - this._lastTarget = this._lastTarget.correspondingUseElement; - } - L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); - } - - this._newPos = this._startPos.add(offset); - this._moving = true; - - L.Util.cancelAnimFrame(this._animRequest); - this._lastEvent = e; - this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true); - }, - - _updatePosition: function () { - var e = {originalEvent: this._lastEvent}; - - // @event predrag: Event - // Fired continuously during dragging *before* each corresponding - // update of the element's position. - this.fire('predrag', e); - L.DomUtil.setPosition(this._element, this._newPos); - - // @event drag: Event - // Fired continuously during dragging. - this.fire('drag', e); - }, - - _onUp: function (e) { - // Ignore simulated events, since we handle both touch and - // mouse explicitly; otherwise we risk getting duplicates of - // touch events, see #4315. - if (e._simulated) { return; } - - L.DomUtil.removeClass(document.body, 'leaflet-dragging'); - - if (this._lastTarget) { - L.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); - } - - L.DomUtil.enableImageDrag(); - L.DomUtil.enableTextSelection(); - - if (this._moved && this._moving) { - // ensure drag is not fired after dragend - L.Util.cancelAnimFrame(this._animRequest); - - // @event dragend: DragEndEvent - // Fired when the drag ends. - this.fire('dragend', { - distance: this._newPos.distanceTo(this._startPos) - }); - } - - this._moving = false; - L.Draggable._dragging = false; - } -}); +/* + * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too. + */ + +L.Draggable = L.Class.extend({ + includes: L.Mixin.Events, + + 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' + } + }, + + initialize: function (element, dragStartTarget) { + this._element = element; + this._dragStartTarget = dragStartTarget || element; + }, + + enable: function () { + if (this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + + this._enabled = true; + }, + + disable: function () { + if (!this._enabled) { return; } + + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } + + this._enabled = false; + this._moved = false; + }, + + _onDown: function (e) { + this._moved = false; + + if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } + + L.DomEvent.stopPropagation(e); + + if (L.Draggable._disabled) { return; } + + L.DomUtil.disableImageDrag(); + L.DomUtil.disableTextSelection(); + + if (this._moving) { return; } + + var first = e.touches ? e.touches[0] : e; + + this._startPoint = new L.Point(first.clientX, first.clientY); + this._startPos = this._newPos = L.DomUtil.getPosition(this._element); + + L.DomEvent + .on(document, L.Draggable.MOVE[e.type], this._onMove, this) + .on(document, L.Draggable.END[e.type], this._onUp, this); + }, + + _onMove: function (e) { + if (e.touches && e.touches.length > 1) { + this._moved = true; + return; + } + + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + newPoint = new L.Point(first.clientX, first.clientY), + offset = newPoint.subtract(this._startPoint); + + if (!offset.x && !offset.y) { return; } + if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; } + + L.DomEvent.preventDefault(e); + + if (!this._moved) { + this.fire('dragstart'); + + this._moved = true; + this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); + + L.DomUtil.addClass(document.body, 'leaflet-dragging'); + this._lastTarget = e.target || e.srcElement; + L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target'); + } + + this._newPos = this._startPos.add(offset); + this._moving = true; + + L.Util.cancelAnimFrame(this._animRequest); + this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget); + }, + + _updatePosition: function () { + this.fire('predrag'); + L.DomUtil.setPosition(this._element, this._newPos); + this.fire('drag'); + }, + + _onUp: function () { + L.DomUtil.removeClass(document.body, 'leaflet-dragging'); + + if (this._lastTarget) { + L.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) + .off(document, L.Draggable.END[i], this._onUp); + } + + L.DomUtil.enableImageDrag(); + L.DomUtil.enableTextSelection(); + + if (this._moved && this._moving) { + // ensure drag is not fired after dragend + L.Util.cancelAnimFrame(this._animRequest); + + this.fire('dragend', { + distance: this._newPos.distanceTo(this._startPos) + }); + } + + this._moving = false; + } +}); diff --git a/src/layer/FeatureGroup.js b/src/layer/FeatureGroup.js index 02207a6cd1c..f7ea3339114 100644 --- a/src/layer/FeatureGroup.js +++ b/src/layer/FeatureGroup.js @@ -1,82 +1,100 @@ -/* - * @class FeatureGroup - * @aka L.FeatureGroup - * @inherits LayerGroup - * - * Extended `LayerGroup` that also has mouse events (propagated from members of the group) and a shared bindPopup method. - * - * @example - * - * ```js - * L.featureGroup([marker1, marker2, polyline]) - * .bindPopup('Hello world!') - * .on('click', function() { alert('Clicked on a group!'); }) - * .addTo(map); - * ``` - */ - -L.FeatureGroup = L.LayerGroup.extend({ - - addLayer: function (layer) { - if (this.hasLayer(layer)) { - return this; - } - - layer.addEventParent(this); - - L.LayerGroup.prototype.addLayer.call(this, layer); - - return this.fire('layeradd', {layer: layer}); - }, - - removeLayer: function (layer) { - if (!this.hasLayer(layer)) { - return this; - } - if (layer in this._layers) { - layer = this._layers[layer]; - } - - layer.removeEventParent(this); - - L.LayerGroup.prototype.removeLayer.call(this, layer); - - return this.fire('layerremove', {layer: layer}); - }, - - // @method setStyle(style: Path options): this - // Sets the given path options to each layer of the group that has a `setStyle` method. - setStyle: function (style) { - return this.invoke('setStyle', style); - }, - - // @method bringToFront(): this - // Brings the layer group to the top of all other layers - bringToFront: function () { - return this.invoke('bringToFront'); - }, - - // @method bringToBack(): this - // Brings the layer group to the top of all other layers - bringToBack: function () { - return this.invoke('bringToBack'); - }, - - // @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(); - - for (var id in this._layers) { - var layer = this._layers[id]; - bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); - } - return bounds; - } -}); - -// @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); -}; +/* + * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods + * shared between a group of interactive layers (like vectors or markers). + */ + +L.FeatureGroup = L.LayerGroup.extend({ + includes: L.Mixin.Events, + + statics: { + EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose' + }, + + addLayer: function (layer) { + if (this.hasLayer(layer)) { + return this; + } + + if ('on' in layer) { + layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); + } + + L.LayerGroup.prototype.addLayer.call(this, layer); + + if (this._popupContent && layer.bindPopup) { + layer.bindPopup(this._popupContent, this._popupOptions); + } + + return this.fire('layeradd', {layer: layer}); + }, + + removeLayer: function (layer) { + if (!this.hasLayer(layer)) { + return this; + } + if (layer in this._layers) { + layer = this._layers[layer]; + } + + if ('off' in layer) { + layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this); + } + + L.LayerGroup.prototype.removeLayer.call(this, layer); + + if (this._popupContent) { + this.invoke('unbindPopup'); + } + + return this.fire('layerremove', {layer: layer}); + }, + + bindPopup: function (content, options) { + this._popupContent = content; + this._popupOptions = options; + return this.invoke('bindPopup', content, options); + }, + + openPopup: function (latlng) { + // open popup on the first layer + for (var id in this._layers) { + this._layers[id].openPopup(latlng); + break; + } + return this; + }, + + setStyle: function (style) { + return this.invoke('setStyle', style); + }, + + bringToFront: function () { + return this.invoke('bringToFront'); + }, + + bringToBack: function () { + return this.invoke('bringToBack'); + }, + + getBounds: function () { + var bounds = new L.LatLngBounds(); + + this.eachLayer(function (layer) { + bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds()); + }); + + return bounds; + }, + + _propagateEvent: function (e) { + e = L.extend({ + layer: e.target, + target: this + }, e); + this.fire(e.type, e); + } +}); + +L.featureGroup = function (layers) { + return new L.FeatureGroup(layers); +}; diff --git a/src/layer/marker/Marker.Popup.js b/src/layer/marker/Marker.Popup.js index b3788ff5152..048e826a89d 100644 --- a/src/layer/marker/Marker.Popup.js +++ b/src/layer/marker/Marker.Popup.js @@ -1,9 +1,90 @@ -/* - * Popup extension to L.Marker, adding popup-related methods. - */ - -L.Marker.include({ - _getPopupAnchor: function () { - return this.options.icon.options.popupAnchor || [0, 0]; - } -}); +/* + * Popup extension to L.Marker, adding popup-related methods. + */ + +L.Marker.include({ + openPopup: function () { + if (this._popup && this._map && !this._map.hasLayer(this._popup)) { + this._popup.setLatLng(this._latlng); + this._map.openPopup(this._popup); + } + + return this; + }, + + closePopup: function () { + if (this._popup) { + this._popup._close(); + } + return this; + }, + + togglePopup: function () { + if (this._popup) { + if (this._popup._isOpen) { + this.closePopup(); + } else { + this.openPopup(); + } + } + return this; + }, + + bindPopup: function (content, options) { + var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]); + + anchor = anchor.add(L.Popup.prototype.options.offset); + + if (options && options.offset) { + anchor = anchor.add(options.offset); + } + + options = L.extend({offset: anchor}, options); + + if (!this._popupHandlersAdded) { + this + .on('click', this.togglePopup, this) + .on('remove', this.closePopup, this) + .on('move', this._movePopup, this); + this._popupHandlersAdded = true; + } + + if (content instanceof L.Popup) { + L.setOptions(content, options); + this._popup = content; + content._source = this; + } else { + this._popup = new L.Popup(options, this) + .setContent(content); + } + + return this; + }, + + setPopupContent: function (content) { + if (this._popup) { + this._popup.setContent(content); + } + return this; + }, + + unbindPopup: function () { + if (this._popup) { + this._popup = null; + this + .off('click', this.togglePopup, this) + .off('remove', this.closePopup, this) + .off('move', this._movePopup, this); + this._popupHandlersAdded = false; + } + return this; + }, + + getPopup: function () { + return this._popup; + }, + + _movePopup: function (e) { + this._popup.setLatLng(e.latlng); + } +}); diff --git a/src/layer/marker/Marker.js b/src/layer/marker/Marker.js index e1f8f957735..6f3d86bbfd1 100644 --- a/src/layer/marker/Marker.js +++ b/src/layer/marker/Marker.js @@ -1,357 +1,317 @@ -/* - * @class Marker - * @inherits Layer - * @aka L.Marker - * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`. - * - * @example - * - * ```js - * L.marker([50.5, 30.5]).addTo(map); - * ``` - */ - -L.Marker = L.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. Set to new `L.Icon.Default()` by default. - icon: new L.Icon.Default(), - - // @option interactive: Boolean = true - // If `false`, the marker will not emit mouse events and will act as a part of the underlying map. - interactive: true, - - // @option draggable: Boolean = false - // Whether the marker is draggable with mouse/touch or not. - draggable: false, - - // @option keyboard: Boolean = true - // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. - keyboard: true, - - // @option title: String = '' - // Text for the browser tooltip that appear on marker hover (no tooltip by default). - title: '', - - // @option alt: String = '' - // Text for the `alt` attribute of the icon image (useful for accessibility). - alt: '', - - // @option zIndexOffset: Number = 0 - // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). - zIndexOffset: 0, - - // @option opacity: Number = 1.0 - // The opacity of the marker. - opacity: 1, - - // @option riseOnHover: Boolean = false - // If `true`, the marker will get on top of others when you hover the mouse over it. - riseOnHover: false, - - // @option riseOffset: Number = 250 - // The z-index offset used for the `riseOnHover` feature. - riseOffset: 250, - - // @option pane: String = 'markerPane' - // `Map pane` where the markers icon will be added. - pane: 'markerPane', - - // FIXME: shadowPane is no longer a valid option - nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'] - }, - - /* @section - * - * You can subscribe to the following events using [these methods](#evented-method). - * - * @event click: MouseEvent - * Fired when the user clicks (or taps) the marker. - * - * @event dblclick: MouseEvent - * Fired when the user double-clicks (or double-taps) the marker. - * - * @event mousedown: MouseEvent - * Fired when the user pushes the mouse button on the marker. - * - * @event mouseover: MouseEvent - * Fired when the mouse enters the marker. - * - * @event mouseout: MouseEvent - * Fired when the mouse leaves the marker. - * - * @event contextmenu: MouseEvent - * Fired when the user right-clicks on the marker. - */ - - - - /* @section - * - * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: - */ - - initialize: function (latlng, options) { - L.setOptions(this, options); - this._latlng = L.latLng(latlng); - }, - - onAdd: function (map) { - this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation; - - this._initIcon(); - this.update(); - }, - - onRemove: function () { - if (this.dragging && this.dragging.enabled()) { - this.options.draggable = true; - this.dragging.removeHooks(); - } - - this._removeIcon(); - this._removeShadow(); - }, - - getEvents: function () { - var events = { - zoom: this.update, - viewreset: this.update - }; - - if (this._zoomAnimated) { - events.zoomanim = this._animateZoom; - } - - return events; - }, - - // @method getLatLng: LatLng - // Returns the current geographical position of the marker. - getLatLng: function () { - return this._latlng; - }, - - // @method setLatLng(latlng: LatLng): this - // Changes the marker position to the given point. - setLatLng: function (latlng) { - var oldLatLng = this._latlng; - this._latlng = L.latLng(latlng); - this.update(); - - // @event move: Event - // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`. - return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng}); - }, - - // @method setZIndexOffset(offset: Number): this - // Changes the [zIndex offset](#marker-zindexoffset) of the marker. - setZIndexOffset: function (offset) { - this.options.zIndexOffset = offset; - return this.update(); - }, - - // @method setIcon(icon: Icon): this - // Changes the marker icon. - setIcon: function (icon) { - - this.options.icon = icon; - - if (this._map) { - this._initIcon(); - this.update(); - } - - if (this._popup) { - this.bindPopup(this._popup, this._popup.options); - } - - return this; - }, - - getElement: function () { - return this._icon; - }, - - update: function () { - - if (this._icon) { - var pos = this._map.latLngToLayerPoint(this._latlng).round(); - this._setPos(pos); - } - - return this; - }, - - _initIcon: function () { - var options = this.options, - classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide'); - - var icon = options.icon.createIcon(this._icon), - addIcon = false; - - // if we're not reusing the icon, remove the old one and init new one - if (icon !== this._icon) { - if (this._icon) { - this._removeIcon(); - } - addIcon = true; - - if (options.title) { - icon.title = options.title; - } - if (options.alt) { - icon.alt = options.alt; - } - } - - L.DomUtil.addClass(icon, classToAdd); - - if (options.keyboard) { - icon.tabIndex = '0'; - } - - this._icon = icon; - - if (options.riseOnHover) { - this.on({ - mouseover: this._bringToFront, - mouseout: this._resetZIndex - }); - } - - var newShadow = options.icon.createShadow(this._shadow), - addShadow = false; - - if (newShadow !== this._shadow) { - this._removeShadow(); - addShadow = true; - } - - if (newShadow) { - L.DomUtil.addClass(newShadow, classToAdd); - } - this._shadow = newShadow; - - - if (options.opacity < 1) { - this._updateOpacity(); - } - - - if (addIcon) { - this.getPane().appendChild(this._icon); - } - this._initInteraction(); - if (newShadow && addShadow) { - this.getPane('shadowPane').appendChild(this._shadow); - } - }, - - _removeIcon: function () { - if (this.options.riseOnHover) { - this.off({ - mouseover: this._bringToFront, - mouseout: this._resetZIndex - }); - } - - L.DomUtil.remove(this._icon); - this.removeInteractiveTarget(this._icon); - - this._icon = null; - }, - - _removeShadow: function () { - if (this._shadow) { - L.DomUtil.remove(this._shadow); - } - this._shadow = null; - }, - - _setPos: function (pos) { - L.DomUtil.setPosition(this._icon, pos); - - if (this._shadow) { - L.DomUtil.setPosition(this._shadow, pos); - } - - this._zIndex = pos.y + this.options.zIndexOffset; - - this._resetZIndex(); - }, - - _updateZIndex: function (offset) { - this._icon.style.zIndex = this._zIndex + offset; - }, - - _animateZoom: function (opt) { - var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); - - this._setPos(pos); - }, - - _initInteraction: function () { - - if (!this.options.interactive) { return; } - - L.DomUtil.addClass(this._icon, 'leaflet-interactive'); - - this.addInteractiveTarget(this._icon); - - if (L.Handler.MarkerDrag) { - var draggable = this.options.draggable; - if (this.dragging) { - draggable = this.dragging.enabled(); - this.dragging.disable(); - } - - this.dragging = new L.Handler.MarkerDrag(this); - - if (draggable) { - this.dragging.enable(); - } - } - }, - - // @method setOpacity(opacity: Number): this - // Changes the opacity of the marker. - setOpacity: function (opacity) { - this.options.opacity = opacity; - if (this._map) { - this._updateOpacity(); - } - - return this; - }, - - _updateOpacity: function () { - var opacity = this.options.opacity; - - L.DomUtil.setOpacity(this._icon, opacity); - - if (this._shadow) { - L.DomUtil.setOpacity(this._shadow, opacity); - } - }, - - _bringToFront: function () { - this._updateZIndex(this.options.riseOffset); - }, - - _resetZIndex: function () { - this._updateZIndex(0); - } -}); - - -// factory L.marker(latlng: LatLng, options? : Marker options) - -// @factory L.marker(latlng: LatLng, options? : Marker options) -// Instantiates a Marker object given a geographical point and optionally an options object. -L.marker = function (latlng, options) { - return new L.Marker(latlng, options); -}; +/* + * L.Marker is used to display clickable/draggable icons on the map. + */ + +L.Marker = L.Class.extend({ + + includes: L.Mixin.Events, + + options: { + icon: new L.Icon.Default(), + title: '', + alt: '', + clickable: true, + draggable: false, + keyboard: true, + zIndexOffset: 0, + opacity: 1, + riseOnHover: false, + riseOffset: 250 + }, + + initialize: function (latlng, options) { + L.setOptions(this, options); + this._latlng = L.latLng(latlng); + }, + + onAdd: function (map) { + this._map = map; + + map.on('viewreset', this.update, this); + + this._initIcon(); + this.update(); + this.fire('add'); + + if (map.options.zoomAnimation && map.options.markerZoomAnimation) { + map.on('zoomanim', this._animateZoom, this); + } + }, + + addTo: function (map) { + map.addLayer(this); + return this; + }, + + onRemove: function (map) { + if (this.dragging) { + this.dragging.disable(); + } + + this._removeIcon(); + this._removeShadow(); + + this.fire('remove'); + + map.off({ + 'viewreset': this.update, + 'zoomanim': this._animateZoom + }, this); + + this._map = null; + }, + + getLatLng: function () { + return this._latlng; + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + + this.update(); + + return this.fire('move', { latlng: this._latlng }); + }, + + setZIndexOffset: function (offset) { + this.options.zIndexOffset = offset; + this.update(); + + return this; + }, + + setIcon: function (icon) { + + this.options.icon = icon; + + if (this._map) { + this._initIcon(); + this.update(); + } + + if (this._popup) { + this.bindPopup(this._popup); + } + + return this; + }, + + update: function () { + if (this._icon) { + this._setPos(this._map.latLngToLayerPoint(this._latlng).round()); + } + return this; + }, + + _initIcon: function () { + var options = this.options, + map = this._map, + animation = (map.options.zoomAnimation && map.options.markerZoomAnimation), + classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide'; + + var icon = options.icon.createIcon(this._icon), + addIcon = false; + + // if we're not reusing the icon, remove the old one and init new one + if (icon !== this._icon) { + if (this._icon) { + this._removeIcon(); + } + addIcon = true; + + if (options.title) { + icon.title = options.title; + } + + if (options.alt) { + icon.alt = options.alt; + } + } + + L.DomUtil.addClass(icon, classToAdd); + + if (options.keyboard) { + icon.tabIndex = '0'; + } + + this._icon = icon; + + this._initInteraction(); + + if (options.riseOnHover) { + L.DomEvent + .on(icon, 'mouseover', this._bringToFront, this) + .on(icon, 'mouseout', this._resetZIndex, this); + } + + var newShadow = options.icon.createShadow(this._shadow), + addShadow = false; + + if (newShadow !== this._shadow) { + this._removeShadow(); + addShadow = true; + } + + if (newShadow) { + L.DomUtil.addClass(newShadow, classToAdd); + } + this._shadow = newShadow; + + + if (options.opacity < 1) { + this._updateOpacity(); + } + + + var panes = this._map._panes; + + if (addIcon) { + panes.markerPane.appendChild(this._icon); + } + + if (newShadow && addShadow) { + panes.shadowPane.appendChild(this._shadow); + } + }, + + _removeIcon: function () { + if (this.options.riseOnHover) { + L.DomEvent + .off(this._icon, 'mouseover', this._bringToFront) + .off(this._icon, 'mouseout', this._resetZIndex); + } + + this._map._panes.markerPane.removeChild(this._icon); + + this._icon = null; + }, + + _removeShadow: function () { + if (this._shadow) { + this._map._panes.shadowPane.removeChild(this._shadow); + } + this._shadow = null; + }, + + _setPos: function (pos) { + L.DomUtil.setPosition(this._icon, pos); + + if (this._shadow) { + L.DomUtil.setPosition(this._shadow, pos); + } + + this._zIndex = pos.y + this.options.zIndexOffset; + + this._resetZIndex(); + }, + + _updateZIndex: function (offset) { + this._icon.style.zIndex = this._zIndex + offset; + }, + + _animateZoom: function (opt) { + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); + + this._setPos(pos); + }, + + _initInteraction: function () { + + if (!this.options.clickable) { return; } + + // TODO refactor into something shared with Map/Path/etc. to DRY it up + + var icon = this._icon, + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; + + L.DomUtil.addClass(icon, 'leaflet-clickable'); + L.DomEvent.on(icon, 'click', this._onMouseClick, this); + L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); + + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); + } + + if (L.Handler.MarkerDrag) { + this.dragging = new L.Handler.MarkerDrag(this); + + if (this.options.draggable) { + this.dragging.enable(); + } + } + }, + + _onMouseClick: function (e) { + var wasDragged = this.dragging && this.dragging.moved(); + + if (this.hasEventListeners(e.type) || wasDragged) { + L.DomEvent.stopPropagation(e); + } + + if (wasDragged) { return; } + + if ((!this.dragging || !this.dragging._enabled) && this._map && + this._map.dragging && this._map.dragging.moved()) { return; } + + this.fire(e.type, { + originalEvent: e, + latlng: this._latlng + }); + }, + + _onKeyPress: function (e) { + if (e.keyCode === 13) { + this.fire('click', { + originalEvent: e, + latlng: this._latlng + }); + } + }, + + _fireMouseEvent: function (e) { + + this.fire(e.type, { + originalEvent: e, + latlng: this._latlng + }); + + // TODO proper custom event propagation + // this line will always be called if marker is in a FeatureGroup + if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousedown') { + L.DomEvent.stopPropagation(e); + } else { + L.DomEvent.preventDefault(e); + } + }, + + setOpacity: function (opacity) { + this.options.opacity = opacity; + if (this._map) { + this._updateOpacity(); + } + + return this; + }, + + _updateOpacity: function () { + L.DomUtil.setOpacity(this._icon, this.options.opacity); + if (this._shadow) { + L.DomUtil.setOpacity(this._shadow, this.options.opacity); + } + }, + + _bringToFront: function () { + this._updateZIndex(this.options.riseOffset); + }, + + _resetZIndex: function () { + this._updateZIndex(0); + } +}); + +L.marker = function (latlng, options) { + return new L.Marker(latlng, options); +}; diff --git a/src/layer/tile/TileLayer.Anim.js b/src/layer/tile/TileLayer.Anim.js new file mode 100644 index 00000000000..70daa065009 --- /dev/null +++ b/src/layer/tile/TileLayer.Anim.js @@ -0,0 +1,110 @@ +/* + Zoom animation logic for L.TileLayer. +*/ + +L.TileLayer.include({ + _animateZoom: function (e) { + if (!this._animating) { + this._animating = true; + this._prepareBgBuffer(); + } + + var bg = this._bgBuffer, + transform = L.DomUtil.TRANSFORM, + initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform], + scaleStr = L.DomUtil.getScaleString(e.scale, e.origin); + + bg.style[transform] = e.backwards ? + scaleStr + ' ' + initialTransform : + initialTransform + ' ' + scaleStr; + }, + + _endZoomAnim: function () { + var front = this._tileContainer, + bg = this._bgBuffer; + + front.style.visibility = ''; + front.parentNode.appendChild(front); // Bring to fore + + // force reflow + L.Util.falseFn(bg.offsetWidth); + + var zoom = this._map.getZoom(); + if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { + this._clearBgBuffer(); + } + + this._animating = false; + }, + + _clearBgBuffer: function () { + var map = this._map; + + if (map && !map._animatingZoom && !map.touchZoom._zooming) { + this._bgBuffer.innerHTML = ''; + this._bgBuffer.style[L.DomUtil.TRANSFORM] = ''; + } + }, + + _prepareBgBuffer: function () { + + var front = this._tileContainer, + bg = this._bgBuffer; + + // if foreground layer doesn't have many tiles but bg layer does, + // keep the existing bg layer and just zoom it some more + + var bgLoaded = this._getLoadedTilesPercentage(bg), + frontLoaded = this._getLoadedTilesPercentage(front); + + if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) { + + front.style.visibility = 'hidden'; + this._stopLoadingImages(front); + return; + } + + // prepare the buffer to become the front tile pane + bg.style.visibility = 'hidden'; + bg.style[L.DomUtil.TRANSFORM] = ''; + + // switch out the current layer to be the new bg layer (and vice-versa) + this._tileContainer = bg; + bg = this._bgBuffer = front; + + this._stopLoadingImages(bg); + + //prevent bg buffer from clearing right after zoom + clearTimeout(this._clearBgBufferTimer); + }, + + _getLoadedTilesPercentage: function (container) { + var tiles = container.getElementsByTagName('img'), + i, len, count = 0; + + for (i = 0, len = tiles.length; i < len; i++) { + if (tiles[i].complete) { + count++; + } + } + return count / len; + }, + + // stops loading all tiles in the background layer + _stopLoadingImages: function (container) { + var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')), + i, len, tile; + + for (i = 0, len = tiles.length; i < len; i++) { + tile = tiles[i]; + + if (!tile.complete) { + tile.onload = L.Util.falseFn; + tile.onerror = L.Util.falseFn; + tile.src = L.Util.emptyImageUrl; + + tile.parentNode.removeChild(tile); + } + } + } +}); diff --git a/src/layer/tile/TileLayer.js b/src/layer/tile/TileLayer.js index 0ce4545efa9..81df0840801 100644 --- a/src/layer/tile/TileLayer.js +++ b/src/layer/tile/TileLayer.js @@ -1,244 +1,275 @@ -/* - * @class TileLayer - * @inherits GridLayer - * @aka L.TileLayer - * Used to load and display tile layers on the map. Extends `GridLayer`. - * - * @example - * - * ```js - * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map); - * ``` - * - * @section URL template - * @example - * - * A string of the following form: - * - * ``` - * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' - * ``` - * - * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles. - * - * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: - * - * ``` - * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); - * ``` - */ - - -L.TileLayer = L.GridLayer.extend({ - - // @section - // @aka TileLayer options - options: { - // @option minZoom: Number = 0 - // Minimum zoom number. - minZoom: 0, - - // @option maxZoom: Number = 18 - // Maximum zoom number. - maxZoom: 18, - - // @option maxNativeZoom: Number = null - // Maximum zoom number the tile source has available. If it is specified, - // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded - // from `maxNativeZoom` level and auto-scaled. - maxNativeZoom: null, - - // @option subdomains: String|String[] = 'abc' - // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. - subdomains: 'abc', - - // @option errorTileUrl: String = '' - // URL to the tile image to show in place of the tile that failed to load. - errorTileUrl: '', - - // @option zoomOffset: Number = 0 - // The zoom number used in tile URLs will be offset with this value. - zoomOffset: 0, - - // @option tms: Boolean = false - // If `true`, inverses Y axis numbering for tiles (turn this on for TMS services). - tms: false, - - // @option zoomReverse: Boolean = false - // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) - zoomReverse: false, - - // @option detectRetina: Boolean = false - // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. - detectRetina: false, - - // @option crossOrigin: Boolean = false - // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data. - crossOrigin: false - }, - - initialize: function (url, options) { - - this._url = url; - - options = L.setOptions(this, options); - - // detecting retina displays, adjusting tileSize and zoom levels - if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) { - - options.tileSize = Math.floor(options.tileSize / 2); - options.zoomOffset++; - - options.minZoom = Math.max(0, options.minZoom); - options.maxZoom--; - } - - if (typeof options.subdomains === 'string') { - options.subdomains = options.subdomains.split(''); - } - - // for https://github.com/Leaflet/Leaflet/issues/137 - if (!L.Browser.android) { - this.on('tileunload', this._onTileRemove); - } - }, - - // @method setUrl(url: String, noRedraw?: Boolean): this - // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). - setUrl: function (url, noRedraw) { - this._url = url; - - if (!noRedraw) { - this.redraw(); - } - return this; - }, - - // @method createTile(coords: Object, done?: Function): HTMLElement - // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) - // to return an `` HTML element with the appropiate image URL given `coords`. The `done` - // callback is called when the tile has been loaded. - 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)); - - if (this.options.crossOrigin) { - tile.crossOrigin = ''; - } - - /* - Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons - http://www.w3.org/TR/WCAG20-TECHS/H67 - */ - tile.alt = ''; - - tile.src = this.getTileUrl(coords); - - return tile; - }, - - // @section Extension methods - // @uninheritable - // Layers extending `TileLayer` might reimplement the following method. - // @method getTileUrl(coords: Object): String - // Called only internally, returns the URL for a tile given its coordinates. - // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. - getTileUrl: function (coords) { - var data = { - r: L.Browser.retina ? '@2x' : '', - s: this._getSubdomain(coords), - x: coords.x, - y: coords.y, - z: this._getZoomForUrl() - }; - if (this._map && !this._map.options.crs.infinite) { - var invertedY = this._globalTileRange.max.y - coords.y; - if (this.options.tms) { - data['y'] = invertedY; - } - data['-y'] = invertedY; - } - - return L.Util.template(this._url, L.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); - } else { - done(null, tile); - } - }, - - _tileOnError: function (done, tile, e) { - var errorUrl = this.options.errorTileUrl; - if (errorUrl) { - tile.src = errorUrl; - } - done(e, tile); - }, - - getTileSize: function () { - var map = this._map, - tileSize = L.GridLayer.prototype.getTileSize.call(this), - zoom = this._tileZoom + this.options.zoomOffset, - zoomN = this.options.maxNativeZoom; - - // increase tile size when overscaling - return zoomN !== null && zoom > zoomN ? - tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() : - tileSize; - }, - - _onTileRemove: function (e) { - e.tile.onload = null; - }, - - _getZoomForUrl: function () { - - var options = this.options, - zoom = this._tileZoom; - - if (options.zoomReverse) { - zoom = options.maxZoom - zoom; - } - - zoom += options.zoomOffset; - - return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom; - }, - - _getSubdomain: function (tilePoint) { - var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; - return this.options.subdomains[index]; - }, - - // stops loading all tiles in the background layer - _abortLoading: function () { - var i, tile; - for (i in this._tiles) { - if (this._tiles[i].coords.z !== this._tileZoom) { - tile = this._tiles[i].el; - - tile.onload = L.Util.falseFn; - tile.onerror = L.Util.falseFn; - - if (!tile.complete) { - tile.src = L.Util.emptyImageUrl; - L.DomUtil.remove(tile); - } - } - } - } -}); - - -// @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); -}; +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 + * @aka L.TileLayer + * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`. + * + * @example + * + * ```js + * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA'}).addTo(map); + * ``` + * + * @section URL template + * @example + * + * A string of the following form: + * + * ``` + * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' + * ``` + * + * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "@2x" to the URL to load retina tiles. + * + * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this: + * + * ``` + * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'}); + * ``` + */ + + +export var TileLayer = GridLayer.extend({ + + // @section + // @aka TileLayer options + options: { + // @option minZoom: Number = 0 + // The minimum zoom level down to which this layer will be displayed (inclusive). + minZoom: 0, + + // @option maxZoom: Number = 18 + // The maximum zoom level up to which this layer will be displayed (inclusive). + maxZoom: 18, + + // @option subdomains: String|String[] = 'abc' + // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. + subdomains: 'abc', + + // @option errorTileUrl: String = '' + // URL to the tile image to show in place of the tile that failed to load. + errorTileUrl: '', + + // @option zoomOffset: Number = 0 + // The zoom number used in tile URLs will be offset with this value. + zoomOffset: 0, + + // @option tms: Boolean = false + // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). + tms: false, + + // @option zoomReverse: Boolean = false + // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) + zoomReverse: false, + + // @option detectRetina: Boolean = false + // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. + detectRetina: false, + + // @option crossOrigin: Boolean|String = false + // Whether the crossOrigin attribute will be added to the tiles. + // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. + // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. + crossOrigin: false + }, + + initialize: function (url, options) { + + this._url = url; + + options = Util.setOptions(this, options); + + // detecting retina displays, adjusting tileSize and zoom levels + if (options.detectRetina && Browser.retina && options.maxZoom > 0) { + + options.tileSize = Math.floor(options.tileSize / 2); + + if (!options.zoomReverse) { + options.zoomOffset++; + options.maxZoom--; + } else { + options.zoomOffset--; + options.minZoom++; + } + + options.minZoom = Math.max(0, options.minZoom); + } + + if (typeof options.subdomains === 'string') { + options.subdomains = options.subdomains.split(''); + } + + // for https://github.com/Leaflet/Leaflet/issues/137 + if (!Browser.android) { + this.on('tileunload', this._onTileRemove); + } + }, + + // @method setUrl(url: String, noRedraw?: Boolean): this + // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). + // If the URL does not change, the layer will not be redrawn unless + // the noRedraw parameter is set to false. + setUrl: function (url, noRedraw) { + if (this._url === url && noRedraw === undefined) { + noRedraw = true; + } + + this._url = url; + + if (!noRedraw) { + this.redraw(); + } + return this; + }, + + // @method createTile(coords: Object, done?: Function): HTMLElement + // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) + // to return an `` HTML element with the appropriate image URL given `coords`. The `done` + // callback is called when the tile has been loaded. + createTile: function (coords, done) { + var tile = document.createElement('img'); + + 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 || this.options.crossOrigin === '') { + tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin; + } + + /* + Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons + http://www.w3.org/TR/WCAG20-TECHS/H67 + */ + tile.alt = ''; + + /* + Set role="presentation" to force screen readers to ignore this + https://www.w3.org/TR/wai-aria/roles#textalternativecomputation + */ + tile.setAttribute('role', 'presentation'); + + tile.src = this.getTileUrl(coords); + + return tile; + }, + + // @section Extension methods + // @uninheritable + // Layers extending `TileLayer` might reimplement the following method. + // @method getTileUrl(coords: Object): String + // Called only internally, returns the URL for a tile given its coordinates. + // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. + getTileUrl: function (coords) { + var data = { + r: Browser.retina ? '@2x' : '', + s: this._getSubdomain(coords), + x: coords.x, + y: coords.y, + z: this._getZoomForUrl() + }; + if (this._map && !this._map.options.crs.infinite) { + var invertedY = this._globalTileRange.max.y - coords.y; + if (this.options.tms) { + data['y'] = invertedY; + } + data['-y'] = invertedY; + } + + return Util.template(this._url, Util.extend(data, this.options)); + }, + + _tileOnLoad: function (done, tile) { + // For https://github.com/Leaflet/Leaflet/issues/3332 + if (Browser.ielt9) { + setTimeout(Util.bind(done, this, null, tile), 0); + } else { + done(null, tile); + } + }, + + _tileOnError: function (done, tile, e) { + var errorUrl = this.options.errorTileUrl; + if (errorUrl && tile.getAttribute('src') !== errorUrl) { + tile.src = errorUrl; + } + done(e, tile); + }, + + _onTileRemove: function (e) { + e.tile.onload = null; + }, + + _getZoomForUrl: function () { + var zoom = this._tileZoom, + maxZoom = this.options.maxZoom, + zoomReverse = this.options.zoomReverse, + zoomOffset = this.options.zoomOffset; + + if (zoomReverse) { + zoom = maxZoom - zoom; + } + + return zoom + zoomOffset; + }, + + _getSubdomain: function (tilePoint) { + var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; + return this.options.subdomains[index]; + }, + + // stops loading all tiles in the background layer + _abortLoading: function () { + var i, tile; + for (i in this._tiles) { + if (this._tiles[i].coords.z !== this._tileZoom) { + tile = this._tiles[i].el; + + tile.onload = Util.falseFn; + tile.onerror = Util.falseFn; + + if (!tile.complete) { + tile.src = Util.emptyImageUrl; + DomUtil.remove(tile); + delete this._tiles[i]; + } + } + } + }, + + _removeTile: function (key) { + var tile = this._tiles[key]; + if (!tile) { return; } + + // Cancels any pending http requests associated with the tile + // unless we're on Android's stock browser, + // see https://github.com/Leaflet/Leaflet/issues/137 + if (!Browser.androidStock) { + tile.el.setAttribute('src', Util.emptyImageUrl); + } + + return GridLayer.prototype._removeTile.call(this, key); + }, + + _tileReady: function (coords, err, tile) { + if (!this._map || (tile && tile.getAttribute('src') === Util.emptyImageUrl)) { + return; + } + + return GridLayer.prototype._tileReady.call(this, coords, err, tile); + } +}); + + +// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options) +// Instantiates a tile layer object given a `URL template` and optionally an options object. + +export function tileLayer(url, options) { + return new TileLayer(url, options); +} diff --git a/src/layer/vector/Circle.js b/src/layer/vector/Circle.js index 945484f89cb..386605d2bc5 100644 --- a/src/layer/vector/Circle.js +++ b/src/layer/vector/Circle.js @@ -1,105 +1,100 @@ -/* - * @class Circle - * @aka L.Circle - * @inherits CircleMarker - * - * A class for drawing circle overlays on a map. Extends `CircleMarker`. - * - * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion). - * - * @example - * - * ```js - * L.circle([50.5, 30.5], 200).addTo(map); - * ``` - */ - -L.Circle = L.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}); - } - L.setOptions(this, options); - this._latlng = L.latLng(latlng); - - if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); } - - // @section - // @aka Circle options - // @option radius: Number; Radius of the circle, in meters. - this._mRadius = this.options.radius; - }, - - // @method setRadius(radius: Number): this - // Sets the radius of a circle. Units are in meters. - setRadius: function (radius) { - this._mRadius = radius; - return this.redraw(); - }, - - // @method getRadius(): Number - // Returns the current radius of a circle. Units are in meters. - getRadius: function () { - return this._mRadius; - }, - - // @method getBounds(): LatLngBounds - // Returns the `LatLngBounds` of the path. - getBounds: function () { - var half = [this._radius, this._radiusY || this._radius]; - - return new L.LatLngBounds( - this._map.layerPointToLatLng(this._point.subtract(half)), - this._map.layerPointToLatLng(this._point.add(half))); - }, - - setStyle: L.Path.prototype.setStyle, - - _project: function () { - - var lng = this._latlng.lng, - lat = this._latlng.lat, - map = this._map, - crs = map.options.crs; - - if (crs.distance === L.CRS.Earth.distance) { - var d = Math.PI / 180, - latR = (this._mRadius / L.CRS.Earth.R) / d, - top = map.project([lat + latR, lng]), - bottom = map.project([lat - latR, lng]), - p = top.add(bottom).divideBy(2), - lat2 = map.unproject(p).lat, - lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / - (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; - - if (isNaN(lngR) || lngR === 0) { - lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425 - } - - this._point = p.subtract(map.getPixelOrigin()); - this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1); - this._radiusY = Math.max(Math.round(p.y - top.y), 1); - - } else { - var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); - - this._point = map.latLngToLayerPoint(this._latlng); - this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x; - } - - this._updateBounds(); - } -}); - -// @factory L.circle(latlng: LatLng, options?: Circle options) -// Instantiates a circle object given a geographical point, and an options object -// which contains the circle radius. -// @alternative -// @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); -}; +/* + * L.Circle is a circle overlay (with a certain radius in meters). + */ + +L.Circle = L.Path.extend({ + initialize: function (latlng, radius, options) { + L.Path.prototype.initialize.call(this, options); + + if (isNaN(radius)) { throw new Error('Circle radius cannot be NaN'); } + + this._latlng = L.latLng(latlng); + this._mRadius = radius; + }, + + options: { + fill: true + }, + + setLatLng: function (latlng) { + this._latlng = L.latLng(latlng); + return this.redraw(); + }, + + setRadius: function (radius) { + this._mRadius = radius; + return this.redraw(); + }, + + projectLatlngs: function () { + var lngRadius = this._getLngRadius(), + latlng = this._latlng, + pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]); + + this._point = this._map.latLngToLayerPoint(latlng); + this._radius = Math.max(this._point.x - pointLeft.x, 1); + }, + + getBounds: function () { + var lngRadius = this._getLngRadius(), + latRadius = (this._mRadius / 40075017) * 360, + latlng = this._latlng; + + return new L.LatLngBounds( + [latlng.lat - latRadius, latlng.lng - lngRadius], + [latlng.lat + latRadius, latlng.lng + lngRadius]); + }, + + getLatLng: function () { + return this._latlng; + }, + + getPathString: function () { + var p = this._point, + r = this._radius; + + if (this._checkIfEmpty()) { + return ''; + } + + if (L.Browser.svg) { + return 'M' + p.x + ',' + (p.y - r) + + 'A' + r + ',' + r + ',0,1,1,' + + (p.x - 0.1) + ',' + (p.y - r) + ' z'; + } else { + p._round(); + r = Math.round(r); + return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360); + } + }, + + getRadius: function () { + return this._mRadius; + }, + + // TODO Earth hardcoded, move into projection code! + + _getLatRadius: function () { + return (this._mRadius / 40075017) * 360; + }, + + _getLngRadius: function () { + return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat); + }, + + _checkIfEmpty: function () { + if (!this._map) { + return false; + } + var vp = this._map._pathViewport, + r = this._radius, + p = this._point; + + return p.x - r > vp.max.x || p.y - r > vp.max.y || + p.x + r < vp.min.x || p.y + r < vp.min.y; + } +}); + +L.circle = function (latlng, radius, options) { + return new L.Circle(latlng, radius, options); +}; diff --git a/src/layer/vector/Path.SVG.js b/src/layer/vector/Path.SVG.js new file mode 100644 index 00000000000..9734e660b34 --- /dev/null +++ b/src/layer/vector/Path.SVG.js @@ -0,0 +1,230 @@ +/* + * Extends L.Path with SVG-specific rendering code. + */ + +L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; + +L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); + +L.Path = L.Path.extend({ + statics: { + SVG: L.Browser.svg + }, + + bringToFront: function () { + var root = this._map._pathRoot, + path = this._container; + + if (path && root.lastChild !== path) { + root.appendChild(path); + } + return this; + }, + + bringToBack: function () { + var root = this._map._pathRoot, + path = this._container, + first = root.firstChild; + + if (path && first !== path) { + root.insertBefore(path, first); + } + return this; + }, + + getPathString: function () { + // form path string here + }, + + _createElement: function (name) { + return document.createElementNS(L.Path.SVG_NS, name); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._initPath(); + this._initStyle(); + }, + + _initPath: function () { + this._container = this._createElement('g'); + + this._path = this._createElement('path'); + + if (this.options.className) { + L.DomUtil.addClass(this._path, this.options.className); + } + + this._container.appendChild(this._path); + }, + + _initStyle: function () { + if (this.options.stroke) { + this._path.setAttribute('stroke-linejoin', 'round'); + this._path.setAttribute('stroke-linecap', 'round'); + } + if (this.options.fill) { + this._path.setAttribute('fill-rule', 'evenodd'); + } + if (this.options.pointerEvents) { + this._path.setAttribute('pointer-events', this.options.pointerEvents); + } + if (!this.options.clickable && !this.options.pointerEvents) { + this._path.setAttribute('pointer-events', 'none'); + } + this._updateStyle(); + }, + + _updateStyle: function () { + if (this.options.stroke) { + this._path.setAttribute('stroke', this.options.color); + this._path.setAttribute('stroke-opacity', this.options.opacity); + this._path.setAttribute('stroke-width', this.options.weight); + if (this.options.dashArray) { + this._path.setAttribute('stroke-dasharray', this.options.dashArray); + } else { + this._path.removeAttribute('stroke-dasharray'); + } + if (this.options.lineCap) { + this._path.setAttribute('stroke-linecap', this.options.lineCap); + } + if (this.options.lineJoin) { + this._path.setAttribute('stroke-linejoin', this.options.lineJoin); + } + } else { + this._path.setAttribute('stroke', 'none'); + } + if (this.options.fill) { + this._path.setAttribute('fill', this.options.fillColor || this.options.color); + this._path.setAttribute('fill-opacity', this.options.fillOpacity); + } else { + this._path.setAttribute('fill', 'none'); + } + }, + + _updatePath: function () { + var str = this.getPathString(); + if (!str) { + // fix webkit empty string parsing bug + str = 'M0 0'; + } + this._path.setAttribute('d', str); + }, + + // TODO remove duplication with L.Map + _initEvents: function () { + if (this.options.clickable) { + if (L.Browser.svg || !L.Browser.vml) { + L.DomUtil.addClass(this._path, 'leaflet-clickable'); + } + + L.DomEvent.on(this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseover', + 'mouseout', 'mousemove', 'contextmenu']; + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this); + } + } + }, + + _onMouseClick: function (e) { + if (!e._simulated && this._map.dragging && this._map.dragging.moved()) { return; } + + this._fireMouseEvent(e); + }, + + _fireMouseEvent: function (e) { + if (!this._map || !this.hasEventListeners(e.type)) { return; } + + var map = this._map, + containerPoint = map.mouseEventToContainerPoint(e), + layerPoint = map.containerPointToLayerPoint(containerPoint), + latlng = map.layerPointToLatLng(layerPoint); + + this.fire(e.type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); + + if (e.type === 'contextmenu') { + L.DomEvent.preventDefault(e); + } + if (e.type !== 'mousemove') { + L.DomEvent.stopPropagation(e); + } + } +}); + +L.Map.include({ + _initPathRoot: function () { + if (!this._pathRoot) { + this._pathRoot = L.Path.prototype._createElement('svg'); + this._panes.overlayPane.appendChild(this._pathRoot); + + if (this.options.zoomAnimation && L.Browser.any3d) { + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated'); + + this.on({ + 'zoomanim': this._animatePathZoom, + 'zoomend': this._endPathZoom + }); + } else { + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide'); + } + + this.on('moveend', this._updateSvgViewport); + this._updateSvgViewport(); + } + }, + + _animatePathZoom: function (e) { + var scale = this.getZoomScale(e.zoom), + offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); + + this._pathRoot.style[L.DomUtil.TRANSFORM] = + L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; + + this._pathZooming = true; + }, + + _endPathZoom: function () { + this._pathZooming = false; + }, + + _updateSvgViewport: function () { + + if (this._pathZooming) { + // Do not update SVGs while a zoom animation is going on otherwise the animation will break. + // When the zoom animation ends we will be updated again anyway + // This fixes the case where you do a momentum move and zoom while the move is still ongoing. + return; + } + + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + max = vp.max, + width = max.x - min.x, + height = max.y - min.y, + root = this._pathRoot, + pane = this._panes.overlayPane; + + // Hack to make flicker on drag end on mobile webkit less irritating + if (L.Browser.mobileWebkit) { + pane.removeChild(root); + } + + L.DomUtil.setPosition(root, min); + root.setAttribute('width', width); + root.setAttribute('height', height); + root.setAttribute('viewBox', [min.x, min.y, width, height].join(' ')); + + if (L.Browser.mobileWebkit) { + pane.appendChild(root); + } + } +}); diff --git a/src/layer/vector/canvas/Path.Canvas.js b/src/layer/vector/canvas/Path.Canvas.js new file mode 100644 index 00000000000..83a625e267b --- /dev/null +++ b/src/layer/vector/canvas/Path.Canvas.js @@ -0,0 +1,214 @@ +/* + * Vector rendering for all browsers that support canvas. + */ + +L.Browser.canvas = (function () { + return !!document.createElement('canvas').getContext; +}()); + +L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({ + statics: { + //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value + CANVAS: true, + SVG: false + }, + + redraw: function () { + if (this._map) { + this.projectLatlngs(); + this._requestUpdate(); + } + return this; + }, + + setStyle: function (style) { + L.setOptions(this, style); + this._addLineDash(); + + if (this._map) { + this._updateStyle(); + this._requestUpdate(); + } + return this; + }, + + onRemove: function (map) { + map + .off('viewreset', this.projectLatlngs, this) + .off('moveend', this._updatePath, this); + + if (this.options.clickable && this._onClick !== undefined) { + this._map.off('click', this._onClick, this); + this._map.off('mousemove', this._onMouseMove, this); + } + + this._requestUpdate(); + + this.fire('remove'); + this._map = null; + }, + + _requestUpdate: function () { + if (this._map && !L.Path._updateRequest) { + L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map); + } + }, + + _fireMapMoveEnd: function () { + L.Path._updateRequest = null; + this.fire('moveend'); + }, + + _initElements: function () { + this._map._initPathRoot(); + this._ctx = this._map._canvasCtx; + }, + + _addLineDash: function() { + if (this.options.dashArray) { + this.options._dashArray = this.options.dashArray.split(',').map(Number); + } + }, + + _updateStyle: function () { + var options = this.options; + + if (options.stroke) { + this._ctx.lineWidth = options.weight; + this._ctx.strokeStyle = options.color; + } + if (options.fill) { + this._ctx.fillStyle = options.fillColor || options.color; + } + + if (options.lineCap) { + this._ctx.lineCap = options.lineCap; + } + if (options.lineJoin) { + this._ctx.lineJoin = options.lineJoin; + } + this._addLineDash(); + }, + + _drawPath: function () { + var i, j, len, len2, point, drawMethod; + + this._ctx.beginPath(); + + this._ctx.setLineDash(this.options && this.options._dashArray || []); + + for (i = 0, len = this._parts.length; i < len; i++) { + for (j = 0, len2 = this._parts[i].length; j < len2; j++) { + point = this._parts[i][j]; + drawMethod = (j === 0 ? 'move' : 'line') + 'To'; + + this._ctx[drawMethod](point.x, point.y); + } + // TODO refactor ugly hack + if (this instanceof L.Polygon) { + this._ctx.closePath(); + } + } + }, + + _checkIfEmpty: function () { + return !this._parts.length; + }, + + _updatePath: function () { + if (this._checkIfEmpty()) { return; } + + var ctx = this._ctx, + options = this.options; + + this._drawPath(); + ctx.save(); + this._updateStyle(); + + if (options.fill) { + ctx.globalAlpha = options.fillOpacity; + ctx.fill(options.fillRule || 'evenodd'); + } + + if (options.stroke) { + ctx.globalAlpha = options.opacity; + ctx.stroke(); + } + + ctx.restore(); + + // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature + }, + + _initEvents: function () { + if (this.options.clickable) { + this._map.on('mousemove', this._onMouseMove, this); + this._map.on('click dblclick contextmenu', this._fireMouseEvent, this); + } + }, + + _fireMouseEvent: function (e) { + if (this._containsPoint(e.layerPoint)) { + this.fire(e.type, e); + } + }, + + _onMouseMove: function (e) { + if (!this._map || this._map._animatingZoom) { return; } + + // TODO don't do on each move + if (this._containsPoint(e.layerPoint)) { + this._ctx.canvas.style.cursor = 'pointer'; + this._mouseInside = true; + this.fire('mouseover', e); + + } else if (this._mouseInside) { + this._ctx.canvas.style.cursor = ''; + this._mouseInside = false; + this.fire('mouseout', e); + } + } +}); + +L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : { + _initPathRoot: function () { + var root = this._pathRoot, + ctx; + + if (!root) { + root = this._pathRoot = document.createElement('canvas'); + root.style.position = 'absolute'; + ctx = this._canvasCtx = root.getContext('2d'); + + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + this._panes.overlayPane.appendChild(root); + + if (this.options.zoomAnimation) { + this._pathRoot.className = 'leaflet-zoom-animated'; + this.on('zoomanim', this._animatePathZoom); + this.on('zoomend', this._endPathZoom); + } + this.on('moveend', this._updateCanvasViewport); + this._updateCanvasViewport(); + } + }, + + _updateCanvasViewport: function () { + // don't redraw while zooming. See _updateSvgViewport for more details + if (this._pathZooming) { return; } + this._updatePathViewport(); + + var vp = this._pathViewport, + min = vp.min, + size = vp.max.subtract(min), + root = this._pathRoot; + + //TODO check if this works properly on mobile webkit + L.DomUtil.setPosition(root, min); + root.width = size.x; + root.height = size.y; + root.getContext('2d').translate(-min.x, -min.y); + } +}); diff --git a/src/map/Map.js b/src/map/Map.js index 8ce12d61511..18db3478a78 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -1,106 +1,29 @@ /* - * @class Map - * @aka L.Map - * @inherits Evented - * - * The central class of the API — it is used to create a map on a page and manipulate it. - * - * @example - * - * ```js - * // initialize the map on the "map" div with a given center and zoom - * var map = L.map('map', { - * center: [51.505, -0.09], - * zoom: 13 - * }); - * ``` - * + * L.Map is the central class of the API - it is used to create a map. */ -L.Map = L.Evented.extend({ +L.Map = L.Class.extend({ + + includes: L.Mixin.Events, 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, - // @option center: LatLng = undefined - // Initial geographic center of the map - center: undefined, - - // @option zoom: Number = undefined - // Initial map zoom level - zoom: undefined, - - // @option minZoom: Number = undefined - // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers. - minZoom: undefined, - - // @option maxZoom: Number = undefined - // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers. - maxZoom: undefined, - - // @option layers: Layer[] = [] - // Array of layers that will be added to the map initially - layers: [], - - // @option maxBounds: LatLngBounds = null - // When this option is set, the map restricts the view to the given - // geographical bounds, bouncing the user back when he tries to pan - // outside the view. To set the restriction dynamically, use - // [`setMaxBounds`](#map-setmaxbounds) method. - maxBounds: undefined, - - // @option renderer: Renderer = * - // The default method for drawing vector layers on the map. `L.SVG` - // or `L.Canvas` by default depending on browser support. - renderer: undefined, - - - // @section Animation Options - // @option fadeAnimation: Boolean = true - // Whether the tile fade animation is enabled. By default it's enabled - // in all browsers that support CSS3 Transitions except Android. - fadeAnimation: true, - - // @option markerZoomAnimation: Boolean = true - // Whether markers animate their zoom with the zoom animation, if disabled - // they will disappear for the length of the animation. By default it's - // enabled in all browsers that support CSS3 Transitions except Android. - markerZoomAnimation: true, - - // @option transform3DLimit: Number = 2^23 - // Defines the maximum size of a CSS translation transform. The default - // value should not be changed unless a web browser positions layers in - // the wrong place after doing a large `panBy`. - transform3DLimit: 8388608, // Precision limit of a 32-bit float - - // @section Interaction Options - // @option zoomSnap: Number = 1 - // Forces the map's zoom level to always be a multiple of this, particularly - // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. - // By default, the zoom level snaps to the nearest integer; lower values - // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` - // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. - zoomSnap: 1, - - // @option zoomDelta: Number = 1 - // Controls how much the map's zoom level will change after a - // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` - // or `-` on the keyboard, or using the [zoom controls](#control-zoom). - // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. - zoomDelta: 1, - - // @option trackResize: Boolean = true - // Whether the map automatically handles browser window resize to update itself. - trackResize: true + /* + center: LatLng, + zoom: Number, + layers: Array, + */ + + fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23, + trackResize: true, + markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d }, initialize: function (id, options) { // (HTMLElement or String, Object) options = L.setOptions(this, options); + this._initContainer(id); this._initLayout(); @@ -113,67 +36,47 @@ L.Map = L.Evented.extend({ this.setMaxBounds(options.maxBounds); } - if (options.zoom !== undefined) { - this._zoom = this._limitZoom(options.zoom); - } - if (options.center && options.zoom !== undefined) { this.setView(L.latLng(options.center), options.zoom, {reset: true}); } this._handlers = []; + this._layers = {}; this._zoomBoundLayers = {}; - this._sizeChanged = true; + this._tileLayersNum = 0; this.callInitHooks(); - this._addLayers(this.options.layers); + this._addLayers(options.layers); }, - // @section Methods for modifying map state + // public methods that modify map state - // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this - // Sets the view of the map (geographical center and zoom) with the given - // animation options. + // replaced by animation-powered implementation in Map.PanAnimation.js setView: function (center, zoom) { - // replaced by animation-powered implementation in Map.PanAnimation.js zoom = zoom === undefined ? this.getZoom() : zoom; - this._resetView(L.latLng(center), zoom); + this._resetView(L.latLng(center), this._limitZoom(zoom)); return this; }, - // @method setZoom(zoom: Number, options: Zoom/pan options): this - // Sets the zoom of the map. setZoom: function (zoom, options) { if (!this._loaded) { - this._zoom = zoom; + this._zoom = this._limitZoom(zoom); return this; } return this.setView(this.getCenter(), zoom, {zoom: options}); }, - // @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); - return this.setZoom(this._zoom + delta, options); + return this.setZoom(this._zoom + (delta || 1), 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); - return this.setZoom(this._zoom - delta, options); + return this.setZoom(this._zoom - (delta || 1), options); }, - // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this - // Zooms the map while keeping a specified geographical point on the map - // stationary (e.g. used internally for scroll zoom and double-click zoom). - // @alternative - // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this - // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. setZoomAround: function (latlng, zoom, options) { var scale = this.getZoomScale(zoom), viewHalf = this.getSize().divideBy(2), @@ -185,7 +88,7 @@ L.Map = L.Evented.extend({ return this.setView(newCenter, zoom, {zoom: options}); }, - _getBoundsCenterZoom: function (bounds, options) { + fitBounds: function (bounds, options) { options = options || {}; bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds); @@ -203,42 +106,17 @@ L.Map = L.Evented.extend({ nePoint = this.project(bounds.getNorthEast(), zoom), center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); - return { - center: center, - zoom: zoom - }; - }, - - // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this - // Sets a map view that contains the given geographical bounds with the - // maximum zoom level possible. - fitBounds: function (bounds, options) { - - bounds = L.latLngBounds(bounds); - - if (!bounds.isValid()) { - throw new Error('Bounds are not valid.'); - } - - var target = this._getBoundsCenterZoom(bounds, options); - return this.setView(target.center, target.zoom, options); + return this.setView(center, zoom, options); }, - // @method fitWorld(options?: fitBounds options): this - // Sets a map view that mostly contains the whole world with the maximum - // zoom level possible. fitWorld: function (options) { return this.fitBounds([[-90, -180], [90, 180]], options); }, - // @method panTo(latlng: LatLng, options?: Pan options): this - // Pans the map to a given center. panTo: function (center, options) { // (LatLng) return this.setView(center, this._zoom, {pan: options}); }, - // @method panBy(offset: Point): this - // Pans the map by a given number of pixels (animated). panBy: function (offset) { // (Point) // replaced with animated panBy in Map.PanAnimation.js this.fire('movestart'); @@ -249,78 +127,103 @@ L.Map = L.Evented.extend({ return this.fire('moveend'); }, - // @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); + this.options.maxBounds = bounds; + if (!bounds) { - return this.off('moveend', this._panInsideMaxBounds); - } else if (this.options.maxBounds) { - this.off('moveend', this._panInsideMaxBounds); + return this.off('moveend', this._panInsideMaxBounds, this); } - this.options.maxBounds = bounds; - if (this._loaded) { this._panInsideMaxBounds(); } - return this.on('moveend', this._panInsideMaxBounds); + return this.on('moveend', this._panInsideMaxBounds, this); + }, + + panInsideBounds: function (bounds, options) { + var center = this.getCenter(), + newCenter = this._limitCenter(center, this._zoom, bounds); + + if (center.equals(newCenter)) { return this; } + + return this.panTo(newCenter, options); }, - // @method setMinZoom(zoom: Number): this - // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). - setMinZoom: function (zoom) { - this.options.minZoom = zoom; + addLayer: function (layer) { + // TODO method is too big, refactor + + var id = L.stamp(layer); + + if (this._layers[id]) { return this; } + + this._layers[id] = layer; + + // TODO getMaxZoom, getMinZoom in ILayer (instead of options) + if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { + this._zoomBoundLayers[id] = layer; + this._updateZoomLevels(); + } + + // TODO looks ugly, refactor!!! + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum++; + this._tileLayersToLoad++; + layer.on('load', this._onTileLayerLoad, this); + } - if (this._loaded && this.getZoom() < this.options.minZoom) { - return this.setZoom(zoom); + if (this._loaded) { + this._layerAdd(layer); } return this; }, - // @method setMaxZoom(zoom: Number): this - // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). - setMaxZoom: function (zoom) { - this.options.maxZoom = zoom; + removeLayer: function (layer) { + var id = L.stamp(layer); + + if (!this._layers[id]) { return this; } - if (this._loaded && (this.getZoom() > this.options.maxZoom)) { - return this.setZoom(zoom); + if (this._loaded) { + layer.onRemove(this); } - return this; - }, + delete this._layers[id]; - // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this - // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. - panInsideBounds: function (bounds, options) { - this._enforcingBounds = true; - var center = this.getCenter(), - newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds)); + if (this._loaded) { + this.fire('layerremove', {layer: layer}); + } + + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } - if (!center.equals(newCenter)) { - this.panTo(newCenter, options); + // TODO looks ugly, refactor + if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { + this._tileLayersNum--; + this._tileLayersToLoad--; + layer.off('load', this._onTileLayerLoad, this); } - this._enforcingBounds = false; return this; }, - // @method invalidateSize(options: Zoom/Pan options): this - // Checks if the map container size changed and updates the map if so — - // call it after you've changed the map size dynamically, also animating - // pan by default. If `options.pan` is `false`, panning will not occur. - // If `options.debounceMoveend` is `true`, it will delay `moveend` event so - // that it doesn't happen often even if the method is called many - // times in a row. + hasLayer: function (layer) { + if (!layer) { return false; } + + return (L.stamp(layer) in this._layers); + }, + + eachLayer: function (method, context) { + for (var i in this._layers) { + method.call(context, this._layers[i]); + } + return this; + }, - // @alternative - // @method invalidateSize(animate: Boolean): this - // Checks if the map container size changed and updates the map if so — - // call it after you've changed the map size dynamically, also animating - // pan by default. invalidateSize: function (options) { if (!this._loaded) { return this; } @@ -331,7 +234,7 @@ L.Map = L.Evented.extend({ var oldSize = this.getSize(); this._sizeChanged = true; - this._lastCenter = null; + this._initialCenter = null; var newSize = this.getSize(), oldCenter = oldSize.divideBy(2).round(), @@ -358,32 +261,13 @@ L.Map = L.Evented.extend({ } } - // @section Map state change events - // @event resize: ResizeEvent - // Fired when the map is resized. return this.fire('resize', { oldSize: oldSize, newSize: newSize }); }, - // @section Methods for modifying map state - // @method stop(): this - // Stops the currently running `panTo` or `flyTo` animation, if any. - stop: function () { - this.setZoom(this._limitZoom(this._zoom)); - if (!this.options.zoomSnap) { - this.fire('viewreset'); - } - return this._stop(); - }, - - // TODO handler.addTo - // TODO Appropiate docs section? - // @section Other Methods - // @method addHandler(name: String, HandlerClass: Function): this - // Adds a new `Handler` to the map, given its name and constructor function. addHandler: function (name, HandlerClass) { if (!HandlerClass) { return this; } @@ -398,11 +282,12 @@ L.Map = L.Evented.extend({ return this; }, - // @method remove(): this - // Destroys the map and clears all related event listeners. remove: function () { + if (this._loaded) { + this.fire('unload'); + } - this._initEvents(true); + this._initEvents('off'); try { // throws error in IE6-8 @@ -411,64 +296,32 @@ L.Map = L.Evented.extend({ this._container._leaflet = undefined; } - L.DomUtil.remove(this._mapPane); - + this._clearPanes(); if (this._clearControlPos) { this._clearControlPos(); } this._clearHandlers(); - if (this._loaded) { - // @section Map state change events - // @event unload: Event - // Fired when the map is destroyed with [remove](#map-remove) method. - this.fire('unload'); - } - - for (var i in this._layers) { - this._layers[i].remove(); - } - return this; }, - // @section Other Methods - // @method createPane(name: String, container?: HTMLElement): HTMLElement - // Creates a new map pane with the given name if it doesn't exist already, - // then returns it. The pane is created as a children of `container`, or - // 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); - if (name) { - this._panes[name] = pane; - } - return pane; - }, - - // @section Methods for Getting Map State + // public methods for getting map state - // @method getCenter(): LatLng - // Returns the geographical center of the map view - getCenter: function () { + getCenter: function () { // (Boolean) -> LatLng this._checkIfLoaded(); - if (this._lastCenter && !this._moved()) { - return this._lastCenter; + if (this._initialCenter && !this._moved()) { + return this._initialCenter; } return this.layerPointToLatLng(this._getCenterLayerPoint()); }, - // @method getZoom(): Number - // Returns the current zoom level of the map view getZoom: function () { return this._zoom; }, - // @method getBounds(): LatLngBounds - // Returns the geographical bounds visible in the current map view getBounds: function () { var bounds = this.getPixelBounds(), sw = this.unproject(bounds.getBottomLeft()), @@ -477,51 +330,47 @@ L.Map = L.Evented.extend({ return new L.LatLngBounds(sw, ne); }, - // @method getMinZoom(): Number - // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. getMinZoom: function () { - return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom; + return this.options.minZoom === undefined ? + (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) : + this.options.minZoom; }, - // @method getMaxZoom(): Number - // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). getMaxZoom: function () { return this.options.maxZoom === undefined ? (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) : this.options.maxZoom; }, - // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number - // Returns the maximum zoom level on which the given bounds fit to the map - // view in its entirety. If `inside` (optional) is set to `true`, the method - // 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]); - var zoom = this.getZoom() || 0, - min = this.getMinZoom(), - max = this.getMaxZoom(), + var zoom = this.getMinZoom() - (inside ? 1 : 0), + maxZoom = this.getMaxZoom(), + size = this.getSize(), + nw = bounds.getNorthWest(), se = bounds.getSouthEast(), - size = this.getSize(), - boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding), - snap = L.Browser.any3d ? this.options.zoomSnap : 1; - var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y); - zoom = this.getScaleZoom(scale, zoom); + zoomNotFound = true, + boundsSize; + + padding = L.point(padding || [0, 0]); + + do { + zoom++; + boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding); + zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y; + + } while (zoomNotFound && zoom <= maxZoom); - if (snap) { - zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level - zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap; + if (zoomNotFound && inside) { + return null; } - return Math.max(min, Math.min(max, zoom)); + return inside ? zoom : zoom - 1; }, - // @method getSize(): Point - // Returns the current size of the map container (in pixels). getSize: function () { if (!this._size || this._sizeChanged) { this._size = new L.Point( @@ -533,172 +382,84 @@ L.Map = L.Evented.extend({ return this._size.clone(); }, - // @method getPixelBounds(): Bounds - // Returns the bounds of the current map view in projected pixel - // coordinates (sometimes useful in layer and overlay implementations). - getPixelBounds: function (center, zoom) { - var topLeftPoint = this._getTopLeftPoint(center, zoom); + getPixelBounds: function () { + var topLeftPoint = this._getTopLeftPoint(); return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); }, - // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to - // the map pane? "left point of the map layer" can be confusing, specially - // since there can be negative offsets. - // @method getPixelOrigin(): Point - // Returns the projected pixel coordinates of the top left point of - // the map layer (useful in custom layer and overlay implementations). getPixelOrigin: function () { this._checkIfLoaded(); - return this._pixelOrigin; - }, - - // @method getPixelWorldBounds(zoom?: Number): Bounds - // Returns the world's bounds in pixel coordinates for zoom level `zoom`. - // If `zoom` is omitted, the map's current zoom level is used. - getPixelWorldBounds: function (zoom) { - return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom); - }, - - // @section Other Methods - - // @method getPane(pane: String|HTMLElement): HTMLElement - // Returns a map pane, given its name or its HTML element (its identity). - getPane: function (pane) { - return typeof pane === 'string' ? this._panes[pane] : pane; + return this._initialTopLeftPoint; }, - // @method getPanes(): Object - // Returns a plain object containing the names of all panes as keys and - // the panes as values. getPanes: function () { return this._panes; }, - // @method getContainer: HTMLElement - // Returns the HTML element that contains the map. getContainer: function () { return this._container; }, - // @section Conversion Methods + // TODO replace with universal implementation after refactoring projections - // @method getZoomScale(toZoom: Number, fromZoom: Number): Number - // Returns the scale factor to be applied to a map transition from zoom level - // `fromZoom` to `toZoom`. Used internally to help with zoom animations. - getZoomScale: function (toZoom, fromZoom) { - // TODO replace with universal implementation after refactoring projections + getZoomScale: function (toZoom) { var crs = this.options.crs; - fromZoom = fromZoom === undefined ? this._zoom : fromZoom; - return crs.scale(toZoom) / crs.scale(fromZoom); + return crs.scale(toZoom) / crs.scale(this._zoom); }, - // @method getScaleZoom(scale: Number, fromZoom: Number): Number - // Returns the zoom level that the map would end up at, if it is at `fromZoom` - // level and everything is scaled by a factor of `scale`. Inverse of - // [`getZoomScale`](#map-getZoomScale). - getScaleZoom: function (scale, fromZoom) { - var crs = this.options.crs; - fromZoom = fromZoom === undefined ? this._zoom : fromZoom; - return crs.zoom(scale * crs.scale(fromZoom)); + getScaleZoom: function (scale) { + return this._zoom + (Math.log(scale) / Math.LN2); }, - // @method project(latlng: LatLng, zoom: Number): Point - // Projects a geographical coordinate `LatLng` according to the projection - // of the map's CRS, then scales it according to `zoom` and the CRS's - // `Transformation`. The result is pixel coordinate relative to - // the CRS origin. - project: function (latlng, zoom) { + + // conversion methods + + project: function (latlng, zoom) { // (LatLng[, Number]) -> Point zoom = zoom === undefined ? this._zoom : zoom; return this.options.crs.latLngToPoint(L.latLng(latlng), zoom); }, - // @method unproject(point: Point, zoom: Number): LatLng - // Inverse of [`project`](#map-project). - unproject: function (point, zoom) { + unproject: function (point, zoom) { // (Point[, Number]) -> LatLng zoom = zoom === undefined ? this._zoom : zoom; return this.options.crs.pointToLatLng(L.point(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) { + layerPointToLatLng: function (point) { // (Point) var projectedPoint = L.point(point).add(this.getPixelOrigin()); return this.unproject(projectedPoint); }, - // @method latLngToLayerPoint(latlng: LatLng): Point - // Given a geographical coordinate, returns the corresponding pixel coordinate - // relative to the [origin pixel](#map-getpixelorigin). - latLngToLayerPoint: function (latlng) { + latLngToLayerPoint: function (latlng) { // (LatLng) var projectedPoint = this.project(L.latLng(latlng))._round(); return projectedPoint._subtract(this.getPixelOrigin()); }, - // @method wrapLatLng(latlng: LatLng): LatLng - // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the - // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the - // CRS's bounds. - // 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)); - }, - - // @method distance(latlng1: LatLng, latlng2: LatLng): Number - // 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)); - }, - - // @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()); }, - // @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()); }, - // @method containerPointToLatLng(point: Point): Point - // 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)); return this.layerPointToLatLng(layerPoint); }, - // @method latLngToContainerPoint(latlng: LatLng): Point - // 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))); }, - // @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) { + mouseEventToContainerPoint: function (e) { // (MouseEvent) return L.DomEvent.getMousePosition(e, this._container); }, - // @method mouseEventToLayerPoint(ev: MouseEvent): Point - // Given a MouseEvent object, returns the pixel coordinate relative to - // the [origin pixel](#map-getpixelorigin) where the event took place. - mouseEventToLayerPoint: function (e) { + mouseEventToLayerPoint: function (e) { // (MouseEvent) return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); }, - // @method mouseEventToLatLng(ev: MouseEvent): LatLng - // Given a MouseEvent object, returns geographical coordinate where the - // event took place. mouseEventToLatLng: function (e) { // (MouseEvent) return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); }, @@ -715,21 +476,17 @@ L.Map = L.Evented.extend({ throw new Error('Map container is already initialized.'); } - L.DomEvent.addListener(container, 'scroll', this._onScroll, this); container._leaflet = true; }, _initLayout: function () { var container = this._container; - this._fadeAnimated = this.options.fadeAnimation && L.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' : '') + - (this._fadeAnimated ? ' leaflet-fade-anim' : '')); + (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); var position = L.DomUtil.getStyle(container, 'position'); @@ -746,130 +503,86 @@ L.Map = L.Evented.extend({ _initPanes: function () { var panes = this._panes = {}; - this._paneRenderers = {}; - - // @section - // - // Panes are DOM elements used to control the ordering of layers on the map. You - // can access panes with [`map.getPane`](#map-getpane) or - // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the - // [`map.createPane`](#map-createpane) method. - // - // Every map has the following default panes that differ only in zIndex. - // - // @pane mapPane: HTMLElement = 'auto' - // Pane that contains all other map panes - - this._mapPane = this.createPane('mapPane', this._container); - L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); - - // @pane tilePane: HTMLElement = 2 - // Pane for tile layers - this.createPane('tilePane'); - // @pane overlayPane: HTMLElement = 4 - // Pane for overlays like polylines and polygons - this.createPane('shadowPane'); - // @pane shadowPane: HTMLElement = 5 - // Pane for overlay shadows (e.g. marker shadows) - this.createPane('overlayPane'); - // @pane markerPane: HTMLElement = 6 - // Pane for marker icons - this.createPane('markerPane'); - // @pane popupPane: HTMLElement = 7 - // Pane for popups. - this.createPane('popupPane'); + + this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container); + + this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane); + panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane); + panes.shadowPane = this._createPane('leaflet-shadow-pane'); + panes.overlayPane = this._createPane('leaflet-overlay-pane'); + panes.markerPane = this._createPane('leaflet-marker-pane'); + panes.popupPane = this._createPane('leaflet-popup-pane'); + + var zoomHide = ' leaflet-zoom-hide'; if (!this.options.markerZoomAnimation) { - L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide'); - L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide'); + L.DomUtil.addClass(panes.markerPane, zoomHide); + L.DomUtil.addClass(panes.shadowPane, zoomHide); + L.DomUtil.addClass(panes.popupPane, zoomHide); } }, + _createPane: function (className, container) { + return L.DomUtil.create('div', className, container || this._panes.objectsPane); + }, - // private methods that modify map state + _clearPanes: function () { + this._container.removeChild(this._mapPane); + }, - // @section Map state change events - _resetView: function (center, zoom) { - L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + _addLayers: function (layers) { + layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; - var loading = !this._loaded; - this._loaded = true; - zoom = this._limitZoom(zoom); + for (var i = 0, len = layers.length; i < len; i++) { + this.addLayer(layers[i]); + } + }, - this.fire('viewprereset'); - var zoomChanged = this._zoom !== zoom; - this - ._moveStart(zoomChanged) - ._move(center, zoom) - ._moveEnd(zoomChanged); + // private methods that modify map state - // @event viewreset: Event - // Fired when the map needs to redraw its content (this usually happens - // on map zoom or load). Very useful for creating custom overlays. - this.fire('viewreset'); + _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) { - // @event load: Event - // Fired when the map is initialized (when its center and zoom are set - // for the first time). - if (loading) { - this.fire('load'); - } - }, + var zoomChanged = (this._zoom !== zoom); - _moveStart: function (zoomChanged) { - // @event zoomstart: Event - // Fired when the map zoom is about to change (e.g. before zoom animation). - // @event movestart: Event - // Fired when the view of the map starts changing (e.g. user starts dragging the map). - if (zoomChanged) { - this.fire('zoomstart'); - } - return this.fire('movestart'); - }, + if (!afterZoomAnim) { + this.fire('movestart'); - _move: function (center, zoom, data) { - if (zoom === undefined) { - zoom = this._zoom; + if (zoomChanged) { + this.fire('zoomstart'); + } } - var zoomChanged = this._zoom !== zoom; this._zoom = zoom; - this._lastCenter = center; - this._pixelOrigin = this._getNewPixelOrigin(center); - - // @event zoom: Event - // Fired repeteadly during any change in zoom level, including zoom - // and fly animations. - if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530 - this.fire('zoom', data); + this._initialCenter = center; + + this._initialTopLeftPoint = this._getNewTopLeftPoint(center); + + if (!preserveMapOffset) { + L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0)); + } else { + this._initialTopLeftPoint._add(this._getMapPanePos()); } - // @event move: Event - // Fired repeteadly during any movement of the map, including pan and - // fly animations. - return this.fire('move', data); - }, + this._tileLayersToLoad = this._tileLayersNum; - _moveEnd: function (zoomChanged) { - // @event zoomend: Event - // Fired when the map has changed, after any animations. - if (zoomChanged) { - this.fire('zoomend'); + var loading = !this._loaded; + this._loaded = true; + + this.fire('viewreset', {hard: !preserveMapOffset}); + + if (loading) { + this.fire('load'); + this.eachLayer(this._layerAdd, this); } - // @event moveend: Event - // Fired when the center of the map stops changing (e.g. user stopped - // dragging the map). - return this.fire('moveend'); - }, + this.fire('move'); - _stop: function () { - L.Util.cancelAnimFrame(this._flyToFrame); - if (this._panAnim) { - this._panAnim.stop(); + if (zoomChanged || afterZoomAnim) { + this.fire('zoomend'); } - return this; + + this.fire('moveend', {hard: !preserveMapOffset}); }, _rawPanBy: function (offset) { @@ -880,10 +593,36 @@ L.Map = L.Evented.extend({ return this.getMaxZoom() - this.getMinZoom(); }, - _panInsideMaxBounds: function () { - if (!this._enforcingBounds) { - this.panInsideBounds(this.options.maxBounds); + _updateZoomLevels: function () { + var i, + minZoom = Infinity, + maxZoom = -Infinity, + oldZoomSpan = this._getZoomSpan(); + + for (i in this._zoomBoundLayers) { + var layer = this._zoomBoundLayers[i]; + if (!isNaN(layer.options.minZoom)) { + minZoom = Math.min(minZoom, layer.options.minZoom); + } + if (!isNaN(layer.options.maxZoom)) { + maxZoom = Math.max(maxZoom, layer.options.maxZoom); + } } + + if (i === undefined) { // we have no tilelayers + this._layersMaxZoom = this._layersMinZoom = undefined; + } else { + this._layersMaxZoom = maxZoom; + this._layersMinZoom = minZoom; + } + + if (oldZoomSpan !== this._getZoomSpan()) { + this.fire('zoomlevelschange'); + } + }, + + _panInsideMaxBounds: function () { + this.panInsideBounds(this.options.maxBounds); }, _checkIfLoaded: function () { @@ -892,158 +631,74 @@ L.Map = L.Evented.extend({ } }, - // DOM event handling + // map events - // @section Interaction events - _initEvents: function (remove) { + _initEvents: function (onOff) { if (!L.DomEvent) { return; } - this._targets = {}; - this._targets[L.stamp(this._container)] = this; - - var onOff = remove ? 'off' : 'on'; - - // @event click: MouseEvent - // Fired when the user clicks (or taps) the map. - // @event dblclick: MouseEvent - // Fired when the user double-clicks (or double-taps) the map. - // @event mousedown: MouseEvent - // Fired when the user pushes the mouse button on the map. - // @event mouseup: MouseEvent - // Fired when the user releases the mouse button on the map. - // @event mouseover: MouseEvent - // Fired when the mouse enters the map. - // @event mouseout: MouseEvent - // Fired when the mouse leaves the map. - // @event mousemove: MouseEvent - // Fired while the mouse moves over the map. - // @event contextmenu: MouseEvent - // Fired when the user pushes the right mouse button on the map, prevents - // default browser context menu from showing if there are listeners on - // this event. Also fired on mobile when the user holds a single touch - // for a second (also called long press). - // @event keypress: Event - // Fired when the user presses a key from the keyboard while the map is focused. - L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' + - 'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this); + onOff = onOff || 'on'; - if (this.options.trackResize) { - L.DomEvent[onOff](window, 'resize', this._onResize, this); + L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this); + + var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter', + 'mouseleave', 'mousemove', 'contextmenu'], + i, len; + + for (i = 0, len = events.length; i < len; i++) { + L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this); } - if (L.Browser.any3d && this.options.transform3DLimit) { - this[onOff]('moveend', this._onMoveEnd); + if (this.options.trackResize) { + L.DomEvent[onOff](window, 'resize', this._onResize, this); } }, _onResize: function () { L.Util.cancelAnimFrame(this._resizeRequest); this._resizeRequest = L.Util.requestAnimFrame( - function () { this.invalidateSize({debounceMoveend: true}); }, this); - }, - - _onScroll: function () { - this._container.scrollTop = 0; - this._container.scrollLeft = 0; + function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); }, - _onMoveEnd: function () { - var pos = this._getMapPanePos(); - if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { - // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have - // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/ - this._resetView(this.getCenter(), this.getZoom()); - } - }, + _onMouseClick: function (e) { + if (!this._loaded || (!e._simulated && + ((this.dragging && this.dragging.moved()) || + (this.boxZoom && this.boxZoom.moved()))) || + L.DomEvent._skipped(e)) { return; } - _findEventTargets: function (e, type) { - var targets = [], - target, - isHover = type === 'mouseout' || type === 'mouseover', - src = e.target || e.srcElement, - dragging = false; - - while (src) { - target = this._targets[L.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; } - targets.push(target); - if (isHover) { break; } - } - if (src === this._container) { break; } - src = src.parentNode; - } - if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) { - targets = [this]; - } - return targets; + this.fire('preclick'); + this._fireMouseEvent(e); }, - _handleDOMEvent: function (e) { + _fireMouseEvent: function (e) { if (!this._loaded || L.DomEvent._skipped(e)) { return; } - var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type; - - if (e.type === 'click') { - // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups). - // @event preclick: MouseEvent - // 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); - synth.type = 'preclick'; - this._handleDOMEvent(synth); - } - - if (type === 'mousedown') { - // prevents outline when clicking on keyboard-focusable element - L.DomUtil.preventOutline(e.target || e.srcElement); - } - - this._fireDOMEvent(e, type); - }, - - _fireDOMEvent: function (e, type, targets) { + var type = e.type; - if (e._stopped) { return; } + type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type)); - // Find the layer the event is propagating from and its parents. - targets = (targets || []).concat(this._findEventTargets(e, type)); + if (!this.hasEventListeners(type)) { return; } - if (!targets.length) { return; } - - var target = targets[0]; - if (type === 'contextmenu' && target.listens(type, true)) { + if (type === 'contextmenu') { L.DomEvent.preventDefault(e); } - var data = { - originalEvent: e - }; - - if (e.type !== 'keypress') { - var isMarker = target instanceof L.Marker; - data.containerPoint = isMarker ? - this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); - data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); - data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); - } + var containerPoint = this.mouseEventToContainerPoint(e), + layerPoint = this.containerPointToLayerPoint(containerPoint), + latlng = this.layerPointToLatLng(layerPoint); - 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; } - } + this.fire(type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + originalEvent: e + }); }, - _draggableMoved: function (obj) { - obj = obj.options.draggable ? obj : this; - return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved()); + _onTileLayerLoad: function () { + this._tileLayersToLoad--; + if (this._tileLayersNum && !this._tileLayersToLoad) { + this.fire('tilelayersload'); + } }, _clearHandlers: function () { @@ -1052,26 +707,25 @@ L.Map = L.Evented.extend({ } }, - // @section Other Methods - - // @method whenReady(fn: Function, context?: Object): this - // Runs the given function `fn` when the map gets initialized with - // a view (center and zoom) and at least one layer, or immediately - // if it's already initialized, optionally passing a function context. whenReady: function (callback, context) { if (this._loaded) { - callback.call(context || this, {target: this}); + callback.call(context || this, this); } else { this.on('load', callback, context); } return this; }, + _layerAdd: function (layer) { + layer.onAdd(this); + this.fire('layeradd', {layer: layer}); + }, + // private methods for getting map state _getMapPanePos: function () { - return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0); + return L.DomUtil.getPosition(this._mapPane); }, _moved: function () { @@ -1079,21 +733,19 @@ L.Map = L.Evented.extend({ return pos && !pos.equals([0, 0]); }, - _getTopLeftPoint: function (center, zoom) { - var pixelOrigin = center && zoom !== undefined ? - this._getNewPixelOrigin(center, zoom) : - this.getPixelOrigin(); - return pixelOrigin.subtract(this._getMapPanePos()); + _getTopLeftPoint: function () { + return this.getPixelOrigin().subtract(this._getMapPanePos()); }, - _getNewPixelOrigin: function (center, zoom) { + _getNewTopLeftPoint: function (center, zoom) { var viewHalf = this.getSize()._divideBy(2); - return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round(); + // TODO round on display, not calculation to increase precision? + return this.project(center, zoom)._subtract(viewHalf)._round(); }, - _latLngToNewLayerPoint: function (latlng, zoom, center) { - var topLeft = this._getNewPixelOrigin(center, zoom); - return this.project(latlng, zoom)._subtract(topLeft); + _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) { + var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos()); + return this.project(latlng, newZoom)._subtract(topLeft); }, // layer point of the current center @@ -1116,13 +768,6 @@ L.Map = L.Evented.extend({ viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), offset = this._getBoundsOffset(viewBounds, bounds, zoom); - // If offset is less than a pixel, ignore. - // This prevents unstable projections from getting into - // an infinite loop of tiny offsets. - if (offset.round().equals([0, 0])) { - return center; - } - return this.unproject(centerPoint.add(offset), zoom); }, @@ -1138,15 +783,11 @@ L.Map = L.Evented.extend({ // returns offset needed for pxBounds to get inside maxBounds at a specified zoom _getBoundsOffset: function (pxBounds, maxBounds, zoom) { - var projectedMaxBounds = L.bounds( - this.project(maxBounds.getNorthEast(), zoom), - this.project(maxBounds.getSouthWest(), zoom) - ), - minOffset = projectedMaxBounds.min.subtract(pxBounds.min), - maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), + var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min), + seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max), - dx = this._rebound(minOffset.x, -maxOffset.x), - dy = this._rebound(minOffset.y, -maxOffset.y); + dx = this._rebound(nwOffset.x, -seOffset.x), + dy = this._rebound(nwOffset.y, -seOffset.y); return new L.Point(dx, dy); }, @@ -1159,25 +800,12 @@ L.Map = L.Evented.extend({ _limitZoom: function (zoom) { var min = this.getMinZoom(), - max = this.getMaxZoom(), - snap = L.Browser.any3d ? this.options.zoomSnap : 1; - if (snap) { - zoom = Math.round(zoom / snap) * snap; - } + max = this.getMaxZoom(); + return Math.max(min, Math.min(max, zoom)); } }); -// @section - -// @factory L.map(id: String, options?: Map options) -// Instantiates a map object given the DOM ID of a `
` element -// and optionally an object literal with `Map options`. -// -// @alternative -// @factory L.map(el: HTMLElement, options?: Map options) -// Instantiates a map object given an instance of a `
` HTML element -// and optionally an object literal with `Map options`. L.map = function (id, options) { return new L.Map(id, options); }; diff --git a/src/map/anim/Map.ZoomAnimation.js b/src/map/anim/Map.ZoomAnimation.js index f6081e2b503..4d06b7f6e87 100644 --- a/src/map/anim/Map.ZoomAnimation.js +++ b/src/map/anim/Map.ZoomAnimation.js @@ -2,38 +2,30 @@ * Extends L.Map to handle zoom animations. */ -// @namespace Map -// @section Animation Options L.Map.mergeOptions({ - // @option zoomAnimation: Boolean = true - // Whether the map zoom animation is enabled. By default it's enabled - // in all browsers that support CSS3 Transitions except Android. zoomAnimation: true, - - // @option zoomAnimationThreshold: Number = 4 - // Won't animate zoom if the zoom difference exceeds this value. zoomAnimationThreshold: 4 }); -var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera; - -if (zoomAnimated) { +if (L.DomUtil.TRANSITION) { L.Map.addInitHook(function () { // don't animate on browsers without hardware-accelerated transitions or old Android/Opera - this._zoomAnimated = this.options.zoomAnimation; + this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION && + L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera; // 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); + L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this); } }); } +<<<<<<< HEAD +L.Map.include(!L.DomUtil.TRANSITION ? {} : { + +======= L.Map.include(!zoomAnimated ? {} : { _createAnimProxy: function () { @@ -43,23 +35,27 @@ L.Map.include(!zoomAnimated ? {} : { this.on('zoomanim', function (e) { var prop = L.DomUtil.TRANSFORM, - transform = proxy.style[prop]; + transform = proxy.style[prop]; L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); // workaround for case when transform is the same and so transitionend event is not fired - if (transform === proxy.style[prop] && this._animatingZoom) { + if (transform === proxy.style[prop] & this._animatingZoom) { this._onZoomTransitionEnd(); } }, this); +<<<<<<< HEAD +======= this.on('load moveend', function () { var c = this.getCenter(), - z = this.getZoom(); + z = this.getZoom(); L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1)); }, this); }, +>>>>>>> master +>>>>>>> upstream/master-docs-merge _catchTransitionEnd: function (e) { if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) { this._onZoomTransitionEnd(); @@ -82,55 +78,66 @@ L.Map.include(!zoomAnimated ? {} : { // offset is the pixel coords of the zoom origin relative to the current center var scale = this.getZoomScale(zoom), - offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale); + offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale), + origin = this._getCenterLayerPoint()._add(offset); // 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 () { - this - ._moveStart(true) - ._animateZoom(center, zoom, true); - }, this); + this + .fire('movestart') + .fire('zoomstart'); + + this._animateZoom(center, zoom, origin, scale, null, true); return true; }, - _animateZoom: function (center, zoom, startAnim, noUpdate) { - if (startAnim) { + _animateZoom: function (center, zoom, origin, scale, delta, backwards, forTouchZoom) { + + if (!forTouchZoom) { this._animatingZoom = true; + } - // remember what center/zoom to set after animation - this._animateToCenter = center; - this._animateToZoom = zoom; + // put transform transition on all layers with leaflet-zoom-animated class + L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); - L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim'); - } + // remember what center/zoom to set after animation + this._animateToCenter = center; + this._animateToZoom = zoom; - // @event zoomanim: ZoomAnimEvent - // Fired on every frame of a zoom animation - this.fire('zoomanim', { - center: center, - zoom: zoom, - noUpdate: noUpdate - }); + // disable any dragging during animation + if (L.Draggable) { + L.Draggable._disabled = true; + } - // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693 - setTimeout(L.bind(this._onZoomTransitionEnd, this), 250); + L.Util.requestAnimFrame(function () { + this.fire('zoomanim', { + center: center, + zoom: zoom, + origin: origin, + scale: scale, + delta: delta, + backwards: backwards + }); + // horrible hack to work around a Chrome bug https://github.com/Leaflet/Leaflet/issues/3689 + setTimeout(L.bind(this._onZoomTransitionEnd, this), 250); + }, this); }, _onZoomTransitionEnd: function () { if (!this._animatingZoom) { return; } - L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); - this._animatingZoom = false; - this._move(this._animateToCenter, this._animateToZoom); + L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); - // This anim frame should prevent an obscure iOS webkit tile loading race condition. L.Util.requestAnimFrame(function () { - this._moveEnd(true); + this._resetView(this._animateToCenter, this._animateToZoom, true, true); + + if (L.Draggable) { + L.Draggable._disabled = false; + } }, this); } }); diff --git a/src/map/handler/Map.TouchZoom.js b/src/map/handler/Map.TouchZoom.js index 55ff7bbf87e..abbf6103c01 100644 --- a/src/map/handler/Map.TouchZoom.js +++ b/src/map/handler/Map.TouchZoom.js @@ -2,20 +2,8 @@ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers. */ -// @namespace Map -// @section Interaction Options L.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, - - // @option bounceAtZoomLimits: Boolean = true - // Set it to false if you don't want the map to zoom beyond min/max zoom - // and then bounce back when pinch-zooming. bounceAtZoomLimits: true }); @@ -30,24 +18,24 @@ L.Map.TouchZoom = L.Handler.extend({ _onTouchStart: function (e) { var map = this._map; - if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } - var p1 = map.mouseEventToContainerPoint(e.touches[0]), - p2 = map.mouseEventToContainerPoint(e.touches[1]); + if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; } - this._centerPoint = map.getSize()._divideBy(2); - this._startLatLng = map.containerPointToLatLng(this._centerPoint); - if (map.options.touchZoom !== 'center') { - this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2)); - } + var p1 = map.mouseEventToLayerPoint(e.touches[0]), + p2 = map.mouseEventToLayerPoint(e.touches[1]), + viewCenter = map._getCenterLayerPoint(); + this._startCenter = p1.add(p2)._divideBy(2); this._startDist = p1.distanceTo(p2); - this._startZoom = map.getZoom(); this._moved = false; this._zooming = true; - map._stop(); + this._centerOffset = viewCenter.subtract(this._startCenter); + + if (map._panAnim) { + map._panAnim.stop(); + } L.DomEvent .on(document, 'touchmove', this._onTouchMove, this) @@ -57,68 +45,83 @@ L.Map.TouchZoom = L.Handler.extend({ }, _onTouchMove: function (e) { - if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } + var map = this._map; - var map = this._map, - p1 = map.mouseEventToContainerPoint(e.touches[0]), - p2 = map.mouseEventToContainerPoint(e.touches[1]), - scale = p1.distanceTo(p2) / this._startDist; + if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; } + var p1 = map.mouseEventToLayerPoint(e.touches[0]), + p2 = map.mouseEventToLayerPoint(e.touches[1]); - this._zoom = map.getScaleZoom(scale, this._startZoom); + this._scale = p1.distanceTo(p2) / this._startDist; + this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter); - if (!map.options.bounceAtZoomLimits && ( - (this._zoom < map.getMinZoom() && scale < 1) || - (this._zoom > map.getMaxZoom() && scale > 1))) { - this._zoom = map._limitZoom(this._zoom); - } + if (this._scale === 1) { return; } - if (map.options.touchZoom === 'center') { - this._center = this._startLatLng; - if (scale === 1) { return; } - } else { - // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng - var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); - if (scale === 1 && delta.x === 0 && delta.y === 0) { return; } - this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); + if (!map.options.bounceAtZoomLimits) { + if ((map.getZoom() === map.getMinZoom() && this._scale < 1) || + (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; } } if (!this._moved) { - map._moveStart(true); + L.DomUtil.addClass(map._mapPane, 'leaflet-touching'); + + map + .fire('movestart') + .fire('zoomstart'); + this._moved = true; } L.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); + this._animRequest = L.Util.requestAnimFrame( + this._updateOnMove, this, true, this._map._container); L.DomEvent.preventDefault(e); }, + _updateOnMove: function () { + var map = this._map, + origin = this._getScaleOrigin(), + center = map.layerPointToLatLng(origin), + zoom = map.getScaleZoom(this._scale); + + map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true); + }, + _onTouchEnd: function () { if (!this._moved || !this._zooming) { this._zooming = false; return; } + var map = this._map; + this._zooming = false; + L.DomUtil.removeClass(map._mapPane, 'leaflet-touching'); L.Util.cancelAnimFrame(this._animRequest); L.DomEvent .off(document, 'touchmove', this._onTouchMove) .off(document, 'touchend', this._onTouchEnd); - // Pinch updates GridLayers' levels only when snapZoom is off, so snapZoom becomes noUpdate. - if (this._map.options.zoomAnimation) { - this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.snapZoom); - } else { - this._map._resetView(this._center, this._map._limitZoom(this._zoom)); - } + var origin = this._getScaleOrigin(), + center = map.layerPointToLatLng(origin), + + oldZoom = map.getZoom(), + floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom, + roundZoomDelta = (floatZoomDelta > 0 ? + Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)), + + zoom = map._limitZoom(oldZoom + roundZoomDelta), + scale = map.getZoomScale(zoom) / this._scale; + + map._animateZoom(center, zoom, origin, scale); + }, + + _getScaleOrigin: function () { + var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale); + return this._startCenter.add(centerOffset); } }); -// @section Handlers -// @property touchZoom: Handler -// Touch zoom handler. L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);