landvex: Fixar och tester klara för alla komponenter

- Datafabrik: Dockerfile fix, agentorkestrering fungerar
- Vision: Identify-modell, FAISS, OCR alla testade
- API: Alla 7 integrationstester passerade
- Upplösare: Entitetsupplösning verifierad
This commit is contained in:
Bernt
2026-07-05 06:41:32 +00:00
parent f4f853d94b
commit aee0f09db8
19583 changed files with 1450867 additions and 1153 deletions
+2191
View File
@@ -0,0 +1,2191 @@
Leaflet Changelog
=================
(all changes without author notice are by [@mourner](https://github.com/mourner))
## 1.9.4 (2023-05-18)
### 🐞 Bug fixes
- Fix tile gaps in Chromium-based browsers ([#8891](https://github.com/Leaflet/Leaflet/pull/8891) by [@IvanSanchez](https://github.com/IvanSanchez))
- Fix vector drifts when `zoomAnimation` is `false` and zooming via `flyTo` or pinch ([#8794](https://github.com/Leaflet/Leaflet/pull/8794) by [@plainheart](https://github.com/plainheart))
- Ensure `toGeoJSON()` still works with an empty array ([#8737](https://github.com/Leaflet/Leaflet/pull/8737) by [@Falke-Design](https://github.com/Falke-Design))
- Ensure `LineUtil` and `PolyUtil` only iterate over array values and not properties ([#8840](https://github.com/Leaflet/Leaflet/pull/8840) by [@Falke-Design](https://github.com/Falke-Design))
- Fix rounding errors in center calculation of `LineUtil` and `PolyUtil` for small layers ([#8784](https://github.com/Leaflet/Leaflet/pull/8784) by [@Falke-Design](https://github.com/Falke-Design))
- Prevent unwanted layer toggle while expanding the Layers control on mobile ([#8910](https://github.com/Leaflet/Leaflet/pull/8910) by [@Falke-Design](https://github.com/Falke-Design))
- Fix an error when a focusing on a `Tooltip`-bound `FeatureGroup` that contains a layer without a `getElement` method ([#8890](https://github.com/Leaflet/Leaflet/pull/8890) by [@Falke-Design](https://github.com/Falke-Design))
- Fix `Tooltip` is not showing when loaded dynamically while moving the map ([#8672](https://github.com/Leaflet/Leaflet/pull/8672) by [@theGOTOguy](https://github.com/theGOTOguy))
- Fix `noMoveStart` option not applying to `fitBounds` ([#8911](https://github.com/Leaflet/Leaflet/pull/8911) by [@AbdullahSohail-SE](https://github.com/AbdullahSohail-SE))
- Fix outlines showing up when interacting with the map on Safari 16.4+ ([#8917](https://github.com/Leaflet/Leaflet/pull/8917) by [@jonkoops](https://github.com/jonkoops))
## 1.9.3 (2022-11-18)
### 🙌 Accessibility
- Expand the layers control on <kbd>Enter</kbd> keydown ([#8556](https://github.com/Leaflet/Leaflet/pull/8556) by [@larsgw](https://github.com/larsgw))
- Align the scale control's alpha transparency with the attribution control ([#8547](https://github.com/Leaflet/Leaflet/pull/8547) by [@Malvoz](https://github.com/Malvoz))
- Allow the scale control's text to overflow the container ([#8548](https://github.com/Leaflet/Leaflet/pull/8548) by [@Malvoz](https://github.com/Malvoz))
### 🐞 Bug fixes
- Remove internal usage of `L` global ([#8536](https://github.com/Leaflet/Leaflet/pull/8536) by [@mourner](https://github.com/mourner))
- Fix intermittent wobble when using `setMaxBounds(map.getBounds())` ([#8534](https://github.com/Leaflet/Leaflet/pull/8534) by [@rjackson](https://github.com/rjackson))
- Ensure that `latLngsToCoords()` does not return references passed to it ([#7344](https://github.com/Leaflet/Leaflet/pull/7344) by [@marlo22](https://github.com/marlo22]))
- Ensure `worldCopyJump()` behavior is consistent when using a keyboard ([#8562](https://github.com/Leaflet/Leaflet/pull/8562) by [@Falke-Design](https://github.com/Falke-Design))
- Ensure correct target is used for the `popupopen` event ([#8571](https://github.com/Leaflet/Leaflet/pull/8571) by [@Belair34](https://github.com/Belair34))
- Prevent recursion when panning a `Popup` ([#8520](https://github.com/Leaflet/Leaflet/pull/8520) by [@rjackson](https://github.com/rjackson))
- Support CSS [`position: sticky`](https://developer.mozilla.org/en-US/docs/Web/CSS/position#sticky) for map container ([#8550](https://github.com/Leaflet/Leaflet/pull/8550) by [@tmiaa](https://github.com/tmiaa))
## 1.9.2 (2022-10-04)
### 🐞 Bug fixes
* ⚠️ Drop ESM entrypoint from package because of numerous compatibility issues with plugins (import `leaflet/dist/leaflet-src.esm.js` explicitly instead to take advantage; ESM by default will come in v2) ([#8493](https://github.com/Leaflet/Leaflet/pull/8493) by [@jonkoops](https://github.com/jonkoops))
* Fix a bug where tooltips could throw an error with canvas renderer ([#8498](https://github.com/Leaflet/Leaflet/pull/8498) by [@Falke-Design](https://github.com/Falke-Design))
* Fix a bug with incorrect marker popup location when there are multiple markers registered to the same popup ([#8523](https://github.com/Leaflet/Leaflet/pull/8523) by [@raychanks](https://github.com/raychanks)).
### 🧪 Tests
* Fix unit tests suite stopping abruptly on Mac ([#8478](https://github.com/Leaflet/Leaflet/pull/8478))
### 📝 Docs
* Fix `Bounds` `equals` parameters in docs ([#8500](https://github.com/Leaflet/Leaflet/pull/8500) by [@Falke-Design](https://github.com/Falke-Design))
## 1.9.2 (2022-10-04)
### 🐞 Bug fixes
* ⚠️ Drop ESM entrypoint from package because of numerous compatibility issues with plugins (import `leaflet/dist/leaflet-src.esm.js` explicitly instead to take advantage; ESM by default will come in v2) ([#8493](https://github.com/Leaflet/Leaflet/pull/8493) by [@jonkoops](https://github.com/jonkoops))
* Fix a bug where tooltips could throw an error with canvas renderer ([#8498](https://github.com/Leaflet/Leaflet/pull/8498) by [@Falke-Design](https://github.com/Falke-Design))
* Fix a bug with incorrect marker popup location when there are multiple markers registered to the same popup ([#8523](https://github.com/Leaflet/Leaflet/pull/8523) by [@raychanks](https://github.com/raychanks)).
### 🧪 Tests
* Fix unit tests suite stopping abruptly on Mac ([#8478](https://github.com/Leaflet/Leaflet/pull/8478))
### 📝 Docs
* Fix `Bounds` `equals` parameters in docs ([#8500](https://github.com/Leaflet/Leaflet/pull/8500) by [@Falke-Design](https://github.com/Falke-Design))
## 1.9.1 (2022-09-23)
* Fix `Events` `listens` not propagating to parent objects, in particular fixing compatibility with **Leaflet.markercluster** plugin ([#8211](https://github.com/Leaflet/Leaflet/pull/8457) by [@Falke-Design](https://github.com/Falke-Design))
## 1.9.0 (2022-09-21)
### ⚠️ Breaking Changes
* Update `color-adjust` to `print-color-adjust` ([#8211](https://github.com/Leaflet/Leaflet/pull/8211) by [@Malvoz](https://github.com/Malvoz))
### ❇️ API changes
* Add `content` and `latLng` options to `Popup` / `Tooltip` constructors ([#7783](https://github.com/Leaflet/Leaflet/pull/7783) by [@Falke-Design](https://github.com/Falke-Design))
* Extend `Bounds` to have the same functions as `LatLngBounds` ([#7882](https://github.com/Leaflet/Leaflet/pull/7882) by [@Falke-Design](https://github.com/Falke-Design))
### ✨ Improvements
* Expose ESM entrypoint with Leaflet global ([#8329](https://github.com/Leaflet/Leaflet/pull/8329) by [@jonkoops](https://github.com/Falke-Design))
* Update `getCenter()` calculation and move it to `PolyUtil` / `LineUtil` ([#7603](https://github.com/Leaflet/Leaflet/pull/7603) by [@Falke-Design](https://github.com/Falke-Design))
* Remove border styles in overflowing popups ([#8260](https://github.com/Leaflet/Leaflet/pull/8260) by [@Malvoz](https://github.com/Malvoz))
* Fix "listener not found" warning when setting `maxBounds` ([#8168](https://github.com/Leaflet/Leaflet/pull/8168))
* Remove "listener not found" warning ([#8234](https://github.com/Leaflet/Leaflet/pull/8234) by [@Falke-Design](https://github.com/Falke-Design))
* Extend `Events.listens` to search for specific function ([#8161](https://github.com/Leaflet/Leaflet/pull/8161) by [@Falke-Design](https://github.com/Falke-Design))
* Add `noMoveStart` option to `panTo` ([#6685](https://github.com/Leaflet/Leaflet/pull/6685) by [@Chivano](https://github.com/Chivano))
* Add `FeatureCollection` handling to `geometryToLayer` ([#8163](https://github.com/Leaflet/Leaflet/pull/8163) by [@Falke-Design](https://github.com/Falke-Design))
### 🙌 Accessibility
* Improve `Tooltip` accessibility (focus and voice over) ([#8247](https://github.com/Leaflet/Leaflet/pull/8247) by [@alekzvik](https://github.com/alekzvik))
* Fix links in accessibility guide ([#8198](https://github.com/Leaflet/Leaflet/pull/8198) by [@Malvoz](https://github.com/Malvoz))
* Remove redundant `role="presentation"` from image tiles ([#8172](https://github.com/Leaflet/Leaflet/pull/8172) by [@Malvoz](https://github.com/Malvoz))
### 🐞 Bug fixes
* Fix invalid GeoJSON on unbalanced arrays ([#7637](https://github.com/Leaflet/Leaflet/pull/7637) by [@steff1986](https://github.com/steff1986))
* Fix 2 step zooming while using mouse wheel scrolling ([#8298](https://github.com/Leaflet/Leaflet/pull/8298) by [@Falke-Design](https://github.com/Falke-Design))
* Fix wrong assigned parameter while calling `map._move` over `requestAnimFrame` ([#8328](https://github.com/Leaflet/Leaflet/pull/8328) by [@AMDvsTMD](https://github.com/AMDvsTMD))
* Fix `_isClickDisabled` to not throw no error if parent is removed from DOM ([#8288](https://github.com/Leaflet/Leaflet/pull/8288) by [@Falke-Design](https://github.com/Falke-Design))
* Fix `DomEvent.DoubleTap` to ignore clicks on `<label>`s with a `for` attribute ([#8227](https://github.com/Leaflet/Leaflet/pull/8227) by [@IvanSanchez](https://github.com/IvanSanchez))
* Fix calling `once()` twice if same event is fired inside `once` ([#8190](https://github.com/Leaflet/Leaflet/pull/8190) by [@Falke-Design](https://github.com/Falke-Design))
* Fix `map.getCenter()` returning a mutable object ([#8167](https://github.com/Leaflet/Leaflet/pull/8167))
* Fix regression about popup close button modifying the URL ([#8160](https://github.com/Leaflet/Leaflet/pull/8160) by [@IvanSanchez](https://github.com/IvanSanchez))
* Fix `min/maxZoom` when used in combination with `detectRetina` ([#7328](https://github.com/Leaflet/Leaflet/pull/7328) by [@bozdoz](https://github.com/bozdoz))
### 📝 Docs
* Use preferred tile.openstreetmap.org URL ([#8418](https://github.com/Leaflet/Leaflet/pull/8418) by [@Firefishy](https://github.com/Firefishy))
* Use LocalStorage for dialog sessions ([#8382](https://github.com/Leaflet/Leaflet/pull/8382) by [@ChristopherWirtOfficial](https://github.com/ChristopherWirtOfficial))
* Update anchor links for headers and in collapsed accordions ([#7780](https://github.com/Leaflet/Leaflet/pull/7780) by [@Falke-Design](https://github.com/Falke-Design))
* Fix typo in reference-1.6.0.html ([#8330](https://github.com/Leaflet/Leaflet/pull/8330) by [@eltociear](https://github.com/eltociear))
* Add pre-commit linting to CONTRIBUTING.md ([#8299](https://github.com/Leaflet/Leaflet/pull/8299) by [@Falke-Design](https://github.com/Falke-Design))
* Ensure no borders on dialog iframe ([#8296](https://github.com/Leaflet/Leaflet/pull/8296) by [@Malvoz](https://github.com/Malvoz))
* Replace Mapbox with OpenStreetMap in tutorials and examples ([#7818](https://github.com/Leaflet/Leaflet/pull/7818) by [@Falke-Design](https://github.com/Falke-Design))
* Remove DOCS-TODO.md ([#8259](https://github.com/Leaflet/Leaflet/pull/8296) by [@Malvoz](https://github.com/Malvoz))
* Better PosAnimation example ([#7386](https://github.com/Leaflet/Leaflet/pull/7386) by [@stell](https://github.com/stell))
* Correct heading level in GeoJSON example ([#8230](https://github.com/Leaflet/Leaflet/pull/8230) by [@Malvoz](https://github.com/Malvoz))
* Update Overlay Tutorial (ImageOverlay, VideoOverlay, SVGOverlay) ([#8090](https://github.com/Leaflet/Leaflet/pull/8090) by [@KonstantinBiryukov](https://github.com/KonstantinBiryukov))
* Change attribute `anchor` to `data-anchor` ([#8174](https://github.com/Leaflet/Leaflet/pull/8174) by [@KnightJam1](https://github.com/KnightJam1))
* Fix bad markdown causing link to not work ([#8156](https://github.com/Leaflet/Leaflet/pull/8156) by [@freyfogle](https://github.com/freyfogle))
* A couple of site SEO fixes ([#8229](https://github.com/Leaflet/Leaflet/pull/8229) by [@Malvoz](https://github.com/Malvoz))
* Fix attribution flag 1px misalignment on some websites ([#8170](https://github.com/Leaflet/Leaflet/pull/8170))
* Attribution flag now resizes with font-size changes ([#8183](https://github.com/Leaflet/Leaflet/pull/8183) by [@sumitsaurabh927](https://github.com/sumitsaurabh927))
* Add Dialog to website ([#8177](https://github.com/Leaflet/Leaflet/pull/8177) by [@Falke-Design](https://github.com/Falke-Design) and [#8193](https://github.com/Leaflet/Leaflet/pull/8193), [#8194](https://github.com/Leaflet/Leaflet/pull/8194) by [@Malvoz](https://github.com/Malvoz))
### 🔧 Workflow
* Improve GitHub Workflows security ([#8419](https://github.com/Leaflet/Leaflet/pull/8419) by [@sashashura](https://github.com/sashashura))
* Update development dependencies
* Replace deprecated `eslint-plugin-script-tags` ([#8331](https://github.com/Leaflet/Leaflet/pull/8331) by [@jonkoops](https://github.com/Falke-Design))
* Use major version ranges for Github Actions ([#8286](https://github.com/Leaflet/Leaflet/pull/8286) by [@jonkoops](https://github.com/Falke-Design))
* Configure YAML issue forms ([#8246](https://github.com/Leaflet/Leaflet/pull/8246) by [@Malvoz](https://github.com/Malvoz))
* Add FUNDING.yml
* Add pre-commit hook to fix linting issues ([#8212](https://github.com/Leaflet/Leaflet/pull/8212) by [@jonkoops](https://github.com/Falke-Design))
* Remove Dependabot specific labels ([#8199](https://github.com/Leaflet/Leaflet/pull/8199) by [@jonkoops](https://github.com/Falke-Design))
* Use shorter bundlemon names ([#8195](https://github.com/Leaflet/Leaflet/pull/8195))
* Make sure integrity hashes are generated for the built version
### 🧪 Tests
* Added test cases for `map.latLngToLayerPoint` method ([#8407](https://github.com/Leaflet/Leaflet/pull/8407) by [@kreloaded](https://github.com/kreloaded))
* Add test for `map.panTo` ([#8390](https://github.com/Leaflet/Leaflet/pull/8390) by [@anurag-dhamala](https://github.com/anurag-dhamala))
* Add test for `map.containerPointToLatLng` and `map.latLngToContainerPoint` ([#8384](https://github.com/Leaflet/Leaflet/pull/8384) by [@abhi3315](https://github.com/abhi3315))
* Add test for `Layer._addZoomLimit` ([#8037](https://github.com/Leaflet/Leaflet/pull/8037) by [@zishiwu123](https://github.com/zishiwu123))
* Add tests for `Map` ([#8206](https://github.com/Leaflet/Leaflet/pull/8206) by [@stephenspol](https://github.com/stephenspol))
* Add test for `CircleMarker._containsPoint` ([#8340](https://github.com/Leaflet/Leaflet/pull/8340) by [@gernhard1337](https://github.com/gernhard1337))
* Add missing handler tests ([#8182](https://github.com/Leaflet/Leaflet/pull/8182) by [@Falke-Design](https://github.com/Falke-Design))
* Cover Rectangle with unit Tests ([#8144](https://github.com/Leaflet/Leaflet/pull/8144) by [@stephenspol](https://github.com/stephenspol))
## 1.8.0 (2022-04-18)
### ⚠️ Breaking Changes
* Improve reliability of `contextmenu` event simulation on mobile Safari by introducing a new `TapHold` handler, replacing legacy `Tap` ([#7026](https://github.com/Leaflet/Leaflet/pull/7026) by [@johnd0e](https://github.com/johnd0e))
* Reorganize `DivOverlay`/`Popup`/`Tooltip` APIs ([#7540](https://github.com/Leaflet/Leaflet/pull/7540) by [@johnd0e](https://github.com/johnd0e))
* Move `Popup` related options from `DivOverlay` to `Popup` ([#7778](https://github.com/Leaflet/Leaflet/pull/7778) by [@Falke-Design](https://github.com/Falke-Design))
* Change `Tooltip` class from `leaflet-clickable` to `leaflet-interactive` ([#7719](https://github.com/Leaflet/Leaflet/pull/7719) by [@Falke-Design](https://github.com/Falke-Design))
* `Map.closeTooltip` now requires a layer as argument ([#7533](https://github.com/Leaflet/Leaflet/pull/7533) by [@johnd0e](https://github.com/johnd0e))
* Improve error / argument handling for event listeners ([#7518](https://github.com/Leaflet/Leaflet/pull/7518) by [@johnd0e](https://github.com/johnd0e))
* Improve reliability of touch events simulation on non-touch devices (`DomEvent.Pointer`) ([#7059](https://github.com/Leaflet/Leaflet/pull/7059), [#7084](https://github.com/Leaflet/Leaflet/pull/7084), [#7415](https://github.com/Leaflet/Leaflet/pull/7415) by [@johnd0e](https://github.com/johnd0e))
* Improve reliability of `dblclick` event simulation on touch devices (`DomEvent.DoubleTap`) ([#7027](https://github.com/Leaflet/Leaflet/pull/7027) by [@johnd0e](https://github.com/johnd0e))
* Improve reliability of `disableClickPropagation` ([#7439](https://github.com/Leaflet/Leaflet/pull/7439) by [@johnd0e](https://github.com/johnd0e))
* Improve `Map` `hasLayer()` and `LayerGroup` `hasLayer()` to require a layer as argument ([#6999](https://github.com/Leaflet/Leaflet/pull/6999) by [@johnd0e](https://github.com/johnd0e))
* Fix `Class.include` to not overwrite `options` ([#7756](https://github.com/Leaflet/Leaflet/pull/7756) by [@johnd0e](https://github.com/johnd0e))
* Fix `Class.extend` to not modify source props object ([#6766](https://github.com/Leaflet/Leaflet/pull/6766) by [@johnd0e](https://github.com/johnd0e))
* Improve `Browser.touch` touch devices detection ([#7029](https://github.com/Leaflet/Leaflet/pull/7029) by [@johnd0e](https://github.com/johnd0e))
* Get rid of legacy Android hacks ([#7022](https://github.com/Leaflet/Leaflet/pull/7022) by [@johnd0e](https://github.com/johnd0e))
* Allow fonts to respect users' browser settings by making the `font-size` _relative_ to the map container. (You can change the font size on `leaflet-container` to adjust it if needed.) ([#7800](https://github.com/Leaflet/Leaflet/pull/7800) by [@Chandu-4444](https://github.com/Chandu-4444))
### ❇️ API changes
* Make `DivOverlay` / `Tooltip` `interactive` ([#7531](https://github.com/Leaflet/Leaflet/pull/7531), [#7532](https://github.com/Leaflet/Leaflet/pull/7532) by [@johnd0e](https://github.com/johnd0e))
* Add `openOn`, `close`, `toggle` functions to `DivOverlay` ([#6639](https://github.com/Leaflet/Leaflet/pull/6639) by [@johnd0e](https://github.com/johnd0e))
* Introduce `DomEvent.off(el)` to remove all listeners ([#7125](https://github.com/Leaflet/Leaflet/pull/7125) by [@johnd0e](https://github.com/johnd0e))
* Allow preventing round-off errors by passing `false` to `Util.formatNum` / `toGeoJSON` ([#7100](https://github.com/Leaflet/Leaflet/pull/7100) by [@johnd0e](https://github.com/johnd0e))
* Add `autoPanOnFocus` to `Marker` ([#8042](https://github.com/Leaflet/Leaflet/pull/8042) by [@IvanSanchez](https://github.com/IvanSanchez))
* Add `referrerPolicy` to `TileLayer` ([#7945](https://github.com/Leaflet/Leaflet/pull/7945) by [@natevw](https://github.com/natevw))
* Add `playsInline` to `VideoOverlay` ([#7928](https://github.com/Leaflet/Leaflet/pull/7928) by [@Falke-Design](https://github.com/Falke-Design))
* Add `getCenter` to `ImageOverlay` ([#7848](https://github.com/Leaflet/Leaflet/pull/7848) by [@Falke-Design](https://github.com/Falke-Design))
* Fire a `tileabort` event when a `TileLayer` load is cancelled ([#6786](https://github.com/Leaflet/Leaflet/pull/6786) by [@dstndstn](https://github.com/dstndstn))
* Add `crossOrigin` to `Icon` ([#7298](https://github.com/Leaflet/Leaflet/pull/7298) by [@syedmuhammadabid](https://github.com/syedmuhammadabid))
### ✨ Improvements
* Improve memory footprint by removing `will-change` CSS property on tile images ([#7872](https://github.com/Leaflet/Leaflet/pull/7872) by [@janjaap](https://github.com/janjaap))
* Improve reliability of icons path detection heuristics ([#7092](https://github.com/Leaflet/Leaflet/pull/7092) by [@johnd0e](https://github.com/johnd0e))
* Improve performance of adding tiled sources by avoiding excessive updates in `GridLayer.onAdd` ([#7570](https://github.com/Leaflet/Leaflet/pull/7570) by [@johnd0e](https://github.com/johnd0e))
* Improve handling of edge cases in `panInside` ([#7469](https://github.com/Leaflet/Leaflet/pull/7469) by [@daverayment](https://github.com/daverayment))
* Minify marker icon SVG ([#7600](https://github.com/Leaflet/Leaflet/pull/7600) by [@rala72](https://github.com/rala72))
* Allow template keys with spaces in `TileLayer` URL ([#7216](https://github.com/Leaflet/Leaflet/pull/7216) by [@lubojr](https://github.com/lubojr))
* Improve behavior of `Tooltip` bound to `ImageOverlay` ([#7306](https://github.com/Leaflet/Leaflet/pull/7306) by [@IvanSanchez](https://github.com/IvanSanchez))
* Remove the gap between Popup tip and content dialog ([#7920](https://github.com/Leaflet/Leaflet/pull/7920) by [@Malvoz](https://github.com/Malvoz))
* Fire `mousemove` through Canvas to map if it has no layers ([#7809](https://github.com/Leaflet/Leaflet/pull/7809) by [@johnd0e](https://github.com/johnd0e))
* Add print styles to prevent printers from removing background-images in controls ([#7851](https://github.com/Leaflet/Leaflet/pull/7851) by [@Malvoz](https://github.com/Malvoz))
* Move attribution code from `Layer` to `Control.Attribution` ([#7764](https://github.com/Leaflet/Leaflet/pull/7764) by [@johnd0e](https://github.com/johnd0e))
* Refactor `vmlCreate()` so that it does not expose closure to `TypeError` ([#7279](https://github.com/Leaflet/Leaflet/pull/7279) by [@darcyparker](https://github.com/darcyparker))
* Improve reliability of `Control.Layers` by not relying on Browser `android` and `touch` properties ([#7057](https://github.com/Leaflet/Leaflet/pull/7057) by [@johnd0e](https://github.com/johnd0e))
* Improve reliability of `Tooltip` by not relying on Browser `touch` checks ([#7535](https://github.com/Leaflet/Leaflet/pull/7535) by [@johnd0e](https://github.com/johnd0e))
* Make `Browser` mutable for easier automated testing ([#7335](https://github.com/Leaflet/Leaflet/pull/7335) by [@bozdoz](https://github.com/bozdoz))
* Replace `div` with `span` in `Control.Layers` container to fix an HTML validation error ([#7914](https://github.com/Leaflet/Leaflet/pull/7914) by [@tmiaa](https://github.com/tmiaa))
* Add a Ukrainian flag to default attribution 🇺🇦 (by [@mourner](https://github.com/mourner) in https://github.com/Leaflet/Leaflet/pull/8109)
### 🙌 Accessibility
* Increase default font sizes and decrease attribution transparency for improved legibility ([#8057](https://github.com/Leaflet/Leaflet/pull/8057), by [@mourner](https://github.com/mourner))
* Improve accessibility of popup close button ([#7908](https://github.com/Leaflet/Leaflet/pull/7908]), by [@Malvoz](https://github.com/Malvoz))
* Auto pan to markers on focus by default for improved keyboard operability ([#8042](https://github.com/Leaflet/Leaflet/pull/8042) by [@IvanSanchez](https://github.com/IvanSanchez))
* Add accessibility section to plugins guide ([#7277](https://github.com/Leaflet/Leaflet/pull/7277) by [@Malvoz](https://github.com/Malvoz))
* Update `Marker` to default to `role="button"` & `alt="marker"` for an improved screen reader experience ([#7895](https://github.com/Leaflet/Leaflet/pull/7895) by [@tmiaa](https://github.com/tmiaa))
* Set `role="button"` for appropriate semantics on the `<a>` layers control ([#7850](https://github.com/Leaflet/Leaflet/pull/7850) by [@Malvoz](https://github.com/Malvoz))
* Generally enable outlines for keyboard users by not stripping `outline` on focus for keyboard events ([#7259](https://github.com/Leaflet/Leaflet/pull/7259) by [@jafin](https://github.com/jafin))
* Enable outlines on `leaflet-container` for keyboard users ([#7996](https://github.com/Leaflet/Leaflet/pull/7996) by [@Malvoz](https://github.com/Malvoz))
* Multiple enhancements to popup's close button ([#7794](https://github.com/Leaflet/Leaflet/pull/7794) by [@Falke-Design](https://github.com/Falke-Design))
* Use relative `font-size` units for resizable text ([#7800](https://github.com/Leaflet/Leaflet/pull/7800) by [@Chandu-4444](https://github.com/Chandu-4444))
* Apply `:hover` styles to `:focus` as well ([#7274](https://github.com/Leaflet/Leaflet/pull/7274) by [@Malvoz](https://github.com/Malvoz))
* Hide the decorative attribution separator from screen readers ([#7969](https://github.com/Leaflet/Leaflet/pull/7969) by [@Malvoz](https://github.com/Malvoz))
* Make the disabled state of zoom controls available to screen readers ([#7280](https://github.com/Leaflet/Leaflet/pull/7280) by [@akshataj96](https://github.com/akshataj96))
* Hide the +/- characters in zoom controls from screen readers to prevent erroneous announcements ([#7795](https://github.com/Leaflet/Leaflet/pull/7795) by [@Falke-Design](https://github.com/Falke-Design))
### 🐞 Bug fixes
* Fix vector drift when dragging and immediately zooming (by [@manubb](https://github.com/manubb) [@johnd0e](https://github.com/johnd0e) [@mourner](https://github.com/mourner) in https://github.com/Leaflet/Leaflet/pull/8103)
* Reduce the occurrence of glitches on rapid zoom (by [@mourner](https://github.com/mourner) in https://github.com/Leaflet/Leaflet/pull/8102)
* Fix `Marker` jumping position while zooming in certain cases ([#7967](https://github.com/Leaflet/Leaflet/pull/7967) by [@Falke-Design](https://github.com/Falke-Design))
* Fix opening / closing `Tooltip` while dragging the map ([#7862](https://github.com/Leaflet/Leaflet/pull/7862) by [@Falke-Design](https://github.com/Falke-Design))
* Break the reference to the options of the `Class` prototype ([#7459](https://github.com/Leaflet/Leaflet/pull/7459) by [@Falke-Design](https://github.com/Falke-Design))
* Improve `Tooltip` options `permanent` & `sticky` to work together ([#7563](https://github.com/Leaflet/Leaflet/pull/7563) by [@Falke-Design](https://github.com/Falke-Design))
* Check if map container is still connected with Leaflet in `locate` event listener ([#7813](https://github.com/Leaflet/Leaflet/pull/7813) by [@Falke-Design](https://github.com/Falke-Design))
* Fix `Tooltip` `bindTooltip` to unbind existent tooltip ([#7633](https://github.com/Leaflet/Leaflet/pull/7633) by [@Falke-Design](https://github.com/Falke-Design))
* Correct `if` condition, to add zoom limits for Layer ([#7609](https://github.com/Leaflet/Leaflet/pull/7609) by [@vcoppe](https://github.com/vcoppe))
* `GridLayer` redraw tiles after changing `maxNativeZoom` ([#6443](https://github.com/Leaflet/Leaflet/pull/6443) by [@cherniavskii](https://github.com/cherniavskii))
* Fix `Popup` `keepInView` if the map needs to panned over a long distance ([#7792](https://github.com/Leaflet/Leaflet/pull/7792) by [@Falke-Design](https://github.com/Falke-Design))
* Tolerate wrong event names in `add/removePointerListener` ([#7808](https://github.com/Leaflet/Leaflet/pull/7808) by [@johnd0e](https://github.com/johnd0e))
* Reset width & padding to prevent cascading CSS from breaking tile rendering ([#6843](https://github.com/Leaflet/Leaflet/pull/6843) by [@Spudley](https://github.com/Spudley))
* Fix `mousedown` event calling after dragging `Canvas` map ([#7781](https://github.com/Leaflet/Leaflet/pull/7781) by [@johnd0e](https://github.com/johnd0e))
* Decrease `console.warn` pollution ([#7748](https://github.com/Leaflet/Leaflet/pull/7748) by [@johnd0e](https://github.com/johnd0e))
* Fix `contextmenu` event default-preventing when there are >1 target candidates ([#7544](https://github.com/Leaflet/Leaflet/pull/7544) by [@johnd0e](https://github.com/johnd0e))
* Prevent click on `Popup`-tip from firing on map. ([#7541](https://github.com/Leaflet/Leaflet/pull/7541) by [@johnd0e](https://github.com/johnd0e))
* Fix error by calling `Path.setStyle` before adding the layer to the map ([#6941](https://github.com/Leaflet/Leaflet/pull/6941) by [@NielsHolt](https://github.com/NielsHolt))
* Reset `BoxZoom` after cancel with ESC ([#7597](https://github.com/Leaflet/Leaflet/pull/7597) by [@Falke-Design](https://github.com/Falke-Design))
* Fix `noConflict` ([#7855](https://github.com/Leaflet/Leaflet/pull/7855) by [@Falke-Design](https://github.com/Falke-Design))
* Fix popup appearance when content is empty ([#8136](https://github.com/Leaflet/Leaflet/pull/8136), by [@ansh-ag](https://github.com/ansh-ag))
* Fix `latLngToCoords` and `latLngsToCoords` not accepting array form of lat/lngs ([#7436](https://github.com/Leaflet/Leaflet/pull/7436), by [@Relkfaw](https://github.com/Relkfaw))
### 📝 Docs
* Add a new [Leaflet accessibility tutorial](https://leafletjs.com/examples/accessibility/) ([#8081](https://github.com/Leaflet/Leaflet/pull/8081), by [@Malvoz](https://github.com/Malvoz))
* Upgrade Code of Conduct to Contributor Covenant v2 and improve its visibility ([#7984](https://github.com/Leaflet/Leaflet/pull/7984) by [@mourner](https://github.com/mourner))
* Lint examples ([#7827](https://github.com/Leaflet/Leaflet/pull/7827) by [@mourner](https://github.com/mourner))
* Update usability in Docs ([#7982](https://github.com/Leaflet/Leaflet/pull/7982), [#7703](https://github.com/Leaflet/Leaflet/pull/7703), [#7950](https://github.com/Leaflet/Leaflet/pull/7950), [#7906](https://github.com/Leaflet/Leaflet/pull/7906), [#7907](https://github.com/Leaflet/Leaflet/pull/7907), [#7696](https://github.com/Leaflet/Leaflet/pull/7696), [#7816](https://github.com/Leaflet/Leaflet/pull/7816), [#7345](https://github.com/Leaflet/Leaflet/pull/7345), [#7815](https://github.com/Leaflet/Leaflet/pull/7815), [#7948](https://github.com/Leaflet/Leaflet/pull/7948), [#7901](https://github.com/Leaflet/Leaflet/pull/7901) by [@Falke-Design](https://github.com/Falke-Design), [@avioli](https://github.com/avioli), [@Malvoz](https://github.com/Malvoz), [@fulldecent](https://github.com/fulldecent), [@saerdnaer](https://github.com/saerdnaer), [@MxDui](https://github.com/MxDui))
* Typos / Fixes in Docs, Samples, ... ([#7263](https://github.com/Leaflet/Leaflet/pull/7263), [#7284](https://github.com/Leaflet/Leaflet/pull/7284), [#7339](https://github.com/Leaflet/Leaflet/pull/7339), [#7349](https://github.com/Leaflet/Leaflet/pull/7349), [#7381](https://github.com/Leaflet/Leaflet/pull/7381), [#7371](https://github.com/Leaflet/Leaflet/pull/7371), [#7485](https://github.com/Leaflet/Leaflet/pull/7485), [#7380](https://github.com/Leaflet/Leaflet/pull/7380), [#7578](https://github.com/Leaflet/Leaflet/pull/7578), [#7758](https://github.com/Leaflet/Leaflet/pull/7758), [#7602](https://github.com/Leaflet/Leaflet/pull/7602), [#7857](https://github.com/Leaflet/Leaflet/pull/7857), [#7860](https://github.com/Leaflet/Leaflet/pull/7860), [#7336](https://github.com/Leaflet/Leaflet/pull/7336), [#7819](https://github.com/Leaflet/Leaflet/pull/7819) by [@timgates42](https://github.com/timgates42), [@IvanSanchez](https://github.com/IvanSanchez), [@ipovos](https://github.com/ipovos), [@elfalem](https://github.com/elfalem), [@BakuCity](https://github.com/BakuCity), [@simon04](https://github.com/simon04), [@user073](https://github.com/user073), [@Dev-Steven](https://github.com/Dev-Steven), [@vanillajonathan](https://github.com/vanillajonathan), [@aquelle-cp](https://github.com/aquelle-cp), [@matkoniecz](https://github.com/matkoniecz), [@Falke-Design](https://github.com/Falke-Design))
* Clarify `zoomend` event ([#7460](https://github.com/Leaflet/Leaflet/pull/7460) by [@xeruf](https://github.com/xeruf))
* Add `false` to `prefix` of `Control.Attribution` ([#7814](https://github.com/Leaflet/Leaflet/pull/7814) by [@Falke-Design](https://github.com/Falke-Design))
* `LayerGroup` inherit from `Interactive Layer` ([#7763](https://github.com/Leaflet/Leaflet/pull/7763) by [@johnd0e](https://github.com/johnd0e))
* Improve `Map.panInside` documentation ([#7397](https://github.com/Leaflet/Leaflet/pull/7397) by [@daverayment](https://github.com/daverayment))
* Update `Renderer` documentation to clarify `tolerance` option is for `Canvas` only ([#7515](https://github.com/Leaflet/Leaflet/pull/7515) by [@Hippl-Eric](https://github.com/Hippl-Eric))
* Add documentation for Event-Listener `propagate` argument ([#7103](https://github.com/Leaflet/Leaflet/pull/7103) by [@riffaud](https://github.com/riffaud))
* Fix an issue with top padding on non-API pages ([#8083](https://github.com/Leaflet/Leaflet/pull/8083), by [@wyankush](https://github.com/wyankush))
### 🔧 Workflow
* Split `plugins.md` into many files for easier maintenance ([#7805](https://github.com/Leaflet/Leaflet/pull/7805) by [@Falke-Design](https://github.com/Falke-Design))
* Add Bundlemon to watch bundle size ([#7934](https://github.com/Leaflet/Leaflet/pull/7934), [#7983](https://github.com/Leaflet/Leaflet/pull/7983), [#7905](https://github.com/Leaflet/Leaflet/pull/7905) by [@jonkoops](https://github.com/jonkoops))
* Add `npm run serve` to serve docs on localhost ([#7973](https://github.com/Leaflet/Leaflet/pull/7973) by [@Falke-Design](https://github.com/Falke-Design))
* Rename `master` branch to `main` ([#7921](https://github.com/Leaflet/Leaflet/pull/7921) by [@jonkoops](https://github.com/jonkoops))
* Upload files to AWS even if the file-size is the same ([#7853](https://github.com/Leaflet/Leaflet/pull/7853) by [@jonkoops](https://github.com/jonkoops))
* Remove `leaflet-include.js` from `debug` samples ([#7776](https://github.com/Leaflet/Leaflet/pull/7776) by [@Falke-Design](https://github.com/Falke-Design))
* Lint adjustments ([#7676](https://github.com/Leaflet/Leaflet/pull/7676), [#7743](https://github.com/Leaflet/Leaflet/pull/7743), [#7757](https://github.com/Leaflet/Leaflet/pull/7757) by [@jonkoops](https://github.com/jonkoops), [@mourner](https://github.com/mourner))
* Simplify release process ([#7711](https://github.com/Leaflet/Leaflet/pull/7711), [#7854](https://github.com/Leaflet/Leaflet/pull/7854), [#7727](https://github.com/Leaflet/Leaflet/pull/7727), [#8039](https://github.com/Leaflet/Leaflet/pull/8039) by [@mourner](https://github.com/mourner))
* Simplify docs update process on release ([#7730](https://github.com/Leaflet/Leaflet/pull/7730) by [@mourner](https://github.com/mourner))
* Update dependencies and add Dependabot config ([#7455](https://github.com/Leaflet/Leaflet/pull/7455), [#7653](https://github.com/Leaflet/Leaflet/pull/7653), [#7677](https://github.com/Leaflet/Leaflet/pull/7677), [#7725](https://github.com/Leaflet/Leaflet/pull/7725) by [@jonkoops](https://github.com/jonkoops))
* Split main workflow into multiple parallel jobs ([#7710](https://github.com/Leaflet/Leaflet/pull/7710) by [@jonkoops](https://github.com/jonkoops))
* Run CI on Github actions ([#7691](https://github.com/Leaflet/Leaflet/pull/7691), [#7654](https://github.com/Leaflet/Leaflet/pull/7654), [#7702](https://github.com/Leaflet/Leaflet/pull/7702) by [@jonkoops](https://github.com/jonkoops))
* Continue running tests even if one fails ([#7723](https://github.com/Leaflet/Leaflet/pull/7723) by [@jonkoops](https://github.com/jonkoops))
* Add https://github.com/Leaflet/Leaflet/labels/blocker check to release process ([#8019](https://github.com/Leaflet/Leaflet/pull/8019) by [@Malvoz](https://github.com/Malvoz))
* Remove all references of Bower ([#7831](https://github.com/Leaflet/Leaflet/pull/7831) by [@jonkoops](https://github.com/jonkoops))
* Add GitHub Actions dependency tracking with Dependabot (by [@nathannaveen](https://github.com/nathannaveen) in https://github.com/Leaflet/Leaflet/pull/8104)
### 🧪 Tests
* Improve unit tests organization ([#7852](https://github.com/Leaflet/Leaflet/pull/7852), by [@Falke-Design](https://github.com/Falke-Design))
* Run tests on `Internet Explorer 11` ([#7741](https://github.com/Leaflet/Leaflet/pull/7741) by [@jonkoops](https://github.com/jonkoops))
* Run tests on `FirefoxNoTouch` ([#7736](https://github.com/Leaflet/Leaflet/pull/7736), [#7742](https://github.com/Leaflet/Leaflet/pull/7742) by [@johnd0e](https://github.com/johnd0e))
* Drop `PhantomJS` from test suite ([#7660](https://github.com/Leaflet/Leaflet/pull/7660), [#7724](https://github.com/Leaflet/Leaflet/pull/7724) by [@jonkoops](https://github.com/jonkoops))
* Simplify `.near` and `.nearLatLng` usage ([#7820](https://github.com/Leaflet/Leaflet/pull/7820) by [@johnd0e](https://github.com/johnd0e))
* Enforce forbid-only rule in the continuous integration ([#7448](https://github.com/Leaflet/Leaflet/pull/7448) by [@johnd0e](https://github.com/johnd0e))
* Increase `captureTimeout` and `browserSocketTimeout` ([#7856](https://github.com/Leaflet/Leaflet/pull/7856) by [@Falke-Design](https://github.com/Falke-Design))
* Added / update tests ([#7790](https://github.com/Leaflet/Leaflet/pull/7790), [#7147](https://github.com/Leaflet/Leaflet/pull/7147), [#7721](https://github.com/Leaflet/Leaflet/pull/7721), [#7461](https://github.com/Leaflet/Leaflet/pull/7461), [#7126](https://github.com/Leaflet/Leaflet/pull/7126), [#7451](https://github.com/Leaflet/Leaflet/pull/7451), [#7450](https://github.com/Leaflet/Leaflet/pull/7450), [#7447](https://github.com/Leaflet/Leaflet/pull/7447), [#7438](https://github.com/Leaflet/Leaflet/pull/7438) by [@Falke-Design](https://github.com/Falke-Design), [@johnd0e](https://github.com/johnd0e), [@phloose](https://github.com/phloose))
* Added missing Test-Spec-Files to index.html ([#7845](https://github.com/Leaflet/Leaflet/pull/7845) by [@Falke-Design](https://github.com/Falke-Design))
* Cover `DomEvent` with unit tests (by [@stephenspol](https://github.com/stephenspol) in https://github.com/Leaflet/Leaflet/pull/8088)
* Cover `DomUtil` with unit tests ([#7547](https://github.com/Leaflet/Leaflet/pull/7547), by [@LGNorris](https://github.com/LGNorris))
## 1.7.1 (2020-09-04)
### Bug fixes
* Fix build toolchain to reflect uglifyjs upgrade from v2 to v3 (by [@ivansanchez](https://github.com/ivansanchez))
## 1.7.0 (2020-09-03)
### API changes
* `VideoOverlay` now can take a `muted` option ([#7071](https://github.com/Leaflet/Leaflet/pull/7071) by [@ronikar](https://github.com/ronikar))
* The `featureGroup` factory method now takes `options`, as the `FeatureGroup` constructor ([#7160](https://github.com/Leaflet/Leaflet/pull/7160) by [@frogcat](https://github.com/frogcat))
### Improvements
* Use passive event listeners for `touchstart`/`touchend` events ([#7008](https://github.com/Leaflet/Leaflet/pull/7008) by [@yneet](https://github.com/yneet))
* Better detection of `PointerEvents`-capable browsers in `L.Browser`, and related changes to `Tap`, `Drag`, and `TouchZoom` handlers ([#7010](https://github.com/Leaflet/Leaflet/pull/7010), ([#7033](https://github.com/Leaflet/Leaflet/pull/7041), ([#7036](https://github.com/Leaflet/Leaflet/pull/7036), ([#7068](https://github.com/Leaflet/Leaflet/pull/7068), ([#7195](https://github.com/Leaflet/Leaflet/pull/7195) by [@johnd0e](https://github.com/johnd0e))
* Add more browser profiles for the automated tests ([#7115](https://github.com/Leaflet/Leaflet/pull/7115) by [@johnd0e](https://github.com/johnd0e))
### Bug fixes
* Fix canvas renderer not clearing the canvas on some zoom transformations, was affecting opacity of items ([#6915](https://github.com/Leaflet/Leaflet/pull/6915) by [@chipta](https://github.com/chipta))
* Fix detection of passive events in `L.Browser` ([#6930](https://github.com/Leaflet/Leaflet/pull/6930) by [@Ivan-Perez](https://github.com/Ivan-Perez))
* Prefix MS-specific CSS style to prevent warnings (by [@ivansanchez](https://github.com/ivansanchez), kudos to [@zachricha](https://github.com/zachricha) for [#6960](https://github.com/Leaflet/Leaflet/pull/6960))
* Clean up `moveend` listener from `map.setMaxBounds` ([#6958](https://github.com/Leaflet/Leaflet/pull/6958) by [@simon04](https://github.com/simon04))
* Fix wrong scope of `bind` call in ESM environments ([#6970](https://github.com/Leaflet/Leaflet/pull/6970) by [@shintonik](https://github.com/shintonik))
* Check that `closePopup` exists before calling it automatically ([#6962](https://github.com/Leaflet/Leaflet/pull/6962) by [@pke](https://github.com/pke))
* Fix exception when calling `layerGroup.hasLayer()` with wrong `layerId` ([#6998](https://github.com/Leaflet/Leaflet/pull/6998) by [@johnd0e](https://github.com/johnd0e))
* Remove `click` filter targeting Android 4.x browsers ([#7013](https://github.com/Leaflet/Leaflet/pull/7013) by [@johnd0e](https://github.com/johnd0e))
* Fix touch zoom handler context ([#7036](https://github.com/Leaflet/Leaflet/pull/7036) by [@johnd0e](https://github.com/johnd0e))
* Tests for `Bounds.overlaps()` and `Bounds.intersects()` ([#7075](https://github.com/Leaflet/Leaflet/pull/7075) by [@mondeja](https://github.com/mondeja))
* Fix event propagation in a popup's container ([#7091](https://github.com/Leaflet/Leaflet/pull/7091) by [@johnd0e](https://github.com/johnd0e))
* Fix tile flickering when `maxNativeZoom === maxZoom` ([#7094](https://github.com/Leaflet/Leaflet/pull/7094) by [@johnd0e](https://github.com/johnd0e))
* Fix `GridLayer`'s zoom-level loading algorithm ([#7123](https://github.com/Leaflet/Leaflet/pull/7123) by [@johnd0e](https://github.com/johnd0e))
* Fix `tooltipAnchor` behavior for different tooltip directions ([#7155](https://github.com/Leaflet/Leaflet/pull/7155) by [@Istador](https://github.com/Istador))
### Docs & Web Site
* Updated examples to use non-legacy Mapbox tiles, and related changes ([#6905](https://github.com/Leaflet/Leaflet/pull/6905) by [@riastrad](https://github.com/riastrad)) ([#6922](https://github.com/Leaflet/Leaflet/pull/6922) by [@danswick](https://github.com/danswick)) ([#6995](https://github.com/Leaflet/Leaflet/pull/6995) by [@riastrad](https://github.com/riastrad))
* Fix documentation for `Polyline.addLatLng()` ([#6924](https://github.com/Leaflet/Leaflet/pull/6924) by [@life777](https://github.com/life777))
* CRS tutorial: change link for UQM tool to an archived version (by [@ivansanchez](https://github.com/ivansanchez))
* Fixed minor spelling errors in documentation ([#6850](https://github.com/Leaflet/Leaflet/pull/6850) by [@flopp](https://github.com/flopp)) ([#6944](https://github.com/Leaflet/Leaflet/pull/6944) by [@jieter](https://github.com/jieter))
* Fixed typo in panes documentation (by [#6939](https://github.com/Leaflet/Leaflet/pull/6939) by [@R4M80MrX](https://github.com/R4M80MrX))
* Fixed broken URL in quick-start example ([#6982](https://github.com/Leaflet/Leaflet/pull/6982) by [@ekbarber](https://github.com/ekbarber))
* Fix documentation for `map.setMaxBounds()` ([#7001](https://github.com/Leaflet/Leaflet/pull/7001) by [@johnd0e](https://github.com/johnd0e))
* Fix tilt code in handler tutorial ([#7014](https://github.com/Leaflet/Leaflet/pull/7014) by [@vncntcltt](https://github.com/vncntcltt))
* Fix instructions for using `jekyll` when building docs ([#7014](https://github.com/Leaflet/Leaflet/pull/7014) by [@vncntcltt](https://github.com/vncntcltt))
* Update WMS servers in WMS tutorial ([#7014](https://github.com/Leaflet/Leaflet/pull/7014) by [@vncntcltt](https://github.com/vncntcltt))
* Website constrast changes and minor cleanup (by [@mourner](https://github.com/mourner))
* Fixed typo in WMS example ([#7098](https://github.com/Leaflet/Leaflet/pull/7098) by [@andreasnuesslein](https://github.com/andreasnuesslein))
* Fix documentation for `divOverlay.getElement()` ([#7111](https://github.com/Leaflet/Leaflet/pull/7111) by [@mondeja](https://github.com/mondeja))
* Fix documentation for `Marker.shadowPane` ([#7135](https://github.com/Leaflet/Leaflet/pull/7135) by [@mi-v](https://github.com/mi-v))
* Update URL about NPM in developer docs ([#7161](https://github.com/Leaflet/Leaflet/pull/7161) by [@iamtekson](https://github.com/iamtekson))
* Fix documentation for `Layer.removeFrom()` regarding `LayerGroup`s (by [@ivansanchez](https://github.com/ivansanchez))
* Fix documentation for `LatLngBounds.overlaps()` [#7194](https://github.com/Leaflet/Leaflet/pull/7194) by [@DerZade](https://github.com/DerZade))
## 1.6.0 (2019-11-17)
### API changes
* `GeoJSON.resetStyle` - allow invocation without an argument ([#6663](https://github.com/Leaflet/Leaflet/pull/6663) by [joukewitteveen](https://github.com/joukewitteveen))
* Add new `markersInheritOptions` option to `L.GeoJSON` ([#6866](https://github.com/Leaflet/Leaflet/pull/6866) by [ghybs](https://github.com/ghybs))
### Improvements
* Use passive event listeners ([#6694](https://github.com/Leaflet/Leaflet/pull/6694) by [danielkorte](https://github.com/danielkorte))
* Add `oldLatLng` coordinates to `L.CircleMarker` `move` event ([#6719](https://github.com/Leaflet/Leaflet/pull/6719) by [errnesto](https://github.com/errnesto))
* Add tests ([#6839](https://github.com/Leaflet/Leaflet/pull/6839), [#6841](https://github.com/Leaflet/Leaflet/pull/6841) by [ghybs](https://github.com/ghybs))
* Add test case to ensure scientific notation is formatted correctly ([#6877](https://github.com/Leaflet/Leaflet/pull/6877) by [desean1625](https://github.com/desean1625))
### Bug fixes
* Fix performance issue with `L.Util.formatNum` ([#6668](https://github.com/Leaflet/Leaflet/pull/6668) by [cherniavskii](https://github.com/cherniavskii))
* Respect `className` option in `SVGOverlay` and `VideoOverlay` ([#6679](https://github.com/Leaflet/Leaflet/pull/6679) by [IvanSanchez](https://github.com/IvanSanchez))
* Cancel the canvas `mousehover` throttle on `mouseout` ([#6749](https://github.com/Leaflet/Leaflet/pull/6749) by [IvanSanchez](https://github.com/IvanSanchez))
* Check for style being passed in `L.Path.setStyle` ([#6728](https://github.com/Leaflet/Leaflet/pull/6728) by [TheRealTorreySmith](https://github.com/TheRealTorreySmith))
* Fix `dblclick` event when both Pointer Events and Touch Events are available ([#6855](https://github.com/Leaflet/Leaflet/pull/6855) by [filcab](https://github.com/filcab))
* Properly unbind animation proxy events when removing map ([#6867](https://github.com/Leaflet/Leaflet/pull/6867) by [ghybs](https://github.com/ghybs))
* Fix race condition in `Marker` when icon is not present ([#6794](https://github.com/Leaflet/Leaflet/pull/6794) by [BenTalagan](https://github.com/BenTalagan))
### Docs & Web Site
* Update SvgOverlay code example ([#6658](https://github.com/Leaflet/Leaflet/pull/6658) by [cherniavskii](https://github.com/cherniavskii))
* Fix mobile locate accuracy snippet ([#6693](https://github.com/Leaflet/Leaflet/pull/6693) by [ghybs](https://github.com/ghybs))
* Fix broken accordions ([#6770](https://github.com/Leaflet/Leaflet/pull/6770) by [mbachner](https://github.com/mbachner))
* Fix misleading `L.Marker` docs sections ([#6871](https://github.com/Leaflet/Leaflet/pull/6871) by [chloe-mc](https://github.com/chloe-mc))
## 1.5.1 (2019-05-08)
* Fix module export regression ([#6647](https://github.com/Leaflet/Leaflet/pull/6647) by [cherniavskii](https://github.com/cherniavskii))
## 1.5.0 (2019-05-08)
### API changes
* Add `keepAspectRatio` option to `VideoOverlay` ([#6038](https://github.com/Leaflet/Leaflet/pull/6038) by [ronikar](https://github.com/ronikar))
* Add support for `keydown` and `keyup` events to map ([#6421](https://github.com/Leaflet/Leaflet/pull/6421) by [OrBin](https://github.com/OrBin))
* Add `shadowPane` option to `Marker` ([#6460](https://github.com/Leaflet/Leaflet/pull/6460) by [alexfig](https://github.com/alexfig))
* Add `L.SVGOverlay` class ([#6517](https://github.com/Leaflet/Leaflet/pull/6517) by [Zsavajji](https://github.com/Zsavajji))
* Add `getIcon` method to `Marker` ([#6457](https://github.com/Leaflet/Leaflet/pull/6457) by [OsamaShabrez](https://github.com/OsamaShabrez))
### Improvements
* Update attribution link to HTTPS ([#6593](https://github.com/Leaflet/Leaflet/pull/6593) by [WillSelway](https://github.com/WillSelway))
* Make `DivIcon` `html` option accept Element ([#6571](https://github.com/Leaflet/Leaflet/pull/6571) by [oscar-sensornet](https://github.com/oscar-sensornet))
* Microoptimize `GeoJSON._setLayerStyle` ([#6616](https://github.com/Leaflet/Leaflet/pull/6616) by [johnd0e](https://github.com/johnd0e))
* Reuse `openPopup` and `openTooltip` code ([#6613](https://github.com/Leaflet/Leaflet/pull/6613) by [johnd0e](https://github.com/johnd0e))
### Bug fixes
* Fix `Control.Layers` radio buttons when using multiple maps ([#6483](https://github.com/Leaflet/Leaflet/pull/6483) by [jjimenezshaw](https://github.com/jjimenezshaw))
* Explicitly remove controls from the map on map unload ([#6488](https://github.com/Leaflet/Leaflet/pull/6488) by [IvanSanchez](https://github.com/IvanSanchez))
* Fixed a bug where mouseover on Canvas-rendered polylines acted inconsistently ([#6516](https://github.com/Leaflet/Leaflet/pull/6516) by [IvanSanchez](https://github.com/IvanSanchez))
* Prevent tiles highlighting in IE11 ([#6570](https://github.com/Leaflet/Leaflet/pull/6570) by [VictorVelarde](https://github.com/VictorVelarde))
* Fix `L.Util.formatNum` rounding precision ([#6587](https://github.com/Leaflet/Leaflet/pull/6587) by [inkstak](https://github.com/inkstak))
* Fix crash, when setting opacity on `Marker` without icon ([#6610](https://github.com/Leaflet/Leaflet/pull/6610) by [charlie67](https://github.com/charlie67))
* Make synthetic `dblclick` event relate to left mouse button ([#6628](https://github.com/Leaflet/Leaflet/pull/6628) by [IvanSanchez](https://github.com/IvanSanchez))
### Docs & Web Site
* Minor spelling corrections in Docs/Tutorials ([#6464](https://github.com/Leaflet/Leaflet/pull/6464) by [ilyankou](https://github.com/ilyankou))
* Document `toGeoJSON`'s `precision` argument ([#6375](https://github.com/Leaflet/Leaflet/pull/6375) by [rkatka](https://github.com/rkatka))
* Add background color CSS declaration ([#6614](https://github.com/Leaflet/Leaflet/pull/6614) by [milesflo](https://github.com/milesflo))
* Clarify `Events.off` method docs ([#6619](https://github.com/Leaflet/Leaflet/pull/6619) by [lancebendo](https://github.com/lancebendo))
* Fix `Layer` example ([#6636](https://github.com/Leaflet/Leaflet/pull/6636) by [billygarrison](https://github.com/billygarrison))
### Development workflow
* Update dev dependencies & remove coverage scripts ([#6635](https://github.com/Leaflet/Leaflet/pull/6635) by [mourner](https://github.com/mourner))
## 1.4.0 (2018-12-30)
### API changes
* Add new `Map.panInside` method ([#6054](https://github.com/Leaflet/Leaflet/pull/6054) by [daverayment](https://github.com/daverayment))
### Improvements
* Remove unused `_drawnLayers` object ([#6324](https://github.com/Leaflet/Leaflet/pull/6324) by [ud09](https://github.com/ud09))
* Avoid unnecessary redrawing in `TileLayer.setUrl()` when URL does not change ([#6313](https://github.com/Leaflet/Leaflet/pull/6313) by [JackNeus](https://github.com/JackNeus))
* Use `section` instead of `form` in layers control ([#6380](https://github.com/Leaflet/Leaflet/pull/6380) by [hundekoerper](https://github.com/hundekoerper))
* Add IE11 linked SVG elements support to `DomUtil.getClass` function ([#6366](https://github.com/Leaflet/Leaflet/pull/6366) by [Schleuse](https://github.com/Schleuse))
### Bug fixes
* Set internal flags at beginning of map initialization ([#6362](https://github.com/Leaflet/Leaflet/pull/6362) by [ghybs](https://github.com/ghybs))
* Guard against layers no longer attached to a map in `bringToFront/Back()` ([#6389](https://github.com/Leaflet/Leaflet/pull/6389) by [perliedman](https://github.com/perliedman))
* Fix `autoPan` option when popup content gets updated while a panning animation is running ([#6365](https://github.com/Leaflet/Leaflet/pull/6365) by [Schleuse](https://github.com/Schleuse))
* Ignore dash arrays with non-numeric entries in canvas ([#6387](https://github.com/Leaflet/Leaflet/pull/6387) by [perliedman](https://github.com/perliedman))
### Docs & Web Site
* Remove additional `<tr>` ([#6334](https://github.com/Leaflet/Leaflet/pull/6334) by [mgred](https://github.com/mgred))
* Fix command to run test with karma options ([#6363](https://github.com/Leaflet/Leaflet/pull/6363) by [ghybs](https://github.com/ghybs))
* Add OSM logo to "Trusted by the best" section ([#6360](https://github.com/Leaflet/Leaflet/pull/6360) by [astv99](https://github.com/astv99))
* Fix typos ([#6398](https://github.com/Leaflet/Leaflet/pull/6398) by [HarryMaher](https://github.com/HarryMaher))
* Make it clear zoomanim is not always fired on every frame ([#6388](https://github.com/Leaflet/Leaflet/pull/6388) by [perliedman](https://github.com/perliedman))
* Add copyright references to code examples ([#6439](https://github.com/Leaflet/Leaflet/pull/6439) by [jbelien](https://github.com/jbelien))
* Fix insecure content error ([#6442](https://github.com/Leaflet/Leaflet/pull/6442) by [tambry](https://github.com/tambry))
* Remove incorrect Earth GIF ([#6326](https://github.com/Leaflet/Leaflet/pull/6326) by [Vincentdecursay](https://github.com/Vincentdecursay))
* Fix duplicated sentence in SVG section ([#6448](https://github.com/Leaflet/Leaflet/pull/6448) by [patrickdemers6](https://github.com/patrickdemers6))
## 1.3.4 (2018-08-21)
### Improvements
* Reset max-width and max-height styles for tiles in custom panes ([#6255](https://github.com/Leaflet/Leaflet/issues/6255) by [jerekshoe](https://github.com/jerekshoe))
* Add unprefixed `cursor: grab` style ([#6281](https://github.com/Leaflet/Leaflet/issues/6281) by [Malvoz](https://github.com/Malvoz))
* Remove legacy prefixed styles, add unprefixed styles ([#6282](https://github.com/Leaflet/Leaflet/issues/6282) by [Malvoz](https://github.com/Malvoz))
### Bug fixes
* Move set/getAttribute('src') calls from `GridLayer` into `TileLayer` ([#6264](https://github.com/Leaflet/Leaflet/issues/6264) by [IvanSanchez](https://github.com/IvanSanchez))
* Support comma- or space-separated strings in `Canvas` `dashArray` option ([#6277](https://github.com/Leaflet/Leaflet/issues/6277) by [IvanSanchez](https://github.com/IvanSanchez))
* Remove trailing commas to avoid crashes in some IE browsers ([#6279](https://github.com/Leaflet/Leaflet/issues/6279) by [helbling](https://github.com/helbling))
* Fixed capitalization of `webkitTransform` property, which broke transformations on certain Webkit browsers ([#6290](https://github.com/Leaflet/Leaflet/issues/6290) by [tuckergordon](https://github.com/tuckergordon))
### Docs & Web Site
* Document `zoomControl` map option ([#6254](https://github.com/Leaflet/Leaflet/issues/6254) by [IvanSanchez](https://github.com/IvanSanchez))
* Explicitly note attribution obligation ([#6261](https://github.com/Leaflet/Leaflet/issues/6261) by [pietervdvn](https://github.com/pietervdvn))
* Minor fixes of `ImageOverlay` docs ([#6266](https://github.com/Leaflet/Leaflet/issues/6266) by [jgravois](https://github.com/jgravois))
* Add Edge to the list of supported browsers ([#6270](https://github.com/Leaflet/Leaflet/issues/6270) by [matkoniecz](https://github.com/matkoniecz))
* Remove references to Leaflet UserVoice page ([#6291](https://github.com/Leaflet/Leaflet/issues/6291) by [zstadler](https://github.com/zstadler))
* Reword docstrings for draggable markers ([#6296](https://github.com/Leaflet/Leaflet/issues/6296) by [IvanSanchez](https://github.com/IvanSanchez))
## 1.3.3 (2018-07-18)
### Bug fixes
* Remove `module` field from `package.json` ([#6239](https://github.com/Leaflet/Leaflet/issues/6239))
## 1.3.2 (2018-07-17)
### Improvements
* Add `use-credentials` CORS option to `ImageOverlay` and `TileLayer` ([#6016](https://github.com/Leaflet/Leaflet/issues/6016) by [caleblogan](https://github.com/caleblogan)) + unit tests ([#6022](https://github.com/Leaflet/Leaflet/issues/6022) and [#6027](https://github.com/Leaflet/Leaflet/issues/6027) by [ghybs](https://github.com/ghybs))
* Clean up references to global `L` in source code ([#6047](https://github.com/Leaflet/Leaflet/issues/6047) and [#6048](https://github.com/Leaflet/Leaflet/issues/6048) by [ghybs](https://github.com/ghybs))
* Allow reset of `Canvas` `dashArray` option + support array type ([#6200](https://github.com/Leaflet/Leaflet/issues/6200) by [McBen](https://github.com/McBen))
### Bug fixes
* Respect the `preferCanvas` option in all panes ([#6019](https://github.com/Leaflet/Leaflet/issues/6019) by [mjumbewu](https://github.com/mjumbewu))
* Do not fire `tileload` event if tile has empty `src` ([#6025](https://github.com/Leaflet/Leaflet/issues/6025) by [cherniavskii](https://github.com/cherniavskii))
* Fix race condition when removing canvas before it has rendered ([#6033](https://github.com/Leaflet/Leaflet/issues/6033) by [louMoxy](https://github.com/louMoxy))
* Fix memory leak in canvas renderer ([#6117](https://github.com/Leaflet/Leaflet/issues/6117) by [aj8k](https://github.com/aj8k))
* Fix dragging for CSS scaled map ([#6055](https://github.com/Leaflet/Leaflet/issues/6055) by [ghybs](https://github.com/ghybs))
* Handle `Polygon`s with empty array of `LatLng`s ([#6119](https://github.com/Leaflet/Leaflet/issues/6119) by [BakuCity](https://github.com/BakuCity))
* Fix view bounds calculation in geolocation event handler ([#6140](https://github.com/Leaflet/Leaflet/issues/6140) by [wladich](https://github.com/wladich))
* Fix error removing map and resizing window at the same time ([#6160](https://github.com/Leaflet/Leaflet/issues/6160) by [danzel](https://github.com/danzel))
* Stop pan key event when pan animation is in progress ([#6231](https://github.com/Leaflet/Leaflet/issues/6231) by [cherniavskii](https://github.com/cherniavskii))
### Docs & Web Site
* Use more explicit tiles in `Map panes` example ([#6018](https://github.com/Leaflet/Leaflet/issues/6018) by [ghybs](https://github.com/ghybs))
* Document parameter of `closestLayerPoint` method ([#6060](https://github.com/Leaflet/Leaflet/issues/6060) by [mattdsteele](https://github.com/mattdsteele))
* Update year ranges ([#6076](https://github.com/Leaflet/Leaflet/issues/6076) by [BakuCity](https://github.com/BakuCity))
* Use https everywhere for OSM links ([#6082](https://github.com/Leaflet/Leaflet/issues/6082) by [rzoller](https://github.com/rzoller))
* Fix typo in `VideoOverlay.js` example docs ([#6090](https://github.com/Leaflet/Leaflet/issues/6090) by [zhuang-hao-ming](https://github.com/zhuang-hao-ming))
* Fix typo in `LatLng` docstring ([#6138](https://github.com/Leaflet/Leaflet/issues/6138) by [jieter](https://github.com/jieter))
* Use https everywhere and get rid of mixed content warnings ([#6151](https://github.com/Leaflet/Leaflet/issues/6151) by [theel0ja](https://github.com/theel0ja))
* More accurate `Browser.retina` explanation ([#6203](https://github.com/Leaflet/Leaflet/issues/6203) by [JLuc](https://github.com/JLuc))
* Use link to official RFC 7946 GeoJSON spec ([#6211](https://github.com/Leaflet/Leaflet/issues/6211) by [ghybs](https://github.com/ghybs))
### ES6 / Rollup
* Add ES module build ([#6021](https://github.com/Leaflet/Leaflet/issues/6021) by [Rich-Harris](https://github.com/Rich-Harris))
### Development workflow
* Move external dependencies to `node_modules` ([#6028](https://github.com/Leaflet/Leaflet/issues/6028) by [cherniavskii](https://github.com/cherniavskii))
* Remove `jake` dependency and use NPM scripts instead ([#5828](https://github.com/Leaflet/Leaflet/issues/5828) by [cherniavskii](https://github.com/cherniavskii))
* Update `ssri` dependency to `5.2.2` ([#6086](https://github.com/Leaflet/Leaflet/issues/6086) by [cherniavskii](https://github.com/cherniavskii))
* Set div width for `getBoundsZoom` parameter `inside` ([#6192](https://github.com/Leaflet/Leaflet/issues/6192) by [ghybs](https://github.com/ghybs))
* Fix unit tests for graphical browsers ([#6199](https://github.com/Leaflet/Leaflet/issues/6199) and [#6202](https://github.com/Leaflet/Leaflet/issues/6202) by [ghybs](https://github.com/ghybs))
* Run tests in Firefox graphical browser as part of CI ([#5831](https://github.com/Leaflet/Leaflet/issues/5831) by [cherniavskii](https://github.com/cherniavskii))
## 1.3.1 (2018-01-18)
### Bug fixes
* Fix `L.TileLayer`regression, which caused incorrect tile URLs ([#6006](https://github.com/Leaflet/Leaflet/issues/6006) by [ghybs](https://github.com/ghybs))
## 1.3.0 (2018-01-15)
### API changes
* Add `tolerance` option to `L.Renderer` instead of hardcoded tolerance for touch devices ([#5922](https://github.com/Leaflet/Leaflet/issues/5922) by [Muscot](https://github.com/Muscot)).
### Improvements
* Use more stable form of Haversine formula ([#5935](https://github.com/Leaflet/Leaflet/issues/5935) by [jussimattas](https://github.com/jussimattas))
* Add `autoPan` option to `L.Marker` ([#5651](https://github.com/Leaflet/Leaflet/issues/5651) by [alenaksu](https://github.com/alenaksu))
* Cancel http requests for outdated tiles ([#5615](https://github.com/Leaflet/Leaflet/issues/5615) by [jbccollins](https://github.com/jbccollins))
* Add `closeOnEscapeKey` option to `L.Popup` ([#5730](https://github.com/Leaflet/Leaflet/issues/5730) by [Mickyfen17](https://github.com/Mickyfen17))
* Add `tileerror` tests to `L.GridLayer` spec ([#5805](https://github.com/Leaflet/Leaflet/issues/5805) by [msiadak](https://github.com/msiadak))
* Use `eachLayer` method for iterations over layers in `L.LayerGroup` ([#5809](https://github.com/Leaflet/Leaflet/issues/5809) by [cherniavskii](https://github.com/cherniavskii))
* Change `Util.formatNum` default to 6 decimals ([#5492](https://github.com/Leaflet/Leaflet/issues/5492) by [fminuti](https://github.com/fminuti))
* Fire `zoomlevelschange` event when calling `setMinZoom` & `setMaxZoom` ([#5230](https://github.com/Leaflet/Leaflet/issues/5230) by [mynameisstephen](https://github.com/mynameisstephen))
* Use zoom parameter if passed to `L.TileLayer`'s `getTileUrl` method ([#5822](https://github.com/Leaflet/Leaflet/issues/5822) by [broncha](https://github.com/broncha))
* Round circle radius in renderer instead of layer ([#5859](https://github.com/Leaflet/Leaflet/issues/5859) by [perliedman](https://github.com/perliedman))
* Make `L.LayerGroup` accept options ([#5792](https://github.com/Leaflet/Leaflet/issues/5792) by [iH8](https://github.com/iH8))
* Round pan offset towards zero ([#5858](https://github.com/Leaflet/Leaflet/issues/5858) by [perliedman](https://github.com/perliedman))
* Improve heuristic for what event targets are considered markers ([#5885](https://github.com/Leaflet/Leaflet/issues/5885) by [perliedman](https://github.com/perliedman))
* Add `typeof` check to `checkDeprecatedMixinEvents` ([#5901](https://github.com/Leaflet/Leaflet/issues/5901) by [murb](https://github.com/murb))
* Optimize images ([#5936](https://github.com/Leaflet/Leaflet/issues/5936) by [grischard](https://github.com/grischard))
* Add `addTo` static function to `L.Handler` class ([#5930](https://github.com/Leaflet/Leaflet/issues/5930) by [cherniavskii](https://github.com/cherniavskii))
* `L.ImageOverlay` accepts `ImageElement` ([#5948](https://github.com/Leaflet/Leaflet/issues/5948) by [ronikar](https://github.com/ronikar))
### Bug fixes
* Fix adding CSS classes in `L.VideoOverlay`, which caused map crash in IE8 ([#5731](https://github.com/Leaflet/Leaflet/issues/5731) by [Dzwiedzminator](https://github.com/Dzwiedzminator) and [#5785](https://github.com/Leaflet/Leaflet/issues/5785) by [cherniavskii](https://github.com/cherniavskii))
* No inertia if drag is stopped before ending the drag ([#5690](https://github.com/Leaflet/Leaflet/issues/5690) by [perliedman](https://github.com/perliedman))
* Remove tiles from the `_tiles` cache when they're aborted ([#5634](https://github.com/Leaflet/Leaflet/issues/5634) by [oliverheilig](https://github.com/oliverheilig))
* Use same condition for adding as well as removing double tap listener ([#5598](https://github.com/Leaflet/Leaflet/issues/5598) by [perliedman](https://github.com/perliedman))
* Correctly wrap tile coordinate bounds ([#5715](https://github.com/Leaflet/Leaflet/issues/5715) by [perliedman](https://github.com/perliedman))
* Fix `L.TileLayer` infinite error loop ([#5783](https://github.com/Leaflet/Leaflet/issues/5783) by [cherniavskii](https://github.com/cherniavskii))
* Fix map not rendering when images have max-height ([#5778](https://github.com/Leaflet/Leaflet/issues/5778) by [idanen](https://github.com/idanen))
* Add defensive check for `this._map` in `L.Marker`'s `update` method ([#5736](https://github.com/Leaflet/Leaflet/issues/5736) by [dnepromell](https://github.com/dnepromell))
* Fix zoom when map container is scaled ([#5794](https://github.com/Leaflet/Leaflet/issues/5794) by [cherniavskii](https://github.com/cherniavskii))
* Update DomPointer.js to revert typo ([#5817](https://github.com/Leaflet/Leaflet/issues/5817) by [daverayment](https://github.com/daverayment))
* Fix `L.ImageOverlay` `className` option ([#5830](https://github.com/Leaflet/Leaflet/issues/5830) by [cherniavskii](https://github.com/cherniavskii))
* Fix `L.TileLayer.WMS` when using Polar stereographic ([#5618](https://github.com/Leaflet/Leaflet/issues/5618) by [scaddenp](https://github.com/scaddenp))
* Stop animation before removing map ([#5876](https://github.com/Leaflet/Leaflet/issues/5876) by [dnepromell](https://github.com/dnepromell) and [#5918](https://github.com/Leaflet/Leaflet/issues/5918) by [aaronplanell](https://github.com/aaronplanell))
* Stop locate before removing map ([#5893](https://github.com/Leaflet/Leaflet/issues/5893) by [ghybs](https://github.com/ghybs))
* Reset `GridLayer`'s `_tileZoom` to `undefined` instead of `null` ([#5888](https://github.com/Leaflet/Leaflet/issues/5888) by [iH8](https://github.com/iH8))
* Fix `L.Map`'s `map.flyTo` method to respect `noMoveStart` option ([#5923](https://github.com/Leaflet/Leaflet/issues/5923) by [iPrytz](https://github.com/iPrytz))
* Fix map pan when `touchZoom` is disabled ([#5952](https://github.com/Leaflet/Leaflet/issues/5952) by [cherniavskii](https://github.com/cherniavskii))
* Fix `L.SVG` to reset size on remove from map ([#5966](https://github.com/Leaflet/Leaflet/issues/5966) by [ghybs](https://github.com/ghybs))
* Fix `L.Marker` to set `alt` attribute for img-based Icons only ([#5979](https://github.com/Leaflet/Leaflet/issues/5979) by [msiadak](https://github.com/msiadak))
### Docs & Web Site
* Add documentation for `PolyLine.closestLayerPoint` ([#5701](https://github.com/Leaflet/Leaflet/issues/5701) by [perliedman](https://github.com/perliedman))
* Replace urls for samples in wms documentation ([#5712](https://github.com/Leaflet/Leaflet/issues/5712) by [jjimenezshaw](https://github.com/jjimenezshaw))
* Add `DomEvent.stop` event type to docs ([#5733](https://github.com/Leaflet/Leaflet/issues/5733) by [theashyster](https://github.com/theashyster))
* Update links to GeoJSON spec in GeoJSON examples ([#5725](https://github.com/Leaflet/Leaflet/issues/5725) by [astridx](https://github.com/astridx))
* Improve docs for `L.LatLngBounds` `pad` method ([#5748](https://github.com/Leaflet/Leaflet/issues/5748) by [CalvinWilliams1012](https://github.com/CalvinWilliams1012))
* Improve Zoom-levels documentation to reflect function properly ([#5769](https://github.com/Leaflet/Leaflet/issues/5769) by [CalvinWilliams1012](https://github.com/CalvinWilliams1012))
* Fix map's height and width in examples to make attributions visible on mobile ([#5772](https://github.com/Leaflet/Leaflet/issues/5772) by [CalvinWilliams1012](https://github.com/CalvinWilliams1012) and [#5819](https://github.com/Leaflet/Leaflet/issues/5819) by [cherniavskii](https://github.com/cherniavskii))
* Fix `L.FeatureGroup` `bringToBack` method docs ([#5788](https://github.com/Leaflet/Leaflet/issues/5788) by [fpopa](https://github.com/fpopa))
* Fix `L.LatLng` `distanceTo` method description ([#5791](https://github.com/Leaflet/Leaflet/issues/5791) by [loisgh](https://github.com/loisgh))
* Fix docs redirects to latest version reference ([#5824](https://github.com/Leaflet/Leaflet/issues/5824) by [cherniavskii](https://github.com/cherniavskii))
* Add CDN alternatives ([#5837](https://github.com/Leaflet/Leaflet/issues/5837) by [ghybs](https://github.com/ghybs))
* Update docs for `L.DomEvent.off()` ([#5855](https://github.com/Leaflet/Leaflet/issues/5855) by [77ganesh](https://github.com/77ganesh) and [#5976](https://github.com/Leaflet/Leaflet/issues/5976) by [kring](https://github.com/kring))
* Move `L.SVG` factory docs to appropriate place ([#5864](https://github.com/Leaflet/Leaflet/issues/5864) by [cherniavskii](https://github.com/cherniavskii))
* New Leaflet playgrounds on Plunker, Codepen and JSFiddle ([#5868](https://github.com/Leaflet/Leaflet/issues/5868) by [iH8](https://github.com/iH8))
* Clarify that any option keys can be used ([#5877](https://github.com/Leaflet/Leaflet/issues/5877) by [perliedman](https://github.com/perliedman))
* Add notes about some classes not inheriting Class ([#5878](https://github.com/Leaflet/Leaflet/issues/5878) by [perliedman](https://github.com/perliedman))
* Clean up and document event propagation properties ([#5880](https://github.com/Leaflet/Leaflet/issues/5880) by [perliedman](https://github.com/perliedman))
* Update building instructions ([#5911](https://github.com/Leaflet/Leaflet/issues/5911) by [shadeland](https://github.com/shadeland))
* Document `mouseup` event for interactive `L.Layer` ([#5941](https://github.com/Leaflet/Leaflet/issues/5941) by [germansokolov13](https://github.com/germansokolov13))
* Document `L.Icon`'s `tooltipAnchor` option, update `tooltipAnchor` and `popupAnchor` default values docs ([#5989](https://github.com/Leaflet/Leaflet/issues/5989) by [cherniavskii](https://github.com/cherniavskii))
### ES6 / Rollup
* Tweak `legacy` option in rollup config - now Leaflet works in IE again ([#5929](https://github.com/Leaflet/Leaflet/issues/5929) by [IvanSanchez](https://github.com/IvanSanchez))
* Remove warning alert in watch bundle ([#5714](https://github.com/Leaflet/Leaflet/issues/5714) by [perliedman](https://github.com/perliedman))
* New rollup config signature ([#5812](https://github.com/Leaflet/Leaflet/issues/5812) by [iH8](https://github.com/iH8))
## 1.2.0 (2017-08-08)
### API Changes
* Make `L.LineUtil._flat` public as `L.LineUtil.isFlat` ([#5667](https://github.com/Leaflet/Leaflet/issues/5667) by [yohanboniface](https://github.com/yohanboniface))
* `L.ImageOverlay`'s `setBounds` method accepts an array of points, not only a `L.LatLngBounds` instance ([#5687](https://github.com/Leaflet/Leaflet/pull/5687) by [ronikar](https://github.com/ronikar)
### Improvements
* `VideoOverlay` accepts `VideoElement` ([#5661](https://github.com/Leaflet/Leaflet/issues/5661) by [eavidan-0](https://github.com/eavidan-0))
* Documentation improvements by [ghybs](https://github.com/ghybs), [thachhoang](https://github.com/thachhoang), [colbin8r](https://github.com/colbin8r), [Timmmm](https://github.com/Timmmm), [cyrilf](https://github.com/cyrilf) and [JonDum](https://github.com/JonDum)
* Filter published files on npm ([#5626](https://github.com/Leaflet/Leaflet/issues/5626) by [TrySound](https://github.com/TrySound)), [e7o-de](https://github.com/e7o-de)
* Throw an error if trying to use `addLayer` with something that is not a `Layer` ([#5689](https://github.com/Leaflet/Leaflet/pull/5689) by [perliedman](https://github.com/perliedman))
* Ensure copyright banner is kept in the minified `leaflet.js` ([#5682](https://github.com/Leaflet/Leaflet/pull/5682) by [jgravois](https://github.com/jgravois))
### Bug fixes
* Fix non-extendable objects regression of 1.1.0 ([#5658](https://github.com/Leaflet/Leaflet/pull/5658) by [mourner](https://github.com/mourner))
* `Control.Layers` properly handles adding the same layer multiple times, as well as cleanup ([#5672](https://github.com/Leaflet/Leaflet/issues/5672), [#5684](https://github.com/Leaflet/Leaflet/issues/5684) and [#5673](https://github.com/Leaflet/Leaflet/issues/5673) by [jjimenezshaw](https://github.com/jjimenezshaw))
* Make `L.Draggable._dragging` public again ([#5659](https://github.com/Leaflet/Leaflet/issues/5659) by [yohanboniface](https://github.com/yohanboniface))
* `DomEvent.off` returns `this` ([#5601](https://github.com/Leaflet/Leaflet/issues/5601) by [jelhan](https://github.com/jelhan))
* Don't use functions from the global `L` internally ([#5596](https://github.com/Leaflet/Leaflet/issues/5596) by [atd-schubert](https://github.com/atd-schubert))
* Return `this` instead of `addOne` in `DomEvent.disableScrollPropagation` ([#5595](https://github.com/Leaflet/Leaflet/issues/5595) by [Eschon](https://github.com/Eschon))
## 1.1.0 (2017-06-27)
### API changes
* Add deprecation notice for `L.Mixin.Events`, fixes [#5358](https://github.com/Leaflet/Leaflet/issues/5358) ([#5365](https://github.com/Leaflet/Leaflet/issues/5365)) (by [perliedman](https://github.com/perliedman))
* Turn `nonBubblingEvents` into a documented boolean option ([#4883](https://github.com/Leaflet/Leaflet/issues/4883) by [IvanSanchez](https://github.com/IvanSanchez))
* Add `L.transformation` factory, allow creation from array ([#5282](https://github.com/Leaflet/Leaflet/issues/5282) by [anetz89](https://github.com/anetz89))
* `toGeoJSON` methods now default to a precision of six decimals (as recommended in the [GeoJSON spec](https://tools.ietf.org/html/rfc7946#section-11.2)), precision is settable through a method parameter ([#5544](https://github.com/Leaflet/Leaflet/pull/5544) by [mattgrande](https://github.com/mattgrande))
### Docs & Web Site
* `reference.html` now always points to latest stable docs ([#5490](https://github.com/Leaflet/Leaflet/issues/5490) by [IvanSanchez](https://github.com/IvanSanchez), [#5493](https://github.com/Leaflet/Leaflet/issues/5493) by [alyhegazy](https://github.com/alyhegazy))
* Subresource integrity information and scripts ([#5468](https://github.com/Leaflet/Leaflet/issues/5468) by [IvanSanchez](https://github.com/IvanSanchez))
* New tutorial on zooming (by [IvanSanchez](https://github.com/IvanSanchez), [#5007](https://github.com/Leaflet/Leaflet/issues/5007))
* Minor documentation improvements by [perliedman](https://github.com/perliedman), [veltman](https://github.com/veltman), [FDMS](https://github.com/FDMS), [ghybs](https://github.com/ghybs), [RichardLitt](https://github.com/RichardLitt), [gatsbimantico](https://github.com/gatsbimantico), [daturkel](https://github.com/daturkel), [jgravois](https://github.com/jgravois), [geografa](https://github.com/geografa), [BjoernSchilberg](https://github.com/BjoernSchilberg), [IvanSanchez](https://github.com/IvanSanchez), [bozdoz](https://github.com/bozdoz), [zemadz](https://github.com/zemadz), [danzel](https://github.com/danzel), [jieter](https://github.com/jieter), [massic80](https://github.com/massic80), [jjimenezshaw](https://github.com/jjimenezshaw), [hnrchrdl](https://github.com/hnrchrdl) and [RayBB](https://github.com/RayBB)
### ES6 / Rollup
* ES6 modules & Rollup ([#4989](https://github.com/Leaflet/Leaflet/issues/4989)) (by [mourner](https://github.com/mourner) and [IvanSanchez](https://github.com/IvanSanchez))
* Additional fixes, testing and cleanup of ES6 code by [thachhoang](https://github.com/thachhoang), [danzel](https://github.com/danzel), [jkuebart](https://github.com/jkuebart), [simon04](https://github.com/simon04), [perliedman](https://github.com/perliedman), [luiscamachopt](https://github.com/luiscamachopt) and [Trufi](https://github.com/Trufi) ([#5373](https://github.com/Leaflet/Leaflet/issues/5373), [#5417](https://github.com/Leaflet/Leaflet/issues/5417), [#5351](https://github.com/Leaflet/Leaflet/issues/5351), [#5330](https://github.com/Leaflet/Leaflet/issues/5330), [#5329](https://github.com/Leaflet/Leaflet/issues/5329), [#5489](https://github.com/Leaflet/Leaflet/issues/5489), [#5504](https://github.com/Leaflet/Leaflet/issues/5504), [#5456](https://github.com/Leaflet/Leaflet/issues/5456), [#5463](https://github.com/Leaflet/Leaflet/issues/5463))
### Improvements
* Add new class `L.VideoOverlay` ([#4988](https://github.com/Leaflet/Leaflet/issues/4988) by [IvanSanchez](https://github.com/IvanSanchez))
* Added z-index support to ImageOverlay ([#5418](https://github.com/Leaflet/Leaflet/issues/5418) by [Saulzi](https://github.com/Saulzi))
* Added error event to `ImageOverlay` layer and added tests for the new ([#5416](https://github.com/Leaflet/Leaflet/issues/5416) by [Saulzi](https://github.com/Saulzi))
* Add `className` option for `ImageOverlay` ([#5555](https://github.com/Leaflet/Leaflet/issues/5555) by [perliedman](https://github.com/perliedman))
### Bug fixes
* Handle edge case of empty bounds on `_getBoundsCenterZoom` ([#5157](https://github.com/Leaflet/Leaflet/issues/5157) by [IvanSanchez](https://github.com/IvanSanchez))
* Add new methods to `L.Bounds` for 2 missing corners, fixes [#5475](https://github.com/Leaflet/Leaflet/issues/5475) ([#5488](https://github.com/Leaflet/Leaflet/issues/5488) by [ghybs](https://github.com/ghybs))
* Handle `Polyline`s with empty array of `LatLng`s, [#5497](https://github.com/Leaflet/Leaflet/issues/5497) ([#5498](https://github.com/Leaflet/Leaflet/issues/5498), by [perliedman](https://github.com/perliedman))
* Take `devicePixelRatio` into account for scrollwheel zoom in win10+chrome ([#5480](https://github.com/Leaflet/Leaflet/issues/5480)) (by [IvanSanchez](https://github.com/IvanSanchez))
* Add hook points to allow for a proper NoGap plugin ([#5476](https://github.com/Leaflet/Leaflet/issues/5476)) (by [IvanSanchez](https://github.com/IvanSanchez))
* Sanity check to prevent loading tiles when bounds are `Infinity` ([#5478](https://github.com/Leaflet/Leaflet/issues/5478), [#5479](https://github.com/Leaflet/Leaflet/issues/5479) by [IvanSanchez](https://github.com/IvanSanchez))
* Fix box zoom race condition ([#5452](https://github.com/Leaflet/Leaflet/issues/5452) by [ppaskaris](https://github.com/ppaskaris))
* On update set current tiles active to avoid pruning ([#5381](https://github.com/Leaflet/Leaflet/issues/5381)) ([#5431](https://github.com/Leaflet/Leaflet/issues/5431) by [oliverheilig](https://github.com/oliverheilig))
* Make `L.Mixin.Events` a simple object instead of a prototype so it doesn't have a 'constructor' property, fixes [#5451](https://github.com/Leaflet/Leaflet/issues/5451) ([#5453](https://github.com/Leaflet/Leaflet/issues/5453) by [luiscamachopt](https://github.com/luiscamachopt))
* Canvas: call `ctx.setLineDash` in `_fillStroke` [#5182](https://github.com/Leaflet/Leaflet/issues/5182) ([#5454](https://github.com/Leaflet/Leaflet/issues/5454) by [TeXitoi](https://github.com/TeXitoi))
* Only rearrange DOM in `toFront`/`toBack` if needed, fixes [#4050](https://github.com/Leaflet/Leaflet/issues/4050) ([#5465](https://github.com/Leaflet/Leaflet/issues/5465) by [perliedman](https://github.com/perliedman))
* Push back keyboard navigation order of `L.Popup`'s close button ([#5461](https://github.com/Leaflet/Leaflet/issues/5461) by [Mirodil](https://github.com/Mirodil))
* Remove spurious check in `DomUtil.preventOutline` ([#5435](https://github.com/Leaflet/Leaflet/issues/5435) by [qjas](https://github.com/qjas))
* Error handler in `ImageOverlay` for 404 links ([#5307](https://github.com/Leaflet/Leaflet/issues/5307)) by [APwhitehat](https://github.com/APwhitehat))
* Ensure renderer's container is initialized when a path is added to map ([#5404](https://github.com/Leaflet/Leaflet/issues/5404) by [IvanSanchez](https://github.com/IvanSanchez))
* Layers Control only add layer events to layers when we are on the map. Fixes [#5421](https://github.com/Leaflet/Leaflet/issues/5421) ([#5422](https://github.com/Leaflet/Leaflet/issues/5422) by [danzel](https://github.com/danzel))
* Layers Control can now become scrollable even if `collapsed: false`, fixes [#5328](https://github.com/Leaflet/Leaflet/issues/5328) ([#5348](https://github.com/Leaflet/Leaflet/issues/5348) by [ghybs](https://github.com/ghybs))
* Stop map on drag start instead of pointer down, fixes [#5350](https://github.com/Leaflet/Leaflet/issues/5350) ([#5378](https://github.com/Leaflet/Leaflet/issues/5378) by [perliedman](https://github.com/perliedman))
* fix invalid GeoJSON produced by nested `LayerGroups` ([#5359](https://github.com/Leaflet/Leaflet/issues/5359) by [Resonance1584](https://github.com/Resonance1584))
* Update toolbar inner border radius ([#5361](https://github.com/Leaflet/Leaflet/issues/5361) by [newmanw](https://github.com/newmanw))
* Export `lastId` in `Util` ([#5349](https://github.com/Leaflet/Leaflet/issues/5349) by [DenisCarriere](https://github.com/DenisCarriere))
* Do not stop keypress on escape if no popup is open, plus unit tests ([#5331](https://github.com/Leaflet/Leaflet/issues/5331) (by [IvanSanchez](https://github.com/IvanSanchez))
* Docs: remove `iframe` borders on examples ([#5327](https://github.com/Leaflet/Leaflet/issues/5327)) (by [tariqksoliman](https://github.com/tariqksoliman))
* Pull `min`/`maxNativeZoom` from `TileLayer` into `GridLayer`, as per [#5316](https://github.com/Leaflet/Leaflet/issues/5316). ([#5319](https://github.com/Leaflet/Leaflet/issues/5319) by [jkuebart](https://github.com/jkuebart))
* Disable click propagation on zoom control buttons, fixes [#5308](https://github.com/Leaflet/Leaflet/issues/5308) ([#5318](https://github.com/Leaflet/Leaflet/issues/5318) by [perliedman](https://github.com/perliedman))
* Add CSS for `-webkit-tap-highlight-color`, fixes [#5302](https://github.com/Leaflet/Leaflet/issues/5302) ([#5303](https://github.com/Leaflet/Leaflet/issues/5303) by [IvanSanchez](https://github.com/IvanSanchez))
* Removed type attribute in HTML5 files ([#5309](https://github.com/Leaflet/Leaflet/issues/5309) by [uzerus](https://github.com/uzerus))
* Add margin to `LatLngBounds.equals` method ([#5071](https://github.com/Leaflet/Leaflet/issues/5071) by [miguelcobain](https://github.com/miguelcobain))
* Add `L.Draggable` options and fix docstring ([#5301](https://github.com/Leaflet/Leaflet/issues/5301) by [IvanSanchez](https://github.com/IvanSanchez))
* Fix max/min calculation for `getBoundsZoom`, fixes [#5136](https://github.com/Leaflet/Leaflet/issues/5136) ([#5137](https://github.com/Leaflet/Leaflet/issues/5137) by [IvanSanchez](https://github.com/IvanSanchez))
* Scrubbing of detached DOM elements, prevents memory leaks; fixes [#5263](https://github.com/Leaflet/Leaflet/issues/5263) ([#5265](https://github.com/Leaflet/Leaflet/issues/5265) by [IvanSanchez](https://github.com/IvanSanchez))
* Remove `marker.dragging` when not on the map, fixes [#5293](https://github.com/Leaflet/Leaflet/issues/5293) ([#5295](https://github.com/Leaflet/Leaflet/issues/5295) by [danzel](https://github.com/danzel))
* Stop scroll propagation in `L.Layers.Control` in chrome>55 ([#5280](https://github.com/Leaflet/Leaflet/issues/5280) by [IvanSanchez](https://github.com/IvanSanchez))
* Allow HTML inputs in layer control's labels, fixes [#5116](https://github.com/Leaflet/Leaflet/issues/5116) ([#51165544](https://github.com/Leaflet/Leaflet/issues/5544) by [iZucken](https://github.com/iZucken))
* Fix possible null reference when auto detecting icon default path, fixes [#5534](https://github.com/Leaflet/Leaflet/issues/5534) ([#5535](https://github.com/Leaflet/Leaflet/issues/5535) by [williamscs](https://github.com/williamscs))
* Don't turn enter keypress into map clicks, fixes [#5499](https://github.com/Leaflet/Leaflet/issues/5499) ([#5507](https://github.com/Leaflet/Leaflet/issues/5507) by [perliedman](https://github.com/perliedman))
* Use minus character instead of hyphen in the zoom control ([#5501](https://github.com/Leaflet/Leaflet/issues/5501) by [damianmoore](https://github.com/damianmoore))
## 1.0.3 (2017-01-23)
### Bug fixes
* Avoid extra `L.Canvas` redraws on several scenarios (by [@perliedman](https://github.com/perliedman), [#5250](https://github.com/Leaflet/Leaflet/pull/5034), also thanks to [@manubb](https://github.com/manubb) for investigation and PR).
* Fix behaviour of `dblclick` events in [Chrome 55 due to `PointerEvent`s](https://developers.google.com/web/updates/2016/11/nic55#pointer-events) (by [@IvanSanchez](https://github.com/IvanSanchez), [#5185](https://github.com/Leaflet/Leaflet/pull/5185), [#5248](https://github.com/Leaflet/Leaflet/pull/5248), [#5268](https://github.com/Leaflet/Leaflet/pull/5268)).
* Fix a dangling comma making IE8 fail to parse the code (by [@batje](https://github.com/batje), [#5270](https://github.com/Leaflet/Leaflet/pull/5270)).
* Backport event handling fixes from [#5054](https://github.com/Leaflet/Leaflet/pull/5054) into `L.SVG.VML` for IE8 (by [@IvanSanchez](https://github.com/IvanSanchez), [#5215](https://github.com/Leaflet/Leaflet/pull/5215)).
* Fix a race condition when closing popups during their `popupopen` event (by [@hagai26](https://github.com/hagai26), [#5202](https://github.com/Leaflet/Leaflet/pull/5202)).
* Fix `getBoundsZoom` return value on CRSs with a flipped axis (by [@computerlove](https://github.com/computerlove), [#5204](https://github.com/Leaflet/Leaflet/pull/5204)).
* Avoid infinite loops when the `errorTileUrl` of a `L.TileLayer` returns 404 (by [@IvanSanchez](https://github.com/IvanSanchez), [#5177](https://github.com/Leaflet/Leaflet/pull/5177)).
* Remove erroneous initialization of unused event listeners (by [@Brikky](https://github.com/Brikky), [#5160](https://github.com/Leaflet/Leaflet/pull/5160)).
* Fix rounding of `L.Canvas` redraw areas to avoid artifacts during partial updates (by [@Ernie23](https://github.com/Ernie23), [#5148](https://github.com/Leaflet/Leaflet/pull/5148)).
* Fix `isPopupOpen()` behaviour of `L.Layer` when no popup is bound to a layer (by [@gvangool](https://github.com/gvangool), [#5106](https://github.com/Leaflet/Leaflet/pull/5106)).
* Add a sanity check in `LatLngBounds.contains()` to allow for non-instantiated `LatLng` objects (by [@IvanSanchez](https://github.com/IvanSanchez), [#5135](https://github.com/Leaflet/Leaflet/pull/5135)).
* Fix collapsing of `L.Control.Layers` when the `collapse` option is `false` (by [@perliedman](https://github.com/perliedman), [#5131](https://github.com/Leaflet/Leaflet/pull/5131)).
### API changes
* Added a new `WrapLatLngBounds` method to `L.CRS`, to fix an issue with `maxBounds` of `GridLayer`s (by [@IvanSanchez](https://github.com/IvanSanchez), [#5185](https://github.com/Leaflet/Leaflet/pull/5185), also thanks to [@DiogoMCampos](https://github.com/DiogoMCampos) for investigation).
* `L.Map.getSize()` will now return `0` instead of `NaN` in non-graphical environments (by [@ughitsaaron](https://github.com/ughitsaaron), [#5209](https://github.com/Leaflet/Leaflet/pull/5209)).
### Improvements
* Several minor documentation improvements by [@IvanSanchez](https://github.com/IvanSanchez), [@jieter](https://github.com/jieter), [@alonsogarciapablo](https://github.com/alonsogarciapablo), [@jasonoverland](https://github.com/jasonoverland), [@danzel](https://github.com/danzel), [@ghybs](https://github.com/ghybs), [@Ralf8686](https://github.com/Ralf8686), [@geoloep](https://github.com/geoloep)
* Add an `alt` attribute to the `<img>`s of marker shadows (by [@topicus](https://github.com/topicus), [#5259](https://github.com/Leaflet/Leaflet/pull/5259)).
## 1.0.2 (2016-11-21)
### Bug fixes
* Fix CSS for marker shadows when `max-width` is already set (by [@brunob](https://github.com/brunob), [#5046](https://github.com/Leaflet/Leaflet/pull/5046)).
* Fix canvas redraw when style updates fill and/or weight (by [@perliedman](https://github.com/perliedman), [#5034](https://github.com/Leaflet/Leaflet/pull/5034)).
* Prevent canvas from firing multiple `mouseover` events for same layer (by [@perliedman](https://github.com/perliedman), [#5033](https://github.com/Leaflet/Leaflet/pull/5033)).
* Fixed a race condition when removing and adding `L.Canvas` vectors during a zoom animation (by [@ghybs](https://github.com/ghybs)) [#5011](https://github.com/Leaflet/Leaflet/pull/5011).
* Fix zoom animation of ImageOverlay when CRS's Y axis is flipped (by [@perliedman](https://github.com/perliedman)), [#4993](https://github.com/Leaflet/Leaflet/pull/4993).
* Fix encoding/decoding of GeoJSON `FeatureCollection`s (by [@IvanSanchez](https://github.com/IvanSanchez)), [#5045](https://github.com/Leaflet/Leaflet/issues/5045).
* Fix `minZoom`/`maxZoom` late inizialization (by [@IvanSanchez](https://github.com/IvanSanchez)), [#4916](https://github.com/Leaflet/Leaflet/pull/4916).
* Fix styling of custom SVG markers by making stricter CSS selectors (by [@jwoyame](https://github.com/jwoyame)) [#4597](https://github.com/Leaflet/Leaflet/pull/4597).
* Fix order of `mouseover`/`mouseout` events on overlapping `L.Canvas` layers (by [@perliedman](https://github.com/perliedman)), [#5090](https://github.com/Leaflet/Leaflet/pull/5090).
* Fix drag behaviour when a draggable marker is removed in mid-drag (by [@IvanSanchez](https://github.com/IvanSanchez), [#5063](https://github.com/Leaflet/Leaflet/pull/5063).
* Fix `L.Control.Layers.collapse()` on initially uncollapsed layer controls (by [@perliedman](https://github.com/perliedman)), [#5131](https://github.com/Leaflet/Leaflet/pull/5131).
* Fix blurriness of `L.Tooltip` by rounding up pixel coordinates (by [@ashmigelski](https://github.com/ashmigelski)), [#5089](https://github.com/Leaflet/Leaflet/pull/5089).
* Fix click events on overlapping geometries when using `L.Canvas` (by [@perliedman](https://github.com/perliedman)), [#5100](https://github.com/Leaflet/Leaflet/pull/5100).
### API changes
* Add a `sortLayers` option to `L.Control.Layers` (by [@IvanSanchez](https://github.com/IvanSanchez), [#4711](https://github.com/Leaflet/Leaflet/pull/4711).
* Implement `bringToFront` and `bringToBack` when using `L.Canvas`, plus preventing other canvas glitches (by [@perliedman](https://github.com/perliedman)), [#5115](https://github.com/Leaflet/Leaflet/pull/5115).
* Add `minNativeZoom` option to `L.TileLayer`s. (by [@bb-juliogarcia](https://github.com/bb-juliogarcia)), [#5088](https://github.com/Leaflet/Leaflet/pull/5088).
### Improvements
* Improve performance when adding lots of `L.Path`s by refactoring away event logic (by [@IvanSanchez](https://github.com/IvanSanchez)) [#5054](https://github.com/Leaflet/Leaflet/pull/5054))].
* Several minor documentation improvements by [@Jmuccigr](https://github.com/Jmuccigr), [@serdarkacka](https://github.com/serdarkacka), [@erickzhao](https://github.com/erickzhao), [@IvanSanchez](https://github.com/IvanSanchez), [@perliedman](https://github.com/perliedman), [@joukewitteveen](https://github.com/joukewitteveen).
* Code reorganization: Extensions for `L.Marker` and `L.Map` no longer have a separate file (by [@mourner](https://github.com/mourner)).
* Removed a duplicated unit test ([@yohanboniface](https://github.com/yohanboniface)).
* Accesibility improvements (ARIA/screenreader related) on map tiles (by [@patrickarlt](https://github.com/patrickarlt)) [#5092](https://github.com/Leaflet/Leaflet/pull/5092).
## 1.0.1 (2016-09-30)
### Bug fixes
* Fixed vector rendering regression in IE8 (by [@perliedman](https://github.com/perliedman)) [#4656](https://github.com/Leaflet/Leaflet/pull/4956).
* Fixed Webpack error when bundling Leaflet's CSS (by [@jefbarn](https://github.com/jefbarn)) [#4679](https://github.com/Leaflet/Leaflet/pull/4979).
## 1.0.0 (2016-09-26)
### API changes
* Remove deprecated `zoomanimated` option for `L.Popup`s (by [@fnicollet](https://github.com/fnicollet)) [#4699](https://github.com/Leaflet/Leaflet/pull/4699)
### Improvements
* Several minor fixes to the documentation (by [@IvanSanchez](https://github.com/IvanSanchez), [@alejo90](https://github.com/alejo90), [@ghybs](https://github.com/ghybs), [@JonasDralle](https://github.com/JonasDralle))
* Add license to `bower.json` (by [@anotherredward](https://github.com/anotherredward)) [#4865](https://github.com/Leaflet/Leaflet/pull/4865)
* Allow creating tooltips without a source layer (by [@yohanboniface](https://github.com/yohanboniface)) [#4836](https://github.com/Leaflet/Leaflet/pull/4836)
* Detect `L.Icon` default path using CSS (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4605](https://github.com/Leaflet/Leaflet/pull/4605)
### Bug fixes
* Fix handling of `getScaleZoom` in some custom CRSs (by [@theashyster](https://github.com/theashyster)) [#4919](https://github.com/Leaflet/Leaflet/pull/4919)
* Guard `L.Path` event handlers against race conditions (by [@perliedman](https://github.com/perliedman) and [@IvanSanchez](https://github.com/IvanSanchez)) [#4855](https://github.com/Leaflet/Leaflet/pull/4855) [#4929](https://github.com/Leaflet/Leaflet/pull/4929) [#4838](https://github.com/Leaflet/Leaflet/pull/4838)
* In `L.GridLayer`s, wrap `tileBounds` when `noWrap` is false (by [@fyeah](https://github.com/fyeah)) [#4908](https://github.com/Leaflet/Leaflet/pull/4908)
* Fix `L.Path`'s `bringToFront()` behaviour in the Edge browser (by [@nikolai-b](https://github.com/nikolai-b)) [#4848](https://github.com/Leaflet/Leaflet/pull/4848)
* Remove spurious counting of event handlers (by [@perliedman](https://github.com/perliedman)) [#4842](https://github.com/Leaflet/Leaflet/pull/4842)
* Throw error on `getCenter()` when a `L.Polygon` has no map and thus no CRS (by [@snkashis](https://github.com/snkashis)) [#4820](https://github.com/Leaflet/Leaflet/pull/4820)
* Add a `_leaflet_id` to map containers to prevent error when removing a map twice (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4810](https://github.com/Leaflet/Leaflet/pull/4810)
* Do not fail when closing a tooltip which is not in a map (by [@yohanboniface](https://github.com/yohanboniface)) [#4937](https://github.com/Leaflet/Leaflet/pull/4937)
## 1.0-rc3 (2016-08-05)
### API changes
- `L.Tooltip` `offset` option now defaults to `[0, 0]` (by [@yohanboniface](https://github.com/yohanboniface)) [#4773](https://github.com/Leaflet/Leaflet/pull/4773)
- Event listeners are now always called in the order they have been registered, while until rc2 listeners with a context were all called before listeners without context (even if registered later), and the listeners with context were called in an unpredictable order (by [@yohanboniface](https://github.com/yohanboniface)) [#4769](https://github.com/Leaflet/Leaflet/pull/4769)
### Improvements
- Added `oldLatLng` in `L.Marker` `drag` event (by [@snkashis](https://github.com/snkashis)) [#4752](https://github.com/Leaflet/Leaflet/pull/4752)
### Bug fixes
- Fixed regression where event listeners where not always fired in the order of registration (by [@yohanboniface](https://github.com/yohanboniface)) [#4769](https://github.com/Leaflet/Leaflet/pull/4769)
- Fixed `L.Tooltip` zoom animation (by [@yohanboniface](https://github.com/yohanboniface)) [#4744](https://github.com/Leaflet/Leaflet/pull/4744)
- Fixed `layer.bindTooltip` crashing when called before adding the layer to the map (by [@yohanboniface](https://github.com/yohanboniface)) [#4779](https://github.com/Leaflet/Leaflet/pull/4779)
- Fixed regression in `L.Popup` autopaning (by [@yohanboniface](https://github.com/yohanboniface)) [#4768](https://github.com/Leaflet/Leaflet/pull/4768)
- Fixed non permanent `L.Tooltip` not being closed on touch when touching the map (by [@yohanboniface](https://github.com/yohanboniface)) [#4767](https://github.com/Leaflet/Leaflet/pull/4767)
- Fixed `popupopen` and `popupclose` not being fired when clicking on path with an open popup (by [@yohanboniface](https://github.com/yohanboniface)) [#4788](https://github.com/Leaflet/Leaflet/pull/4788)
## 1.0-rc2 (2016-07-18)
### API changes
* Make `L.Handler.enable`/`disable` return `this` (by [@yohanboniface](https://github.com/yohanboniface)) [#4708](https://github.com/Leaflet/Leaflet/pull/4708)
* Icon `size` option can now be initialised with a number (by [@rheh](https://github.com/rheh)) [#4608](https://github.com/Leaflet/Leaflet/pull/4608)
* Add `classname` option to `L.GridLayer` (by [@jayvarner](https://github.com/jayvarner)) [#4553](https://github.com/Leaflet/Leaflet/pull/4553)
* Consistent returns for `Map.addLayer` (by [@nathancahill](https://github.com/nathancahill)) [#4504](https://github.com/Leaflet/Leaflet/pull/4504)
* Create points from objects with `x` and `y` properties (by [@nathancahill](https://github.com/nathancahill)) [#4465](https://github.com/Leaflet/Leaflet/pull/4465)
* Add `updateWhenZooming` option to `L.GridLayer` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4462](https://github.com/Leaflet/Leaflet/pull/4462)
### Improvements
* Refactoring of events (by [@fab1an](https://github.com/fab1an) and [@perliedman](https://github.com/perliedman)) [#4697](https://github.com/Leaflet/Leaflet/pull/4697)
* Do not alter `popup.options.offset` when computing popup offset (fix #4645) (by [@yohanboniface](https://github.com/yohanboniface)) [#4662](https://github.com/Leaflet/Leaflet/pull/4662)
* Use different `L.Bounds` for "marking as prunable" and loading tiles (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4650](https://github.com/Leaflet/Leaflet/pull/4650)
* Added `L.Tooltip` class to display small tooltips on the map (by [@yohanboniface](https://github.com/yohanboniface)) [#3952](https://github.com/Leaflet/Leaflet/pull/3952)
### Bug fixes
* Fixed `GridLayer`'s outer edge snapping to vertical center of map (fix #4702) (by [@yohanboniface](https://github.com/yohanboniface)) [#4704](https://github.com/Leaflet/Leaflet/pull/4704)
* Fixed scrollwheel zoom too fast in MS Edge (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4694](https://github.com/Leaflet/Leaflet/pull/4694)
* Use `pointer-events: visiblePainted` as fallback for IE <11 (by [@perliedman](https://github.com/perliedman)) [#4690](https://github.com/Leaflet/Leaflet/pull/4690)
* Avoid double borders on `abbr` in website (by [@brunob](https://github.com/brunob)) [#4663](https://github.com/Leaflet/Leaflet/pull/4663)
* Prevent firing map click when layer has popup (by [@jwoyame](https://github.com/jwoyame)) [#4603](https://github.com/Leaflet/Leaflet/pull/4603)
* Disable pointer events on popup tip (by [@jwoyame](https://github.com/jwoyame)) [#4599](https://github.com/Leaflet/Leaflet/pull/4599)
* Prevent `L.DomUtil.create()` from automatically setting a CSS class name (by [@MuellerMatthew](https://github.com/MuellerMatthew)) [#4563](https://github.com/Leaflet/Leaflet/pull/4563)
* Fix off-by-one bug in `Control.Layers._getLayer` (by [@ValentinH](https://github.com/ValentinH)) [#4561](https://github.com/Leaflet/Leaflet/pull/4561)
* Fix scrollwheel events zomming two levelz in Chrome by scaling down `getWheelDelta()` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4538](https://github.com/Leaflet/Leaflet/pull/4538)
* Prevent event listeners from being called when all listeners are removed (by [@perliedman](https://github.com/perliedman)) [#4555](https://github.com/Leaflet/Leaflet/pull/4555)
* Don't prevent browser's touch scroll and/or zoom unless handlers are enabled (by [@perliedman](https://github.com/perliedman)) [#4552](https://github.com/Leaflet/Leaflet/pull/4552)
* Fixed `getBoundsZoom` with small size and padding (by [@dianjin](https://github.com/dianjin)) [#4532](https://github.com/Leaflet/Leaflet/pull/4532)
* Fixed `L.Control.Layers` in IE8 (by [@jieter](https://github.com/jieter)) [#4509](https://github.com/Leaflet/Leaflet/pull/4509)
* Fixed `TileLayer`'s retina logic when `zoomReverse` is enabled. (by [@perliedman](https://github.com/perliedman)) [#4503](https://github.com/Leaflet/Leaflet/pull/4503)
* Fixed `setMaxBounds` not resetting `maxBounds` when passing `null` argument (by [@yohanboniface](https://github.com/yohanboniface)) [#4494](https://github.com/Leaflet/Leaflet/pull/4494)
* Fixed canvas not filtering click event after drag (by [@yohanboniface](https://github.com/yohanboniface)) [#4493](https://github.com/Leaflet/Leaflet/pull/4493)
* Fixed `L.Control.removeLayer()` raising an error when trying to remove a layer not yet added (by [@jieter](https://github.com/jieter)) [#4487](https://github.com/Leaflet/Leaflet/pull/4487)
* Fixed disabling drag on click in IE11 (by [@perliedman](https://github.com/perliedman)) [#4479](https://github.com/Leaflet/Leaflet/pull/4479)
* Fixed `L.Evented.listens()` on removed event handlers, #4474 (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4476](https://github.com/Leaflet/Leaflet/pull/4476)
* Better handling of `markerZoomAnimation` event hooks (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4460](https://github.com/Leaflet/Leaflet/pull/4460)
## 1.0-rc1 (2016-04-18)
### API changes
* Make `L.Control.Layers.collapse`/`expand` public methods (by [@yohanboniface](https://github.com/yohanboniface)) [#4370](https://github.com/Leaflet/Leaflet/pull/4370)
* Make `L.latLngBounds` factory return an empty bounds with no argument (by [@yohanboniface](https://github.com/yohanboniface)) [#4368](https://github.com/Leaflet/Leaflet/pull/4368)
* `Map.fitBounds` now raises an error if bounds are not valid (by [@theotow](https://github.com/theotow)) [#4353](https://github.com/Leaflet/Leaflet/pull/4353)
* Temporarily support legacy options on `L.Circle` (by [@JrFolk](https://github.com/JrFolk)) [#4290](https://github.com/Leaflet/Leaflet/pull/4290)
* Throw error on `NaN` circle radius (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4237](https://github.com/Leaflet/Leaflet/pull/4237)
* `L.Class.include()` & `mergeOptions()` now return `this` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4246](https://github.com/Leaflet/Leaflet/pull/4246)
* Consistent GeoJSON casing (by [@yohanboniface](https://github.com/yohanboniface)) [#4108](https://github.com/Leaflet/Leaflet/pull/4108)
* Move `L.LatLng.equals` to `L.CRS.equals`. (by [@perliedman](https://github.com/perliedman)) [#4074](https://github.com/Leaflet/Leaflet/pull/4074)
* Make non-interactive markers not firing pointer events (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3937](https://github.com/Leaflet/Leaflet/pull/3937)
### Improvements
* Give popups an id (by [@tylercubell](https://github.com/tylercubell)) [#4355](https://github.com/Leaflet/Leaflet/pull/4355)
* Support `{-y}` in tile layer urls (by [@jieter](https://github.com/jieter)) [#4337](https://github.com/Leaflet/Leaflet/pull/4337)
* Support `dashArray` path option in canvas (by [@gommo](https://github.com/gommo)) [#4173](https://github.com/Leaflet/Leaflet/pull/4173)
* Clean up `navigator.pointerEnabled` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4287](https://github.com/Leaflet/Leaflet/pull/4287)
* Use array in `L.Control.Layers` internally (by [@jieter](https://github.com/jieter)) [#4227](https://github.com/Leaflet/Leaflet/pull/4227)
* Implement `L.Browser.edge` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4143](https://github.com/Leaflet/Leaflet/pull/4143)
* Optimized icons (by [@vtduncan](https://github.com/vtduncan)) [#4124](https://github.com/Leaflet/Leaflet/pull/4124)
* Cast `L.DivIcon.bgPos` option to `L.Point` (by [@perliedman](https://github.com/perliedman)) [#4090](https://github.com/Leaflet/Leaflet/pull/4090)
* Switch to wheel event where available (by [@mourner](https://github.com/mourner)) [#3653](https://github.com/Leaflet/Leaflet/pull/3653)
* Fractional zoom controls (by [@IvanSanchez](https://github.com/IvanSanchez) and [@hyperknot](https://github.com/hyperknot)) [#3523](https://github.com/Leaflet/Leaflet/pull/3523)
* Added click tolerance also for non-touch devices (by [DavidUv](https://github.com/DavidUv)) [#4396](https://github.com/Leaflet/Leaflet/pull/4396)
### Bug fixes
* Fixed an edge case on `Map.fitBounds` (by [@perliedman](https://github.com/perliedman)) [#4377](https://github.com/Leaflet/Leaflet/pull/4377)
* Fixed an edge case bug in `flyTo` (by [@hyperknot](https://github.com/hyperknot)) [#4376](https://github.com/Leaflet/Leaflet/pull/4376)
* Use mean earth radius for distance calculation in `L.CRS.Earth`. (by [@perliedman](https://github.com/perliedman)) [#4369](https://github.com/Leaflet/Leaflet/pull/4369)
* Fixed zoom event fired twice (by [@perliedman](https://github.com/perliedman)) [#4367](https://github.com/Leaflet/Leaflet/pull/4367)
* Initialize canvas dash on init. Check that canvas supports `setLineDash`. (by [@perliedman](https://github.com/perliedman)) [#4364](https://github.com/Leaflet/Leaflet/pull/4364)
* Do not calculate inverted y coords for CRSes with infinite: true (by [@jieter](https://github.com/jieter)) [#4344](https://github.com/Leaflet/Leaflet/pull/4344)
* Fixed zoom handling on `Map.TouchZoom` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4340](https://github.com/Leaflet/Leaflet/pull/4340)
* Fixed `this._times.length` undefined in `Map.Drag.js` (by [@LucasMouraDeOliveira](https://github.com/LucasMouraDeOliveira)) [#4324](https://github.com/Leaflet/Leaflet/pull/4324)
* Fixed simulated click handling in `L.Path` (by [@elkami12](https://github.com/elkami12)) [#4314](https://github.com/Leaflet/Leaflet/pull/4314)
* Fixed attribution text not removed when Layer is removed from map (by [@dr-itz](https://github.com/dr-itz)) [#4293](https://github.com/Leaflet/Leaflet/pull/4293)
* Fixed bug when adding/removing a layer from `L.Control.Layer` that is not on the map (by [@errebenito](https://github.com/errebenito)) [#4280](https://github.com/Leaflet/Leaflet/pull/4280)
* Fixed `Map.attributionControl` only set on `addInitHook` (by [@snkashis](https://github.com/snkashis)) [#4263](https://github.com/Leaflet/Leaflet/pull/4263)
* Check for `e.originalEvent` in `DomUtil._filterclick` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4256](https://github.com/Leaflet/Leaflet/pull/4256)
* Stop drag propagation on `L.Draggable` (by [@turban](https://github.com/turban)) [#4250](https://github.com/Leaflet/Leaflet/pull/4250)
* Fixed error when quickly removing a layer just added (by [@hyperknot](https://github.com/hyperknot)) [#4244](https://github.com/Leaflet/Leaflet/pull/4244)
* Fixed not resetting properly on `Map.stop` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4229](https://github.com/Leaflet/Leaflet/pull/4229)
* Fixed conflict between `Map.fadeAnimation` and `GridLayer.opacity` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4228](https://github.com/Leaflet/Leaflet/pull/4228)
* Fix fractional zoom calculation (by [@hyperknot](https://github.com/hyperknot)) [#4224](https://github.com/Leaflet/Leaflet/pull/4224)
* Better cleanup of L.Control.Layers, fixes [#4213](https://github.com/Leaflet/Leaflet/pull/4213) (plus unit tests) (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4214](https://github.com/Leaflet/Leaflet/pull/4214)
* Fixed transform issue when not `L.Browser.any3d` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4212](https://github.com/Leaflet/Leaflet/pull/4212)
* Fixed fractional zoom controls broken when initial zoom isn't specified (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4209](https://github.com/Leaflet/Leaflet/pull/4209)
* Fix extra tiles usage (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4193](https://github.com/Leaflet/Leaflet/pull/4193)
* Sanity check: test `pxBounds` validity before using it (by [@yohanboniface](https://github.com/yohanboniface)) [#4191](https://github.com/Leaflet/Leaflet/pull/4191)
* Disable event defaults when disabling scroll propagation (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4160](https://github.com/Leaflet/Leaflet/pull/4160)
* Fixed precision issues in `L.Circle` radius (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4133](https://github.com/Leaflet/Leaflet/pull/4133)
* Workarounded some touch-capable browsers firing `dblclick` instead of touch events (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4131](https://github.com/Leaflet/Leaflet/pull/4131)
* Use all projected coords when calculating `L.Polyline`'s pixel bounds. (by [@perliedman](https://github.com/perliedman)) [#4114](https://github.com/Leaflet/Leaflet/pull/4114)
* Fixed removing all events when passing an `undefined` property (by [@robertleeplummerjr](https://github.com/robertleeplummerjr)) [#4113](https://github.com/Leaflet/Leaflet/pull/4113)
* Fixed retina URL computation (by [@hyperknot](https://github.com/hyperknot)) [#4110](https://github.com/Leaflet/Leaflet/pull/4110)
* Fire `tileunload` for all unloading of tiles (by [@tcoats](https://github.com/tcoats)) [#4099](https://github.com/Leaflet/Leaflet/pull/4099)
* Fixed duplicated code in `L.GridLayer.retainParent()` (by [@jblarsen](https://github.com/jblarsen)) [#4094](https://github.com/Leaflet/Leaflet/pull/4094)
* Make sure to always reset `_enforcingBounds`. (by [@perliedman](https://github.com/perliedman)) [#4089](https://github.com/Leaflet/Leaflet/pull/4089)
* Fix bug with max bounds and custom projections (by [@OleLaursen](https://github.com/OleLaursen)) [#4078](https://github.com/Leaflet/Leaflet/pull/4078)
* When limiting center to bounds, ignore offsets less than a pixel. (by [@perliedman](https://github.com/perliedman)) [#4077](https://github.com/Leaflet/Leaflet/pull/4077)
* Fixed bug for hover event between circle overlapping polygon on canvas (by [@fimietta](https://github.com/fimietta)) [#4072](https://github.com/Leaflet/Leaflet/pull/4072)
* Fixed but where `L.Control.Layers` where calling `map._size` instead of `getSize()` (by [@Brobin](https://github.com/Brobin)) [#4063](https://github.com/Leaflet/Leaflet/pull/4063)
* Round new map position before animating pan (by [@RLRR](https://github.com/RLRR)) [#4046](https://github.com/Leaflet/Leaflet/pull/4046)
* Fixed overlayed circles not responding to mouse events in canvas (by [@Deftwun](https://github.com/Deftwun)) [#4033](https://github.com/Leaflet/Leaflet/pull/4033)
* Fixed GeoJSON `resetStyle` (by [@yohanboniface](https://github.com/yohanboniface)) [#4028](https://github.com/Leaflet/Leaflet/pull/4028)
* Fixed popup toggle on marker click (by [@yohanboniface](https://github.com/yohanboniface)) [#4016](https://github.com/Leaflet/Leaflet/pull/4016)
* Fixed event target fallbacking to map after marker drag (fix [#3971](https://github.com/Leaflet/Leaflet/pull/3971)) (by [@yohanboniface](https://github.com/yohanboniface)) [#4010](https://github.com/Leaflet/Leaflet/pull/4010)
* Fixed `maxZoom` not honoring 0 (by [@simsibimsiwimsi](https://github.com/simsibimsiwimsi)) [#4000](https://github.com/Leaflet/Leaflet/pull/4000)
* Skip `L.GridLayer._updateLevels()` when out of min/max zoom (prevents IE8 exceptions) (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3999](https://github.com/Leaflet/Leaflet/pull/3999)
* `L.DomUtil.getPosition()` should return a fallback value (for VML in IE8) (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3998](https://github.com/Leaflet/Leaflet/pull/3998)
* `L.Marker`: init interaction regardless of new icon or not (by [@celadevra](https://github.com/celadevra)) [#3978](https://github.com/Leaflet/Leaflet/pull/3978)
* Fix `interactive=false` not taken into account for canvas (by [@yohanboniface](https://github.com/yohanboniface)) [#3956](https://github.com/Leaflet/Leaflet/pull/3956)
* Fix canvas path disappearing on animation (by [@klaftertief](https://github.com/klaftertief)) [#3950](https://github.com/Leaflet/Leaflet/pull/3950)
* Only check for moving draggable in canvas renderer when map is draggable (by [@klaftertief](https://github.com/klaftertief)) [#3942](https://github.com/Leaflet/Leaflet/pull/3942)
* Fix SVG Dragging issues in IE and Edge (by [@perliedman](https://github.com/perliedman)) [#4382](https://github.com/Leaflet/Leaflet/pull/4382)
* Fix click not working on inputs in controls in IE 11 (by [@perliedman](https://github.com/perliedman)) [#4371](https://github.com/Leaflet/Leaflet/pull/4371)
* Make drag and touch zoom handlers insensitive to order of event handlers (by [@perliedman](https://github.com/perliedman)) [#4387](https://github.com/Leaflet/Leaflet/pull/4387)
### Other
* Added a Code of Conduct (by [@mourner](https://github.com/mourner)) [#4142](https://github.com/Leaflet/Leaflet/pull/4142)
* Dual 1.0 & 0.7 docs to gh-pages (by [@IvanSanchez](https://github.com/IvanSanchez)) [#4085](https://github.com/Leaflet/Leaflet/pull/4085)
* 🍂doc (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3916](https://github.com/Leaflet/Leaflet/pull/3916)
* A lot of documentation improvements (by [@nathancahill](https://github.com/nathancahill)) [#4418](https://github.com/Leaflet/Leaflet/pull/4418), [#4419](https://github.com/Leaflet/Leaflet/pull/4419), [#4423](https://github.com/Leaflet/Leaflet/pull/4423), [#4425](https://github.com/Leaflet/Leaflet/pull/4425) and [a lot more](https://github.com/Leaflet/Leaflet/pulls?q=is%3Apr+author%3Anathancahill+is%3Aclosed)
* Replace links to google groups with ones to GIS StackExchange (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3886](https://github.com/Leaflet/Leaflet/pull/3886)
## 1.0-beta2 (2015-10-14)
Beta 2 fixes over 50 bugs that were reported by users trying out beta 1. The vast majority of changes are small fixes to problems that are triggered in very specific situations or conditions, a few API consolidation changes, and a few browser workarounds.
### API changes
* `L.circle` now accepts `radius` as an option (like `L.circleMarker`) rather than a second argument (by [@IvanSanchez](https://github.com/IvanSanchez))
### Improvements
* Implemented canvas optimizations on mousehover interactions (by [@philippelatulippe](https://github.com/philippelatulippe)) [#3076](https://github.com/Leaflet/Leaflet/issues/3076)
* Improved drag behaviour by preventing a `preclick` event during drag (by [@yohanboniface](https://github.com/yohanboniface)) [#3632](https://github.com/Leaflet/Leaflet/issues/3632)
* Implemented `L.ImageOverlay.setBounds()` and fixed image overlay initialization (by [@fminuti](https://github.com/fminuti)) [#3680](https://github.com/Leaflet/Leaflet/pull/3680)
* Implemented draggable items to fire `mousedown` events (by [@yohanboniface](https://github.com/yohanboniface)) [#3682](https://github.com/Leaflet/Leaflet/pull/3682)
* Changed detection of browsers capable of `msPointer` events (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3684](https://github.com/Leaflet/Leaflet/pull/3684)
* Implemented latitude truncation for spherical mercator projection (by [@perliedman](https://github.com/perliedman)) [#3700](https://github.com/Leaflet/Leaflet/issues/3700)
* Armored against browsers not implementing `Geolocation.clearWatch()` [#3707](https://github.com/Leaflet/Leaflet/issues/3707)
* Implemented generation of sourcemaps when building and minifying source files (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3723](https://github.com/Leaflet/Leaflet/pull/3723)
* Added `bringToFront` and `bringToBack` to popups (by [@danzel](https://github.com/danzel)). [#3908](https://github.com/Leaflet/Leaflet/pull/3908) [#3307](https://github.com/Leaflet/Leaflet/issues/3813)
* Multiply offset by 3 on keyboard pan when shift key is pressed (by [@yohanboniface](https://github.com/yohanboniface)) [#3921](https://github.com/Leaflet/Leaflet/pull/3921)
### Bug fixes
* Fixed event propagation on double finger tap (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3532](https://github.com/Leaflet/Leaflet/issues/3532)
* Fixed style changes on re-added layers (by [@wpf500](https://github.com/wpf500)) [#3547](https://github.com/Leaflet/Leaflet/pull/3547)
* Fixed `preventOutline` exceptions [#3625](https://github.com/Leaflet/Leaflet/issues/3625)
* Fixed a box zoom regression bug [#3633](https://github.com/Leaflet/Leaflet/issues/3633)
* Fixed `contextmenu` behaviour when there are no event listeners (by [@yohanboniface](https://github.com/yohanboniface)) [#3638](https://github.com/Leaflet/Leaflet/issues/3638)
* Fixed map controls not showing on top of map layers (by [@patrickarlt](https://github.com/patrickarlt)) [#3644](https://github.com/Leaflet/Leaflet/pull/3644)
* Fixed display of marker images with `max-width` (by [@davidjb](https://github.com/davidjb)) [#3647](https://github.com/Leaflet/Leaflet/pull/3647)
* Fixed `mouseout` and `mouseover` event bubbling (by [@yohanboniface](https://github.com/yohanboniface)) [#3648](https://github.com/Leaflet/Leaflet/issues/3648) [#3797](https://github.com/Leaflet/Leaflet/issues/3797) [#3708](https://github.com/Leaflet/Leaflet/issues/3708)
* Fixed a layer control bug when removing layers (by [@davidlukerice](https://github.com/davidlukerice)) [#3651](https://github.com/Leaflet/Leaflet/pull/3651)
* Fixed pan and zoom animations interacting with each other [#3355](https://github.com/Leaflet/Leaflet/issues/3355) [#3655](https://github.com/Leaflet/Leaflet/issues/3655)
* Fixed a regression bug in canvas renderer when removing layers [#3661](https://github.com/Leaflet/Leaflet/issues/3661)
* Remove a workaround for legacy Android 2 browsers [#2282](https://github.com/Leaflet/Leaflet/issues/2282)
* Fixed VML vector rendering in IE8 when removing and re-adding layers (by [@fminuti](https://github.com/fminuti)) [#2809](https://github.com/Leaflet/Leaflet/issues/2809)
* Fixed `flyTo` animations when the map center doesn't change (by [@fminuti](https://github.com/fminuti)) [#3663](https://github.com/Leaflet/Leaflet/pull/3663)
* Fixed fade animations for semitransparent tile layers (by [@w8r](https://github.com/w8r)) [#3671](https://github.com/Leaflet/Leaflet/pull/3671)
* Fixed behaviour of the `baselayerchange` event (by [@yohanboniface](https://github.com/yohanboniface)) [#3677](https://github.com/Leaflet/Leaflet/issues/3677)
* Fixed marker icon handling during a marker drag (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3687](https://github.com/Leaflet/Leaflet/issues/3687)
* Fixed a IE 11 map container visibility bug (by [@fminuti](https://github.com/fminuti)) [#2788](https://github.com/Leaflet/Leaflet/issues/2788)
* Prevented dragging interactions while a map zoom animation is running (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3692](https://github.com/Leaflet/Leaflet/issues/3692)
* Implement disabling layers in the layer control when they are unavailable at the current zoom level (by [@IvanSanchez](https://github.com/IvanSanchez)) [#252](https://github.com/Leaflet/Leaflet/issues/252)
* Refactored `L.Util.requestAnimFrame` definition (by [@fminuti](https://github.com/fminuti)) [#3703](https://github.com/Leaflet/Leaflet/issues/3703)
* Fixed an edge case of `L.Circle.getBounds()` (by [@knabar](https://github.com/knabar)) [#3776](https://github.com/Leaflet/Leaflet/pull/3776)
* Fixed `Rectangle.setBounds()` return value (by [@IvanSanchez](https://github.com/IvanSanchez)) https://github.com/Leaflet/Leaflet/commit/e698f641afadd4a0b412a6c9e065a6dbfbe16f44
* Fixed event firing order when opening a popup (by [@yohanboniface](https://github.com/yohanboniface)) [#3745](https://github.com/Leaflet/Leaflet/issues/3745)
* Fixed tile layer loading when dragging then immediately pinch-zooming the map (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3814](https://github.com/Leaflet/Leaflet/issues/3814)
* Work around browser limitations when panning the map too far away (by [@yohanboniface](https://github.com/yohanboniface) and [@IvanSanchez](https://github.com/IvanSanchez)) [#3608](https://github.com/Leaflet/Leaflet/issues/3608)
* Fixed popup text selection and touch events for IE 10 (by [@danzel](https://github.com/danzel)) [#3804](https://github.com/Leaflet/Leaflet/issues/3804)
* Fixed tile layer ordering when `maxZoom` is set (by [@patrickarlt](https://github.com/patrickarlt)) [#3721](https://github.com/Leaflet/Leaflet/issues/3721)
* Fixed scale behaviour on non-standard CRSs (by [@javimolla](https://github.com/patrickarlt), special thanks to [Your First PR](https://yourfirstpr.github.io/)) [#2990](https://github.com/Leaflet/Leaflet/issues/2990)
* Fixed rendering of off-screen vector layers (by [@yohanboniface](https://github.com/yohanboniface)) [#3836](https://github.com/Leaflet/Leaflet/issues/3836)
* Fixed rendering of intersecting vector layers (by [@yohanboniface](https://github.com/yohanboniface)) [#3583](https://github.com/Leaflet/Leaflet/issues/3583)
* Enforced stricter code linting
* Fixed disabled drag handlers not being able to be re-enabled (by [@yohanboniface](https://github.com/yohanboniface)) [#3825](https://github.com/Leaflet/Leaflet/issues/3825)
* Fixed panning outside the map bounds (by [@yohanboniface](https://github.com/yohanboniface)) [#3878](https://github.com/Leaflet/Leaflet/issues/3878)
* Worked around IE 11 not focusing the map using keyboard (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3772](https://github.com/Leaflet/Leaflet/issues/3772)
* Fixed vector layer positioning during a zoom animation in low zoom (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3769](https://github.com/Leaflet/Leaflet/issues/3769)
* Implemented `noWrap` option in `L.GridLayer` (by [@IvanSanchez](https://github.com/IvanSanchez)) [#3691](https://github.com/Leaflet/Leaflet/issues/3691)
* Fixed popups panning the map while running another pan animation (by [@yohanboniface](https://github.com/yohanboniface)) [#3744](https://github.com/Leaflet/Leaflet/issues/3744)
* Fixed uncorrect length of scale control due to CSS styles (by [@yohanboniface](https://github.com/yohanboniface)) [#3668](https://github.com/Leaflet/Leaflet/issues/3668)
* Fixed detection of default image path for icons (by [@ilfa](https://github.com/ilfa)) [#3770](https://github.com/Leaflet/Leaflet/pull/3770)
* Various Canvas events fixes (by [@yohanboniface](https://github.com/yohanboniface)) [#3917](https://github.com/Leaflet/Leaflet/pull/3917)
* Fix touch scroll in layers control (by [@yohanboniface](https://github.com/yohanboniface)) [#2882](https://github.com/Leaflet/Leaflet/issues/2882)
* Fix duration not passed through from setView to panBy (by [@yohanboniface](https://github.com/yohanboniface)) [#3300](https://github.com/Leaflet/Leaflet/issues/3300)
## 1.0-beta1 (2015-07-14)
The first beta of the huge, long-awaited Leaflet 1.0 release.
This version contains a lot of beneficial but potentially breaking changes (especially if you're a plugin author), so please read through the changes carefully if your code breaks after upgrading.
### Vectors API improvements
Leaflet vector layers rendering got a major rewrite, making it possible to switch between rendering backends (Canvas, SVG) dynamically (and even use both for different layers at the same time), have more than one physical pane for vector layers, significantly improving performance, adding Canvas retina support, getting rid of ugly hacks and generally making the code much cleaner. Main PR [#2290](https://github.com/Leaflet/Leaflet/pull/2290).
* Added `Renderer` layer (inherited by SVG/Canvas implementations) that handles renderer bounds and zoom animation.
* Added `SVG` & `Canvas` classes that now contain all renderer-specific code for vector layers and can be added as layers to the map.
* All vector layer classes (`Path`, `Polyline`, etc.) now don't contain any renderer-specific code and instead can be passed a renderer layer to use as `renderer` in options;
* Removed `MultiPolyline` and `MultiPolygon` classes since multiple rings are now handled by `Polyline` and `Polygon` classes respectively. Layers with multiple rings now perform much better (since each is now physically a single path object instead of being a `FeatureGroup` of layers).
* **Dramatically improved performance of interactive Canvas layers**. Mouse events work much faster (due to improved hit detection algorithms), and layers get updated many times faster (with partial redraws instead of redrawing the whole Canvas).
* **Dramatically improved performance of interactive SVG layers** (e.g. creating a big GeoJSON layer and adding to a map got 3x faster while taking 2.5x less memory). [#2240](https://github.com/Leaflet/Leaflet/issues/2240) [#2315](https://github.com/Leaflet/Leaflet/issues/2315)
* Added retina support for Canvas layers.
* Improved default vector layer styles.
* Added `Polyline` and `Polygon` `getCenter` for getting centroids.
* Respectively, improved `Polyline` and `Polygon` popup positioning when calling `openPopup`.
* Improved `Polyline`/`Polygon` `getBounds` to be cached (so it works much faster).
* Changed `Circle` to inherit from `CircleMarker` and not the other way.
* Fixed `GeoJSON` `resetStyle` to work properly in case `style` option wasn't set.
* Fixed a bug where map fired `moveend` when updating Canvas layers.
* Removed `Polyline` and `Polygon` `spliceLatLngs` method (just use `setLatLngs` instead).
* Changed `Polygon` `getLatLngs` to always return an array of rings (nested array) (by [@yohanboniface](https://github.com/yohanboniface)). [#2618](https://github.com/Leaflet/Leaflet/issues/2618) [#3386](https://github.com/Leaflet/Leaflet/pull/3386) [Leaflet/Leaflet#3279](https://github.com/Leaflet/Leaflet/pull/3279) [#2648](https://github.com/Leaflet/Leaflet/issues/2648)
* Added the ability to specify a custom pane for vector layers (making it possible to easily do stacks like vectors-tiles-vectors with proper z-index order) (by [@Jakobud](https://github.com/Jakobud)). [#3266](https://github.com/Leaflet/Leaflet/pull/3266) [#2786](https://github.com/Leaflet/Leaflet/pull/2786)
* Added vector layers `fillRule` option (by [@bullgare](https://github.com/bullgare)). [#2834](https://github.com/Leaflet/Leaflet/pull/2834)
* Removed `L_PREFER_CANVAS` global switch and added `Map` `preferCanvas` option instead (by [@yohanboniface](https://github.com/yohanboniface)). [#3623](https://github.com/Leaflet/Leaflet/pull/3623)
### Layers API improvements
All Leaflet layers (including markers, popups, tile and vector layers) have been refactored to have a common parent, `Layer` class, that shares the basic logic of adding and removing. The leads to the following changes (main PR [#2266](https://github.com/Leaflet/Leaflet/pull/2266)):
* Added `Layer` class which all layers added to a map should inherit from.
* Added `add` and `remove` events to all layers.
* Added `remove` method to layers and controls (`marker.remove()` is now equivalent to `map.removeLayer(marker)`).
* Improved performance of adding and removing layers.
* Added `pane` option to all layers that can be changed (e.g. you can set `pane: 'overlayPane'` to a tile layer).
* Added `map` `createPane` method for custom panes. [#1742](https://github.com/Leaflet/Leaflet/issues/1742)
* Simplified map panes &mdash; removed `objectsPane`, all panes are on the same level now.
* Added `shadowPane` option to markers as well.
* Added `getEvents` method to all layers that returns an `{event: listener, ...}` hash; layers now manage its listeners automatically without having to do this in `onAdd`/`onRemove`.
* Added `togglePopup`, `setPopupContent`, `getPopup`, `isPopupOpen` methods to all layers. [#2279](https://github.com/Leaflet/Leaflet/issues/2279) [#2292](https://github.com/Leaflet/Leaflet/issues/2292)
* Improved performance of adding/removing layers with layers control present (instead of listening to any layer add/remove, the control only listens to layers added in configuration).
* Fixed `FeatureGroup` `getBounds` to work correctly when containing circle markers.
* Removed `Map` `tilelayersload` event.
* Removed `Popup` `open` and `close` events in favor of `add` and `remove` for consistency.
* Moved all layer-related logic in `Map.js` to `Layer.js`.
* Improved `FeatureGroup` `bindPopup` to accept a function for generating popup content, significantly improving memory footprint when adding popups to a huge number of layers (by [@patrickarlt](https://github.com/patrickarlt)). [#3109](https://github.com/Leaflet/Leaflet/pull/3109) [#3059](https://github.com/Leaflet/Leaflet/pull/3059)
* Added `ImageOverlay` interactivity (mouse events, etc.) (by [@snkashis](https://github.com/snkashis)). [#3009](https://github.com/Leaflet/Leaflet/pull/3009) [#1727](https://github.com/Leaflet/Leaflet/issues/1727)
* Added `ImageOverlay` `getBounds` (by [@brunob](https://github.com/brunob)). [#2848](https://github.com/Leaflet/Leaflet/pull/2848)
* Renamed `clickable` option to `interactive` (by [@AndriiHeonia](https://github.com/AndriiHeonia)). [#2838](https://github.com/Leaflet/Leaflet/pull/2838) [#2499](https://github.com/Leaflet/Leaflet/issues/2499)
* Changed the default z-index of panes to be bigger, leaving more room for customization (by [@patrickarlt](https://github.com/patrickarlt)). [#3591](https://github.com/Leaflet/Leaflet/pull/3591)
* Improved DOM event bubbling mechanism for layers (by [@yohanboniface](https://github.com/yohanboniface)). [Leaflet/Leaflet#3307](https://github.com/Leaflet/Leaflet/pull/3307) [#3450](https://github.com/Leaflet/Leaflet/pull/3450) [#3307](https://github.com/Leaflet/Leaflet/pull/3307)
* Added `nonBubblingEvents` option to layers (by [@yohanboniface](https://github.com/yohanboniface)). [#3605](https://github.com/Leaflet/Leaflet/pull/3605) [#3307](https://github.com/Leaflet/Leaflet/pull/3307) [#3604](https://github.com/Leaflet/Leaflet/issues/3604)
In addition, layer events implementation was rewritten to use **event delegation**, significantly improving add/remove performance and memory footprint. [#3032](https://github.com/Leaflet/Leaflet/pull/3032) [#46](https://github.com/Leaflet/Leaflet/issues/46)
### TileLayer & Projections API improvements
TileLayer code and everything projections-related has undergone a major refactoring, documented in PR [#2247](https://github.com/Leaflet/Leaflet/pull/2247). It includes the following changes (in addition to much cleaner and simpler code):
#### TileLayer API improvements
These changes make implementing custom grid-like layers for Leaflet much easier.
* Moved most of the `TileLayer` logic into the new `GridLayer` class (which `TileLayer` now inherits, extending it with logic specific to tile servers).
* Removed `TileLayer.Canvas` in favor of the much more flexible and powerful `GridLayer`.
* Improved tile wrapping and bounding logic in `TileLayer` to work transparently and completely depent on the CRS used; removed the need for `TileLayer` hacks when using custom projections.
* Added non-square tiles support (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3573](https://github.com/Leaflet/Leaflet/pull/3573) [#3570](https://github.com/Leaflet/Leaflet/issues/3570)
* Removed `url` property in `tileload` and `tileloadstart` events (get it through `tile.src`).
#### Projections API improvements
These changes were targeted at removing any hardcoded projection-specific logic accross Leaflet classes, making it transparent and driven by the chosen CRS. They make using Leaflet with custom projections (including flat maps) much easier, and remove the need for ugly hacks in the [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
* Added `CRS.Earth` which serves as a base for all Earth-based CRS (`EPSG3857` etc.). [#2345](https://github.com/Leaflet/Leaflet/pull/2345)
* Added `Projection` `bounds` property that defines bounds for different projections (`Mercator`, etc.) in projected coordinates (by [@perliedman](https://github.com/perliedman)).
* Added `CRS` `wrapLat` and `wrapLng` properties which define whether the world wraps on a given axis and in what range. Set as `[-180, 180]` for `EPSG:3857`, `EPSG:4326`, `EPSG:3395` by default.
* Added `CRS` and `Map` `wrapLatLng` (the latter depends on the chosen CRS).
* Added `CRS` `infinite` property that disables boundaries (the world can go on infinitely in any direction), default for `CRS.Simple`.
* Added `CRS` `getProjectedBounds` and `Map` `getPixelWorldBounds` methods that returns world bounds in projected coordinates depending on zoom (derived from `Projection` `bounds`) (by [@perliedman](https://github.com/perliedman)).
* Added `CRS` `unproject` method that converts coordinates in CRS-dependent units into `LatLng` coordinates (by [@sowelie](https://github.com/sowelie)).
* Added `Map` `distance(latlng1, latlng2)` method that takes CRS into account and works with flat projections. [#2345](https://github.com/Leaflet/Leaflet/pull/2345) [#1442](https://github.com/Leaflet/Leaflet/issues/1442)
* Improved `Circle` to be approximated with an ellipse in Earth-based projections. [#2345](https://github.com/Leaflet/Leaflet/pull/2345)
* Fixed `EPSG:4326` to have correct tile pyramid (2x1 tiles at root).
* Fixed `Projection.SphericalMercator` to project to meter units (by [@calvinmetcalf](https://github.com/calvinmetcalf)).
* Fixed `Map` `worldCopyJump` option to work for other projections.
* Fixed `Circle` to work with flat projections. [#2345](https://github.com/Leaflet/Leaflet/pull/2345) [#1465](https://github.com/Leaflet/Leaflet/issues/1465)
* Fixed scale control precision in non-EPSG:3857 projections. [#2345](https://github.com/Leaflet/Leaflet/pull/2345)
* Fixed `ImageOverlay` not working properly in some non-standard projections. [#3011](https://github.com/Leaflet/Leaflet/issues/3011)
### Animations improvements and fractional zoom
Animation code in Leaflet had undergone a major rewrite (main PR: [#2382](https://github.com/Leaflet/Leaflet/pull/2382)), bringing improvements such as:
- Multi-level tile buffering for more seamless zooming experience with less flickering
- Optimal path animation (`map.flyTo`) for zooming to a particular place in a curve (simultaneously zooming and panning)
- Fractional zoom support &mdash; now you can do things like `map.setZoom(12.45)` and it just works.
- Layers now rely on `zoom` event in addition to `viewreset` for frame-by-frame zoom animations. [#3278](https://github.com/Leaflet/Leaflet/pull/3278)
### UX improvements
* Improved panning inertia behavior so that there are no tearing during animation when panning around quickly. [#2360](https://github.com/Leaflet/Leaflet/issues/2360)
* Improved inertial scrolling to have a nicer, more natural feel (by [@RickMohr](https://github.com/RickMohr)). [Leaflet/Leaflet#3063](https://github.com/Leaflet/Leaflet/pull/3063) [#3063](https://github.com/Leaflet/Leaflet/pull/3063) [#3062](https://github.com/Leaflet/Leaflet/issues/3062) [#2987](https://github.com/Leaflet/Leaflet/issues/2987)
* Added popup fade out animation.
* Improved keyboard support to ignore keystrokes with modifier keys (Ctrl, Alt, Meta) to not interfere with OS shortcuts.
* Made popup to close when pressing Escape (by [@snkashis](https://github.com/snkashis)). [#951](https://github.com/Leaflet/Leaflet/issues/951) [#3000](https://github.com/Leaflet/Leaflet/pull/3000)
* Improved keyboard zoom to zoom by 3 levels if shift key is pressed (by [@yohanboniface](https://github.com/yohanboniface)). [#3024](https://github.com/Leaflet/Leaflet/pull/3024)
* Improved layers control to become scrollable when there are too many layers (by [@rowanwins](https://github.com/rowanwins)). [#3571](https://github.com/Leaflet/Leaflet/pull/3571) [#3167](https://github.com/Leaflet/Leaflet/issues/3167)
### Misc. performance improvements
* Significantly [improved](http://jsperf.com/leaflet-parsefloat-in-latlng/2) `LatLng` creation performance (8x).
* Improved performance of layer objects construction.
* Significantly improved `FeatureGroup` (and correspondingly `GeoJSON`) layer adding and events performance (about 10x) by implementing a much better event propagation mechanism. [#2311](https://github.com/Leaflet/Leaflet/pull/2311)
* Improved Leaflet events and DOM events memory footprint.
* Improved `Evented` `off` performance by about 2 times.
### Other breaking API changes
* Changed `DomUtil` `off` to require `context` attribute if you remove a listener that was previously added with a particular context.
* Removed `DomUtil.getViewportOffset` method as it is no longer necessary.
* Removed `DomUtil` `getTranslateString` and `getScaleString` methods.
* Removed `DomUtil` `documentIsLtr` method.
* Removed `LatLng` `RAD_TO_DEG`, `DEG_TO_RAD` and `MAX_MARGIN` constants.
* Renamed `Util.limitExecByInterval` to `Util.throttle`.
### Other API improvements
* Added `Evented` class that now many Leaflet classes inherit from (instead of mixing in events methods in each class), while keeping the old `includes: L.Mixin.Events` syntax available. [#2310](https://github.com/Leaflet/Leaflet/pull/2310)
* Improved `Class` `options` to be prototypically inherited instead of copied over in children classes, which leads to more efficient memory use, faster object construction times and ability to change parent options that get propagated to all children automatically (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2300](https://github.com/Leaflet/Leaflet/pull/2300) [#2294](https://github.com/Leaflet/Leaflet/issues/2294)
* Added `{r}` variable to `TileLayer` `url` for adding `"@2x"` prefix on Retina-enabled devices (by [@urbaniak](https://github.com/urbaniak)). [#2296](https://github.com/Leaflet/Leaflet/pull/2296)
* Added `Point` `ceil` method (by [@perliedman](https://github.com/perliedman)).
* Added `Util.wrapNum` method for wrapping a number to lie in a certain range.
* Improved `L.bind` to use native `Function` `bind` on modern browsers and prepend real arguments to bound ones.
* Added `LatLng` `equals` second argument `maxMargin`.
* Added `DomUtil` `setClass` and `getClass` methods.
* Improved `Marker` `move` event to include `oldLatlng` (by [@danzel](https://github.com/danzel)). [#2412](https://github.com/Leaflet/Leaflet/pull/2412)
* Added `Marker` `mousemove` event (by [@bullgare](https://github.com/bullgare)). [#2798](https://github.com/Leaflet/Leaflet/pull/2798)
* Improved `DomEvent` `on`/`off` to accept space-separated events and object literals (`{type: fn}`).
* Added `DomUtil` `toFront` and `toBack` methods.
* Added `Evented` `listens` as a shortcut to `hasEventListeners`.
* Added optional support for center-oriented scroll for `L.map` option `touchZoom` (by [@mtmail](https://github.com/mtmail)). [#2527](https://github.com/Leaflet/Leaflet/pull/2527)
* Added `getElement` method to all layers that returns an associated DOM element.
* Added `LatLng` `clone` method (by [@yohanboniface](https://github.com/yohanboniface)). [#3390](https://github.com/Leaflet/Leaflet/pull/3390)
* Added `Map` `setMinZoom` and `setMaxZoom` methods (by [@nathancahill](https://github.com/nathancahill)). [#3383](https://github.com/Leaflet/Leaflet/pull/3383) [#3380](https://github.com/Leaflet/Leaflet/issues/3380)
* Added `originalEvent` to `Map` and `Marker` `drag` and `move` event data (by [@yohanboniface](https://github.com/yohanboniface)). [#3264](https://github.com/Leaflet/Leaflet/pull/3264)
* Added `Popup` `autoClose` option (by [@jfirebaugh](https://github.com/jfirebaugh)). [#3018](https://github.com/Leaflet/Leaflet/pull/3018) [#2716](https://github.com/Leaflet/Leaflet/issues/2716) [#1853](https://github.com/Leaflet/Leaflet/issues/1853)
* Added `Control.Zoom` `enable` and `disable` methods. [#3172](https://github.com/Leaflet/Leaflet/issues/3172)
* Added an argument to `L.PolyUtil.clipPolygon` that controls rounding (by [@aparshin](https://github.com/aparshin)). [#2926](https://github.com/Leaflet/Leaflet/pull/2926) [#2917](https://github.com/Leaflet/Leaflet/issues/2917)
* Added `Map` `wheelDebounceTime` option (by [@AndriiHeonia](https://github.com/AndriiHeonia)). [#2836](https://github.com/Leaflet/Leaflet/pull/2836) [#2501](https://github.com/Leaflet/Leaflet/issues/2501)
* Added `TileLayer.WMS` `uppercase` option (now `false` by default) (by [@kdeloach](https://github.com/kdeloach)). [#2831](https://github.com/Leaflet/Leaflet/pull/2831) [#1751](https://github.com/Leaflet/Leaflet/issues/1751)
* Added `ImageOverlay` `alt` option (by [@francis-liberty](https://github.com/francis-liberty)). [#2767](https://github.com/Leaflet/Leaflet/pull/2767) [#2564](https://github.com/Leaflet/Leaflet/issues/2564)
* Improved `L.Util.extend` performance (by [@fab1an](https://github.com/fab1an)). [#2746](https://github.com/Leaflet/Leaflet/pull/2746)
* Added `Map` `stop` method for stopping panning or `flyTo` animation.
* Added `Map` `maxBoundsViscosity` option that controls how much the map resits dragging out of max bounds (set to `1` for a hard limit) (by [@IvanSanchez](https://github.com/IvanSanchez)). [#2224](https://github.com/Leaflet/Leaflet/pull/2224) [#3510](https://github.com/Leaflet/Leaflet/pull/3510) [#1673](https://github.com/Leaflet/Leaflet/issues/1673)
* Added `Bounds` and `LatLngBounds` `overlaps` method (by [@IvanSanchez](https://github.com/IvanSanchez)). [#2981](https://github.com/Leaflet/Leaflet/issues/2981) [#3508](https://github.com/Leaflet/Leaflet/pull/3508)
* Added `ImageOverlay` `crossOrigin` option (by [@patrickarlt](https://github.com/patrickarlt)). [#3594](https://github.com/Leaflet/Leaflet/pull/3594) [#2621](https://github.com/Leaflet/Leaflet/pull/2621)
### Bugfixes
* Fixed `DomUtil` `on` to make it possible to add the same listener to the same element for multiple contexts.
* Fixed a bug that thowed an error when attempting to reinitialize the map on unload (by [@gamb](https://github.com/gamb)). [#2410](https://github.com/Leaflet/Leaflet/pull/2410)
* Fixed excessively blurry non-retina tiles on retina iOS devices.
* Fixed a bug with popup close button in IE7/8. [#2351](https://github.com/Leaflet/Leaflet/issues/2351)
* Fixed `ImageOverlay` mercator distortion on lower zoom levels.
* Fixed a bug where layers didn't fire `popupopen` and `popupclose` events when manually creating a popup object and passing it to `bindPopup`. [#2354](https://github.com/Leaflet/Leaflet/issues/2354)
* Fixed box-zoom overlay appearing under markers. [#1813](https://github.com/Leaflet/Leaflet/issues/1813)
* Fixed `Map` `panInsideBounds` to accept array-form bounds (by [@RLRR](https://github.com/RLRR)). [#3489](https://github.com/Leaflet/Leaflet/pull/3489)
* Fixed marker draggable state to persist when removing and adding back to the map (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3488](https://github.com/Leaflet/Leaflet/pull/3488)
* Fixed inertia not working when parallel to axis (by [@rikvanmechelen](https://github.com/rikvanmechelen)). [#3432](https://github.com/Leaflet/Leaflet/issues/3432)
* Fixed images and SVG inside popups having max-width property overridden (by [@yohanboniface](https://github.com/yohanboniface)). [#3452](https://github.com/Leaflet/Leaflet/pull/3452)
* Fixed cursors when dragging is disabled (by [@juliensoret](https://github.com/juliensoret)). [#3219](https://github.com/Leaflet/Leaflet/issues/3219) [#3233](https://github.com/Leaflet/Leaflet/pull/3233)
* Fixed `LatLng` `wrap` to not drop altitude (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3420](https://github.com/Leaflet/Leaflet/issues/3420)
* Fixed Firefox for Android not being detected as mobile (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3419](https://github.com/Leaflet/Leaflet/pull/3419)
* Fixed an infinite loop when calling `setMaxBounds` several times in a row (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3410](https://github.com/Leaflet/Leaflet/pull/3410) [#3320](https://github.com/Leaflet/Leaflet/issues/3320)
* Fixed a WTF `Map` `locate` bug that could cause some unexpected effects (by [@sindre](https://github.com/sindre)). [Leaflet/Leaflet#3404](https://github.com/Leaflet/Leaflet/issues/3404)
* Fixed broken zoom in Project Spartan browsers (by [@mitchless](https://github.com/mitchless)). [#3379](https://github.com/Leaflet/Leaflet/pull/3379)
* Fixed broken `Polygon` `getCenter` when all points <area></area> in the same pixel (by [@yohanboniface](https://github.com/yohanboniface)). [#3372](https://github.com/Leaflet/Leaflet/pull/3372)
* Fixed broken `Polyline` `getCenter` when the line is very small (by [@yohanboniface](https://github.com/yohanboniface)). [#3371](https://github.com/Leaflet/Leaflet/pull/3371)
* Fixed keyboard zoom out not working on `azerty` keyboards. [#3362](https://github.com/Leaflet/Leaflet/issues/3362)
* Fixed a bug where tabbing to an offscreen marker broke control positioning (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3340](https://github.com/Leaflet/Leaflet/pull/3340) [#3333](https://github.com/Leaflet/Leaflet/issues/3333)
* Fixed `Map` `panBy` not always returning `this` (by [@Trufi](https://github.com/Trufi)). [#3303](https://github.com/Leaflet/Leaflet/pull/3303)
* Fixed a bug where pushing a zoom control with a keyboard would switch focus to the map (by [@Jakobud](https://github.com/Jakobud)). [#3275](https://github.com/Leaflet/Leaflet/pull/3275) [#3159](https://github.com/Leaflet/Leaflet/issues/3159)
* Fixed more race conditions leading to map freeze. [#3270](https://github.com/Leaflet/Leaflet/pull/3270) [#3268](https://github.com/Leaflet/Leaflet/issues/3268) [#2693](https://github.com/Leaflet/Leaflet/issues/2693) [#2478](https://github.com/Leaflet/Leaflet/issues/2478)
* Fixed iOS race condition when doing a very small pan. [#2519](https://github.com/Leaflet/Leaflet/issues/2519)
* Fixed `Polygon` `toGeoJSON` exporting a `MultiPolygon` in some cases (by [@yohanboniface](https://github.com/yohanboniface)). [#2977](https://github.com/Leaflet/Leaflet/pull/2977)
* Fixed `TileLayer.WMS` passing all options as WMS params. [#2972](https://github.com/Leaflet/Leaflet/issues/2972)
* Fixed a race condition that made `map.fitBounds(map.getBounds())` zoom out sometimes. [#2957](https://github.com/Leaflet/Leaflet/issues/2957)
* Fixed a bug where a draggable marker stopped being draggable after `setIcon` (by [@jeffsmale90](https://github.com/jeffsmale90)). [#2578](https://github.com/Leaflet/Leaflet/issues/2578)
* Fixed some IE11 touch performance and stability issues (by [@danzel](https://github.com/danzel)). [#3007](https://github.com/Leaflet/Leaflet/pull/3007) [#2820](https://github.com/Leaflet/Leaflet/issues/2820)
* Fixed a race condition with `TileLayer` when `maxNativeZoom = 0`. [#3004](https://github.com/Leaflet/Leaflet/issues/3004)
* Fixed `FeatureGroup` `setStyle` not affecting `ImageOverlay` `opacity` (by [@opie](https://github.com/opie)). [#3008](https://github.com/Leaflet/Leaflet/pull/3008) [#2246](https://github.com/Leaflet/Leaflet/issues/2246)
* Fixed class init hooks not being called for parent classes (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2545](https://github.com/Leaflet/Leaflet/issues/2545)
* Fixed a bug when adding a control that's already added or removing a control that's not present (by [@jfirebaugh](https://github.com/jfirebaugh)). [#3020](https://github.com/Leaflet/Leaflet/issues/3020) [#2877](https://github.com/Leaflet/Leaflet/issues/2877)
* Fixed buggy inertia on low zoom levels when `Map` `worldCopyJump` option is enabled (by [@flemenach](https://github.com/flemenach)). [#3038](https://github.com/Leaflet/Leaflet/pull/3038) [#2624](https://github.com/Leaflet/Leaflet/issues/2624)
* Fixed a bug where `getMaxZoom` sometimes returned wrong result inside a `Control.Layers` `baselayerchange` event (by [@CloudNiner](https://github.com/CloudNiner)). [#3048](https://github.com/Leaflet/Leaflet/pull/3048) [#2318](https://github.com/Leaflet/Leaflet/issues/2318)
* Fixed a bug where during zooming one map, dragging was disabled on all other maps on the page (by [@kdeloach](https://github.com/kdeloach)). [#3050](https://github.com/Leaflet/Leaflet/pull/3050) [#2539](https://github.com/Leaflet/Leaflet/issues/2539)
* Fixed a `Map` `getSize` race condition where it could return a wrong result (by [@kshepard](https://github.com/kshepard)). [#3052](https://github.com/Leaflet/Leaflet/pull/3052) [#3031](https://github.com/Leaflet/Leaflet/issues/3031)
* Fixed `L.latLng` factory to accept `altitude` as a third argument too (by [@lonelyelk](https://github.com/lonelyelk)). [#3070](https://github.com/Leaflet/Leaflet/pull/3070).
* Fixed `L.latLng` factory to not throw error on empty array input (by [@lonelyelk](https://github.com/lonelyelk)). [#3080](https://github.com/Leaflet/Leaflet/pull/3080) [#3079](https://github.com/Leaflet/Leaflet/issues/3079)
* Fixed popups to have a default color (by [@snkashis](https://github.com/snkashis)). [#3115](https://github.com/Leaflet/Leaflet/pull/3115) [#3112](https://github.com/Leaflet/Leaflet/issues/3112) [#333](https://github.com/Leaflet/Leaflet/issues/333)
* Fixed `Map` `locate` not to mutate the given options object (by [@tmcw](https://github.com/tmcw)). [#3173](https://github.com/Leaflet/Leaflet/pull/3173)
* Fixed Canvas polylines not to render if stroke is set to 0 (by [@optical](https://github.com/optical)). [#3203](https://github.com/Leaflet/Leaflet/pull/3203)
* Prevented a Joomla bug that could lead to vector layers disappearing (by [@SniperSister](https://github.com/SniperSister)). [#2886](https://github.com/Leaflet/Leaflet/pull/2886) [#2881](https://github.com/Leaflet/Leaflet/issues/2881) [Leaflet/Leaflet#2881](https://github.com/Leaflet/Leaflet/issues/2881)
* Fixed IE Metro touch events passing through objects on top of a Leaflet map (by [@nikgavalas](https://github.com/nikgavalas)). [#2954](https://github.com/Leaflet/Leaflet/pull/2954)
* Fixed a bug where vector layers wouldn't display on websites with SVG max-width set to 100% (by [@SniperSister](https://github.com/SniperSister)). [#2886](https://github.com/Leaflet/Leaflet/pull/2886) [#2881](https://github.com/Leaflet/Leaflet/issues/2881)
* Fixed `Marker` `setIcon` to not reset popup anchor offset (by [@bullgare](https://github.com/bullgare)). [#2872](https://github.com/Leaflet/Leaflet/pull/2872)
* Fixed a case where you provide `zoom` in `Map` options and then set center (by [@AndriiHeonia](https://github.com/AndriiHeonia)). [#2849](https://github.com/Leaflet/Leaflet/pull/2849) [#1871](https://github.com/Leaflet/Leaflet/issues/1871)
* Fixed a race condition with `TileLayer` `setUrl` where `loading` event didn't fire properly (by [@bennlich](https://github.com/bennlich)). [#2802](https://github.com/Leaflet/Leaflet/pull/2802) [#2675](https://github.com/Leaflet/Leaflet/issues/2675) [#2827](https://github.com/Leaflet/Leaflet/pull/2827)
* Fixed a bug where a tile layer was not cleaned up properly after zooming past its max zoom (by [@francis-liberty](https://github.com/francis-liberty)). [#2764](https://github.com/Leaflet/Leaflet/pull/2764) [#1905](https://github.com/Leaflet/Leaflet/issues/1905)
* Fixed a memory leak with `Marker` `setIcon` (by [@jfgodoy](https://github.com/jfgodoy)). [#2811](https://github.com/Leaflet/Leaflet/pull/2811)
* Fixed `LatLng` `distanceTo` to not return `NaN` on equal locations. [#2705](https://github.com/Leaflet/Leaflet/issues/2705)
* Fixed a bug where `TileLayer` `maxNativeZoom` didn't work properly with `bounds` option set (by [@mlegenhausen](https://github.com/mlegenhausen)). [#2725](https://github.com/Leaflet/Leaflet/pull/2725) [#2704](https://github.com/Leaflet/Leaflet/issues/2704)
* Fixed a bug where tiles touching the edge of the bounds from outside where loaded (by [@IvanSanchez](https://github.com/IvanSanchez)). [#2981](https://github.com/Leaflet/Leaflet/issues/2981) [#3508](https://github.com/Leaflet/Leaflet/pull/3508)
* Fixed a memory leak in `map.remove()` (by [@IvanSanchez](https://github.com/IvanSanchez)). [#3514](https://github.com/Leaflet/Leaflet/pull/3514) [#2718](https://github.com/Leaflet/Leaflet/issues/2718)
* Fixed a bug where layers control collapsed when hovering over a disabled radio button (by [@rowanwins](https://github.com/rowanwins)). [#3564](https://github.com/Leaflet/Leaflet/pull/3564) [#2771](https://github.com/Leaflet/Leaflet/issues/2771) [Leaflet#2771](https://github.com/Leaflet/Leaflet/issues/2771)
* Fixed GeoJSON not accepting null geometry (by [@pglotov](https://github.com/pglotov)). [#3585](https://github.com/Leaflet/Leaflet/pull/3585)
* Fixed occasional popup 1px horizontal offset (by [@patrickarlt](https://github.com/patrickarlt)). [#3587](https://github.com/Leaflet/Leaflet/pull/3587) [#3526](https://github.com/Leaflet/Leaflet/pull/3526) [#3586](https://github.com/Leaflet/Leaflet/issues/3586) [#3525](https://github.com/Leaflet/Leaflet/issues/3525)
* Fixed some inconsistencies in mouse events for Canvas vector layers (by [@wpf500](https://github.com/wpf500)). [#3590](https://github.com/Leaflet/Leaflet/pull/3590) [#3568](https://github.com/Leaflet/Leaflet/issues/3568) [#3618](https://github.com/Leaflet/Leaflet/pull/3618)
* Fixed scale control for non-earth CRS (by [@cmulders](https://github.com/cmulders)). [#3614](https://github.com/Leaflet/Leaflet/pull/3614)
### Misc improvements
* Improved the build system (`jake build`) to report gzipped library size.
* Added version name and commit number to Leaflet builds. [#2276](https://github.com/Leaflet/Leaflet/issues/2276)
* Added Leaflet logo in vector format as `src/images/logo.svg`.
* Added reference to Leaflet CSS in `package.json` (by [@bclinkinbeard](https://github.com/bclinkinbeard)). [#2432](https://github.com/Leaflet/Leaflet/pull/2432)
* Switched from JSHint to ESLint for checking code style and made the checks more strict. [#3176](https://github.com/Leaflet/Leaflet/pull/3176)
* Added a plugin guide section on module loaders and publishing plugins (by [@patrickarlt](https://github.com/patrickarlt)). [#2955](https://github.com/Leaflet/Leaflet/pull/2955) [Leaflet/Leaflet#2955](https://github.com/Leaflet/Leaflet/pull/2955)
* Fixed the build system to work in non-git Leaflet folder (by [@cschwarz](https://github.com/cschwarz)). [#2758](https://github.com/Leaflet/Leaflet/pull/2758)
## 0.7.7 (2015-10-26)
* Fixed a regression that could sometimes cause tiles to disappear when pinch-zooming on iOS devices.
* Fixed a regression related to msPointer detection in IE10 (affecting Leaflet.draw and some other plugins) (by @danzel) #3842 #3839 #3804
* Fixed a bug where a mouseout could fire after a vector element was removed (by @sambernet). #3849 #3829
* Fixed touch interactions in Edge browser (by @mitchless & @Neorth). #3853 #3379
* Fixed a bug where removing a layer group from a feature group would throw an error (by @Lambdac0re). #3869
Note tha we skipped 0.7.6 version for which we accidentally published a broken build to NPM.
## 0.7.5 (2015-09-02)
* Fixed a regression that broke animation in Leaflet.markercluster. [#3796](https://github.com/Leaflet/Leaflet/issues/3796)
## 0.7.4 (2015-09-01)
* Fixed a bug introduced by a recent Chrome update that caused the map not to react to interactions until all tiles are loaded. [#3689](https://github.com/Leaflet/Leaflet/issues/3689)
* Fixed a bug where tile layers were not respecting their `minZoom` or `maxZoom` options (by [@ivansanchez](https://github.com/ivansanchez)) [#3778](https://github.com/Leaflet/Leaflet/pull/3778)
* Improved detection of touch-capable web browsers, in particular fixing touch interactions in device emulation mode in Chrome and Firefox (by [@ivansanchez](https://github.com/ivansanchez)) [#3520](https://github.com/Leaflet/Leaflet/pull/3520) [#3710](https://github.com/Leaflet/Leaflet/pull/3710)
* Fixed a bug where tile layers with `maxNativeZoom` were not checking bounds properly (by [@jlaxon](https://github.com/jlaxon)) [#3415](https://github.com/Leaflet/Leaflet/pull/3152)
* Fixed a bug where popups were not remembering which layer originated them after calling `bindPopup(popup)` (by [@Eschon](https://github.com/Eschon)) [#3675](https://github.com/Leaflet/Leaflet/pull/3675)
* Fixed a race condition between markers and zoom animation (by [@perliedman](https://github.com/perliedman)) [#3152](https://github.com/Leaflet/Leaflet/pull/3152)
* Fixed a bug with `Map.fitBounds` padding (by [@bsstoner](https://github.com/bsstoner)). [#2489](https://github.com/Leaflet/Leaflet/issues/2489)
* Allow canvas paths to fire `dblclick` and `contextmenu` events (by [@ivansanchez](https://github.com/ivansanchez)) [#3482](https://github.com/Leaflet/Leaflet/pull/3482)
* Allow canvas filled polygons to use the `fillRule` option (by [@w8r](https://github.com/w8r)) [#3477](https://github.com/Leaflet/Leaflet/pull/3477)
* Allow canvas lines to use `lineCap` and `lineJoin` options (by [@northerneyes](https://github.com/northerneyes)) [#3156](https://github.com/Leaflet/Leaflet/pull/3156)
* Updated bower definitions to comply with best practices (by [@guptaso2](https://github.com/guptaso2)) [#2726](https://github.com/Leaflet/Leaflet/pull/2726) [#3448](https://github.com/Leaflet/Leaflet/pull/3448)
## 0.7.3 (2014-05-23)
* Added proper **bower** and **component** support (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#2561](https://github.com/Leaflet/Leaflet/pull/2561) [#1903](https://github.com/Leaflet/Leaflet/issues/1903)
* Fixed a bug where dragging the map outside the window caused an error on FF. [#2610](https://github.com/Leaflet/Leaflet/issues/2610)
* Fixed a bug where some taps on Android where not working, often falsely perceived as drags (by [@axefrog](https://github.com/axefrog)). [#2503](https://github.com/Leaflet/Leaflet/pull/2503)
* Fixed a bug where clicks on Android were skipped when happened too fast. [#2303](https://github.com/Leaflet/Leaflet/issues/2303)
* Fixed a bug where calling `setView` (or similar methods) several times in succession could freeze the map. [#2521](https://github.com/Leaflet/Leaflet/issues/2521) [#2236](https://github.com/Leaflet/Leaflet/issues/2236) [#2485](https://github.com/Leaflet/Leaflet/issues/2485)
* Fixed a bug where `Control.Layers` wasn't properly removed (by [@jack-kerouac](https://github.com/jack-kerouac)). [#2569](https://github.com/Leaflet/Leaflet/pull/2569)
* Fixed a bug that caused `TileLayer` `load` event not to fire properly. [#2510](https://github.com/Leaflet/Leaflet/issues/2510)
* Fixed Canvas-based paths not triggering `remove` event when removed (by @adimitrov). [#2486](https://github.com/Leaflet/Leaflet/pull/2486)
* Fixed a bug where you could end up with fractional zoom after pinch-zooming in some cases (by [@danzel](https://github.com/danzel). [#2400](https://github.com/Leaflet/Leaflet/pull/2400) [#1943](https://github.com/Leaflet/Leaflet/issues/1934)
## 0.7.2 (2014-01-17)
* Fixed a bug that appeared with **Chrome 32 update** that made all **mouse events shifted on scrolled pages**. [#2352](https://github.com/Leaflet/Leaflet/issues/2352)
* Fixed a bug that could break tile loading when using non-standard tile sizes. [#2288](https://github.com/Leaflet/Leaflet/issues/2288)
* Fixed a bug where `maxNativeZoom` option didn't work with TMS tile layers (by [@perliedman](https://github.com/perliedman)). [#2321](https://github.com/Leaflet/Leaflet/issues/2321) [#2314](https://github.com/Leaflet/Leaflet/issues/2314)
## 0.7.1 (2013-12-06)
* Fixed a bug where pinch-zooming quickly could occasionally leave the map at a fractional zoom thus breaking tile loading (by [@danzel](https://github.com/danzel)). [#2269](https://github.com/Leaflet/Leaflet/pull/2269) [#2255](https://github.com/Leaflet/Leaflet/issues/2255) [#1934](https://github.com/Leaflet/Leaflet/issues/1934)
* Fixed tile flickering issues on some old Android 2/3 devices by disabling buggy hardware accelerated CSS there (by [@danzel](https://github.com/danzel)). [#2216](https://github.com/Leaflet/Leaflet/pull/2216) [#2198](https://github.com/Leaflet/Leaflet/issues/2198)
* Fixed a regression that sometimes led to an error when you started pinch-zoom as the first action on the map (by [@danzel](https://github.com/danzel)). [#2259](https://github.com/Leaflet/Leaflet/pull/2259) [#2256](https://github.com/Leaflet/Leaflet/issues/2256)
* Reverted `L.Util.template` optimizations to allow Leaflet to be used in eval-banning environments like browser extensions (performance gain was insignificant anyway) (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#2217](https://github.com/Leaflet/Leaflet/issues/2217) [#2209](https://github.com/Leaflet/Leaflet/issues/2209) [#1968](https://github.com/Leaflet/Leaflet/issues/1968)
* Fixed a regression where `Map` `invalidateSize` could lead to unexpected results when called before map initialization (by [@Zverik](https://github.com/Zverik)). [#2250](https://github.com/Leaflet/Leaflet/issues/2250) [#2249](https://github.com/Leaflet/Leaflet/issues/2249)
* Fixed a regression where `FeatureGroup` containing other group overwrote original `e.layer` value in mouse events (by [@gumballhead](https://github.com/gumballhead)). [#2253](https://github.com/Leaflet/Leaflet/pull/2253) [#2252](https://github.com/Leaflet/Leaflet/issues/2252)
* Fixed a regression where `CircleMarker` `setLatLngs` didn't return `this` (by [@radicalbiscuit](https://github.com/radicalbiscuit)). [#2206](https://github.com/Leaflet/Leaflet/pull/2206)
## 0.7 (2013-11-18)
### Improvements
#### Usability improvements
* Added **support for IE11 touch devices** (by [@danzel](https://github.com/danzel), [@DanielX2](https://github.com/DanielX2) and [@fnicollet](https://github.com/fnicollet)). [#2039](https://github.com/Leaflet/Leaflet/pull/2039) [#2066](https://github.com/Leaflet/Leaflet/pull/2066) [#2037](https://github.com/Leaflet/Leaflet/issues/2037) [#2102](https://github.com/Leaflet/Leaflet/issues/2102)
* Added shift-double-click to zoom out shortcut. [#2185](https://github.com/Leaflet/Leaflet/issues/2185)
* Significantly improved **controls design on mobile** devices. [#1868](https://github.com/Leaflet/Leaflet/issues/1868) [#2012](https://github.com/Leaflet/Leaflet/issues/2012)
* Fixed and improved IE7-8 control and popup styles.
* Made subtle improvements to control styles on desktop browsers.
* Improved keyboard nav support so that map doesn't loose focus when you click on a control (by [@jacobtoye](https://github.com/jacobtoye)). [#2150](https://github.com/Leaflet/Leaflet/issues/2150) [#2148](https://github.com/Leaflet/Leaflet/issues/2148)
* Improved `maxBounds` behavior: now it doesn't force higher minimal zoom, and anchors to max bounds edges properly when zooming (by [@kapouer](https://github.com/kapouer) and [@mourner](https://github.com/mourner)). [#2187](https://github.com/Leaflet/Leaflet/pull/2187) [#1946](https://github.com/Leaflet/Leaflet/pull/1946) [#2081](https://github.com/Leaflet/Leaflet/issues/2081) [#2168](https://github.com/Leaflet/Leaflet/issues/2168) [#1908](https://github.com/Leaflet/Leaflet/issues/1908)
#### Map API improvements
* Made `Map` `setView` `zoom` argument optional. [#2056](https://github.com/Leaflet/Leaflet/issues/2056)
* Added `maxZoom` option to `Map` `fitBounds`. [#2101](https://github.com/Leaflet/Leaflet/issues/2101)
* Added `Map` `bounceAtZoomLimits` option that makes the map bounce when you pinch-zoom past limits (it worked like this before, but now you can disable this) (by [@trevorpowell](https://github.com/trevorpowell)). [#1864](https://github.com/Leaflet/Leaflet/issues/1864) [#2072](https://github.com/Leaflet/Leaflet/pull/2072)
* Added `distance` property to `Map` and `Marker` `dragend` events. [#2158](https://github.com/Leaflet/Leaflet/issues/2158) [#872](https://github.com/Leaflet/Leaflet/issues/872)
* Added optional support for center-oriented scroll and double-click zoom (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1939](https://github.com/Leaflet/Leaflet/issues/1939)
* Added `timestamp` to `Map` `locationfound` event. [#584](https://github.com/Leaflet/Leaflet/pull/584)
* Improved `Map` `invalidateSize` to call `moveend` immediately unless given `debounceMoveend: true` option (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2181](https://github.com/Leaflet/Leaflet/issues/2181)
#### TileLayer API improvements
* Added `TileLayer` `maxNativeZoom` option that allows displaying tile layers on zoom levels above their maximum by **upscaling tiles**. [#1802](https://github.com/Leaflet/Leaflet/issues/1802) [#1798](https://github.com/Leaflet/Leaflet/issues/1798)
* Added `TileLayer` `tileloadstart` event (by [@tmcw](https://github.com/tmcw)). [#2142](https://github.com/Leaflet/Leaflet/pull/2142) [#2140](https://github.com/Leaflet/Leaflet/issues/2140)
* Improved `TileLayer` world size (used for wrapping and limiting tiles) to be derived from CRS instead of hardcoded, making it easier to use with custom projections (by [@perliedman](https://github.com/perliedman)). [#2160](https://github.com/Leaflet/Leaflet/pull/2160)
#### Marker API improvements
* Added CSS classes to draggable markers for easier customization (by [@snkashis](https://github.com/snkashis)). [#1902](https://github.com/Leaflet/Leaflet/issues/1902) [#1916](https://github.com/Leaflet/Leaflet/issues/1916)
* Added `Marker` `add` event (by [@tohaocean](https://github.com/tohaocean)). [#1942](https://github.com/Leaflet/Leaflet/issues/1942)
* Added `Marker` `getPopup` method (by [@scottharvey](https://github.com/scottharvey)). [#618](https://github.com/Leaflet/Leaflet/issues/618) [#1197](https://github.com/Leaflet/Leaflet/pull/1197)
* Added `Marker` `alt` option for adding `alt` text to markers (by [@jimmytidey](https://github.com/jimmytidey)). [#2112](https://github.com/Leaflet/Leaflet/pull/2112)
#### Vector layers API improvements
* Added `Path` `className` option for adding custom class names to vector layers.
* Added `Path` `lineCap` and `lineJoin` options (by [@palewire](https://github.com/palewire)). [#1843](https://github.com/Leaflet/Leaflet/issues/1843) [#1863](https://github.com/Leaflet/Leaflet/issues/1863) [#1881](https://github.com/Leaflet/Leaflet/issues/1881)
* Added ability to pass vector options to `GeoJSON` (by [@kapouer](https://github.com/kapouer)). [#2075](https://github.com/Leaflet/Leaflet/pull/2075)
* Improved `Polygon` `setLatLngs` to also accept holes (by [@aparshin](https://github.com/aparshin)). [#2095](https://github.com/Leaflet/Leaflet/pull/2095) [#1518](https://github.com/Leaflet/Leaflet/issues/1518)
* Added `GeoJSON` 3D format support and optional `altitude` argument to `LatLng` constructor (by [@Starefossen](https://github.com/Starefossen)). [#1822](https://github.com/Leaflet/Leaflet/pull/1822)
* Added `MultiPolygon` and `MultiPolyline` `openPopup` method. [#2046](https://github.com/Leaflet/Leaflet/issues/2046)
#### Popup API improvements
* Added `Popup` `update` method. [#1959](https://github.com/Leaflet/Leaflet/issues/1959)
* Added `Popup` `autoPanPaddingTopLeft` and `autoPanPaddingBottomRight` options (by [@albburtsev](https://github.com/albburtsev)). [#1972](https://github.com/Leaflet/Leaflet/issues/1972) [#1588](https://github.com/Leaflet/Leaflet/issues/1588)
* Added `Popup` `getContent` method. [#2100](https://github.com/Leaflet/Leaflet/issues/2100)
* Added `Popup` `getLatLng` method (by [@AndreyGeonya](https://github.com/AndreyGeonya)). [#2097](https://github.com/Leaflet/Leaflet/pull/2097)
#### Misc API improvements
* Added `ImageOverlay` `setUrl` and `getAttribution` methods and `attribution` option (by [@stsydow](https://github.com/stsydow)). [#1957](https://github.com/Leaflet/Leaflet/issues/1957) [#1958](https://github.com/Leaflet/Leaflet/issues/1958)
* Added localization support for the zoom control (by [@Danielku15](https://github.com/Danielku15)). [#1953](https://github.com/Leaflet/Leaflet/issues/1953) [#1643](https://github.com/Leaflet/Leaflet/issues/1643) [#1953](https://github.com/Leaflet/Leaflet/pull/1953)
* Significantly improved `L.Util.template` performance (affects `L.TileLayer`) by introducing cached template compilation (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1969](https://github.com/Leaflet/Leaflet/issues/1969) [#1968](https://github.com/Leaflet/Leaflet/issues/1968) [#1554](https://github.com/Leaflet/Leaflet/issues/1554)
* Added `CRS` `getSize` for getting the world size in pixels (by [@perliedman](https://github.com/perliedman)). [#2160](https://github.com/Leaflet/Leaflet/pull/2160)
* Added `leaflet-drag-target` CSS class to an element under cursor when dragging for more flexible customization. [#2164](https://github.com/Leaflet/Leaflet/issues/2164) [#1902](https://github.com/Leaflet/Leaflet/issues/1902)
* Improved `L.DomUtil` `addClass`, `removeClass`, `hasClass` methods performance and fixed it to work with SVG elements. [#2164](https://github.com/Leaflet/Leaflet/issues/2164)
#### Dev workflow improvements
* Added an [official FAQ](https://github.com/Leaflet/Leaflet/blob/main/FAQ.md).
* Cleaned up and moved old IE styles to `leaflet.css` and removed `leaflet.ie.css`, so **no need for IE conditional comment** when including Leaflet now. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Added `leaflet-oldie` CSS class to map container in IE7-8 for easier styling. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Officially **dropped support for IE6**. Nobody cares anyway, and Leaflet should still be accessible on it. [#2159](https://github.com/Leaflet/Leaflet/issues/2159)
* Improved the build system to check JS errors in spec files. [#2151](https://github.com/Leaflet/Leaflet/issues/2151)
* Fixed `jake` command to run tests before building the source. [#2151](https://github.com/Leaflet/Leaflet/issues/2151)
* Switched the main file in `package.json` to unminified version for NPM/Browserify (by [@icetan](https://github.com/icetan)). [#2109](https://github.com/Leaflet/Leaflet/pull/2109)
### Bugfixes
#### 0.6 regression fixes
* Fixed a **memory leak in iOS7** that could crash Safari when handling lots of objects (e.g. 1000 markers) (by [@danzel](https://github.com/danzel)). [#2149](https://github.com/Leaflet/Leaflet/pull/2149) [#2122](https://github.com/Leaflet/Leaflet/issues/2122)
* Fixed a bug that caused lag at the beginning of panning in Chrome (by [@jfirebaugh](https://github.com/jfirebaugh)). [#2163](https://github.com/Leaflet/Leaflet/issues/2163)
* Fixed a regression that made the layers control unscrollable in Firefox. [#2029](https://github.com/Leaflet/Leaflet/issues/2029)
* Fixed a regression that broke `worldCopyJump: true` option (by [@fastrde](https://github.com/fastrde)). [#1904](https://github.com/Leaflet/Leaflet/issues/1904) [#1831](https://github.com/Leaflet/Leaflet/issues/1831) [#1982](https://github.com/Leaflet/Leaflet/issues/1982)
* Fixed a regression where a first map click after popup close button click was ignored (by [@fastrde](https://github.com/fastrde)). [#1537](https://github.com/Leaflet/Leaflet/issues/1537) [#1963](https://github.com/Leaflet/Leaflet/issues/1963) [#1925](https://github.com/Leaflet/Leaflet/issues/1925)
* Fixed a regression where `L.DomUtil.getMousePosition` would throw an error if container argument not provided (by [@scooterw](https://github.com/scooterw)). [#1826](https://github.com/Leaflet/Leaflet/issues/1826) [#1928](https://github.com/Leaflet/Leaflet/issues/1928) [#1926](https://github.com/Leaflet/Leaflet/issues/1926)
* Fixed a regression with vector layers positioning when zooming on IE10+ touch devices (by [@danzel](https://github.com/danzel)). [#2002](https://github.com/Leaflet/Leaflet/issues/2002) [#2000](https://github.com/Leaflet/Leaflet/issues/2000)
* Fixed a regression with `maxBounds` behaving weirdly on panning inertia out of bounds. [#2168](https://github.com/Leaflet/Leaflet/issues/2168)
#### General bugfixes
* Fixed a bug where the map could freeze if centered and immediately recentered on initialization. [#2071](https://github.com/Leaflet/Leaflet/issues/2071)
* Fixed a bug where all maps except the first one on a page didn't track window resize. [#1980](https://github.com/Leaflet/Leaflet/issues/1980)
* Fixed a bug where tiles in `EPSG:3395` projection were shifted (by [@aparshin](https://github.com/aparshin)). [#2020](https://github.com/Leaflet/Leaflet/issues/2020)
* Fixed a bug where scale control displayed wrong scale when on pages with `box-sizing: border-box`.
* Fixed a bug where zoom control button didn't appear as disabled if map was initialized at the zoom limit. [#2083](https://github.com/Leaflet/Leaflet/issues/2083)
* Fixed a bug where box zoom also triggered a map click event (by [@fastrde](https://github.com/fastrde)). [#1951](https://github.com/Leaflet/Leaflet/issues/1951) [#1884](https://github.com/Leaflet/Leaflet/issues/1884)
* Fixed a bug where shift-clicking on a map immediately after a drag didn't trigger a click event (by [@fastrde](https://github.com/fastrde)). [#1952](https://github.com/Leaflet/Leaflet/issues/1952) [#1950](https://github.com/Leaflet/Leaflet/issues/1950)
* Fixed a bug where content was updated twice when opening a popup. [#2137](https://github.com/Leaflet/Leaflet/issues/2137)
* Fixed a bug that could sometimes cause infinite panning loop when using `maxBounds` (by [@kapouer](https://github.com/kapouer) and [@mourner](https://github.com/mourner)). [#2187](https://github.com/Leaflet/Leaflet/pull/2187) [#1946](https://github.com/Leaflet/Leaflet/pull/1946) [#2081](https://github.com/Leaflet/Leaflet/issues/2081) [#2168](https://github.com/Leaflet/Leaflet/issues/2168) [#1908](https://github.com/Leaflet/Leaflet/issues/1908)
#### Browser bugfixes
* Fixed a bug where keyboard `+` no longer zoomed the map on FF 22+ (by [@fastrde](https://github.com/fastrde)). [#1943](https://github.com/Leaflet/Leaflet/issues/1943) [#1981](https://github.com/Leaflet/Leaflet/issues/1981)
* Fixed a bug where calling `Map` `remove` throwed an error in IE6-8. [#2015](https://github.com/Leaflet/Leaflet/issues/2015)
* Fixed a bug where `isArray` didn't work in rare cases in IE9. [#2077](https://github.com/Leaflet/Leaflet/issues/2077)
* Fixed a bug where FF sometimes produced console warnings when animating markers. [#2090](https://github.com/Leaflet/Leaflet/issues/2090)
* Fixed a bug where mouse wasn't handled correctly on RTL pages in some cases (by [@danzel](https://github.com/danzel)). [#1986](https://github.com/Leaflet/Leaflet/issues/1986) [#2136](https://github.com/Leaflet/Leaflet/pull/2136)
#### Mobile bugfixes
* Fixed a bug where tiles could **disappear after zooming on Chrome 30+ for Android** (by [@danzel](https://github.com/danzel)). [#2152](https://github.com/Leaflet/Leaflet/pull/2152) [#2078](https://github.com/Leaflet/Leaflet/issues/2078)
* Fixed a bug on IE10+ touch where pinch-zoom also caused click (by [@danzel](https://github.com/danzel)). [#2117](https://github.com/Leaflet/Leaflet/pull/2117) [#2094](https://github.com/Leaflet/Leaflet/issues/2094)
* Fixed a bug on IE10+ touch where controls didn't loose the pressed state after tapping (by [@danzel](https://github.com/danzel)). [#2111](https://github.com/Leaflet/Leaflet/pull/2111) [#2103](https://github.com/Leaflet/Leaflet/issues/2103)
* Fixed a bug where clicking on layers control labels on iOS throwed an error (by [@olemarkus](https://github.com/olemarkus) and [@dagjomar](https://github.com/dagjomar)). [#1984](https://github.com/Leaflet/Leaflet/issues/1984) [#1989](https://github.com/Leaflet/Leaflet/issues/1989)
#### Map API bugfixes
* Fixed a bug where `Map` `getCenter` returned old result after map container size changed (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1940](https://github.com/Leaflet/Leaflet/issues/1940) [#1919](https://github.com/Leaflet/Leaflet/issues/1919)
* Fixed `Map` `invalidateSize` rounding issues when changing map size by an odd pixel amount (by [@russelldavis](https://github.com/russelldavis)). [#1931](https://github.com/Leaflet/Leaflet/issues/1931)
* Fixed a bug where `Map` `removeLayer` didn't return `this` in a corner case (by [@jfirebaugh](https://github.com/jfirebaugh)).
* Fixed a bug where calling `Map` `setZoom` before `setView` would throw an error. [#1449](https://github.com/Leaflet/Leaflet/issues/1449)
#### Layers API bugfixes
* Fixed a bug where `Popup` `setLatLng` unnecessarily reset content and updated layout; works much faster now. [#2167](https://github.com/Leaflet/Leaflet/issues/2167)
* Fixed a bug where `toGeoJSON` of layers originated from GeoJSON GeometryCollection and MultiPoint didn't work properly (wasn't round-tripped). [#1956](https://github.com/Leaflet/Leaflet/issues/1956)
* Fixed `GeoJSON` dependencies in build configuration that could lead to a broken custom build in some situations (by [@alubchuk](https://github.com/alubchuk)). [#1909](https://github.com/Leaflet/Leaflet/issues/1909)
* Fixed a bug where `CircleMarker` popup placement wasn't updated after calling `setLatLng` (by [@snkashis](https://github.com/snkashis)). [#1921](https://github.com/Leaflet/Leaflet/issues/1921) [#1927](https://github.com/Leaflet/Leaflet/issues/1927)
* Fixed a bug where popup anchor wasn't updated on `Marker` `setIcon` (by [@snkashis](https://github.com/snkashis)). [#1874](https://github.com/Leaflet/Leaflet/issues/1874) [#1891](https://github.com/Leaflet/Leaflet/issues/1891)
* Fixed a bug with GeoJSON not throwing a descriptive error if a polygon has zero length inner ring (by [@snkashis](https://github.com/snkashis)). [#1917](https://github.com/Leaflet/Leaflet/issues/1917) [#1918](https://github.com/Leaflet/Leaflet/issues/1918)
* Fixed a bug where `FeatureGroup` would break when using non-evented children layers (by [@tmcw](https://github.com/tmcw)). [#2032](https://github.com/Leaflet/Leaflet/pull/2032) [#1962](https://github.com/Leaflet/Leaflet/issues/1962)
* Fixed a bug where `CircleMarker` `getRadius` would always return `null`. [#2016](https://github.com/Leaflet/Leaflet/issues/2016) [#2017](https://github.com/Leaflet/Leaflet/pull/2017)
* Fixed a bug where `TileLayer.WMS` didn't work with WMS 1.3 & EPSG4326 projection (by [@Bobboya](https://github.com/Bobboya)). [#1897](https://github.com/Leaflet/Leaflet/pull/1897)
* Fixed a bug where `FeatureGroup` events `e.layer` was sometimes empty in old IE. [#1938](https://github.com/Leaflet/Leaflet/issues/1938)
#### Misc API bugfixes
* Fixed a bug where `L.latLngBounds` didn't accept simple object `LatLng` form (by [@Gnurfos](https://github.com/Gnurfos)). [#2025](https://github.com/Leaflet/Leaflet/issues/2025) [#1915](https://github.com/Leaflet/Leaflet/issues/1915)
* Fixed a bug where `L.Util.tempalate` wouldn't work with double quotes in the string (by [@jieter](https://github.com/jieter)). [#1968](https://github.com/Leaflet/Leaflet/issues/1968) [#2121](https://github.com/Leaflet/Leaflet/pull/2121) [#2120](https://github.com/Leaflet/Leaflet/issues/2120)
* Fixed a bug where attribution control that was added to a map after attributed layers didn't have the corresponding attributions (by [@snkashis](https://github.com/snkashis)). [#2177](https://github.com/Leaflet/Leaflet/issues/2177) [#2178](https://github.com/Leaflet/Leaflet/pull/2178)
## 0.6.4 (2013-07-25)
* Fixed a regression where `fitBounds` and `setMaxBounds` could freeze the browser in some situations. [#1895](https://github.com/Leaflet/Leaflet/issues/1895) [1866](https://github.com/Leaflet/Leaflet/issues/1866)
* Fixed a bug where click on a map on a page with horizontal scroll caused the page to scroll right (by [@mstrelan](https://github.com/mstrelan)). [#1901](https://github.com/Leaflet/Leaflet/issues/1901)
## 0.6.3 (2013-07-17)
### Regression fixes
* Fixed a regression where mouse interaction had incorrect coordinates in some map positioning cases (by [@scooterw](https://github.com/scooterw)). [#1826](https://github.com/Leaflet/Leaflet/issues/1826) [#1684](https://github.com/Leaflet/Leaflet/issues/1684) [#1745](https://github.com/Leaflet/Leaflet/issues/1745) [#1](https://github.com/Leaflet/Leaflet/issues/1)
* Fixed a regression that prevented the map from responding to drag on areas covered with `ImageOverlay` (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1821](https://github.com/Leaflet/Leaflet/issues/1821)
* Fixed a regression where `layerremove` and `layeradd` were fired before the corresponding action finishes (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1846](https://github.com/Leaflet/Leaflet/issues/1846)
* Fixed a regression with `worldCopyJump: true` breaking the map on small zoom levels (by [@danzel](https://github.com/danzel)). [#1831](https://github.com/Leaflet/Leaflet/issues/1831)
* Fixed a regression where `Marker` shadow didn't animate on zoom after using `setIcon`. [#1768](https://github.com/Leaflet/Leaflet/issues/1768)
* Fixed a regression where the map would stuck when trying to animate zoom before any tile layers are added to the map. [#1484](https://github.com/Leaflet/Leaflet/issues/1484) [#1845](https://github.com/Leaflet/Leaflet/issues/1845)
* Fixed a regression with the layers control and popups closing on inside click in IE < 9. [#1850](https://github.com/Leaflet/Leaflet/issues/1850)
* Fixed a regression where scrolled popup content woudln't scroll in FF (by [@jfirebaugh](https://github.com/jfirebaugh)).
### Bug fixes
* Fixed vector feature flickering on Safari Mac for screen < 2000px. [#902](https://github.com/Leaflet/Leaflet/issues/902)
* Fixed a bug where `GeoJSON` ignored non-feature geometries passed in an array. [#1840](https://github.com/Leaflet/Leaflet/issues/1840)
* Fixed a bug where `Map` `minZoom` and `maxZoom` didn't always override values derived from the added tile layers. [1848](https://github.com/Leaflet/Leaflet/issues/1848)
* Fixed a bug where `TileLayer.Canvas` wasn't immediately redrawn when `redraw` is called (by [@tofferrosen](https://github.com/tofferrosen)). [#1797](https://github.com/Leaflet/Leaflet/issues/1797) [#1817](https://github.com/Leaflet/Leaflet/issues/1817)
* Fixed a bug where `FeatureGroup` still fired `layerremove` event on `removeLayer` even if the given layer wan't present in the group (by (by [@danzel](https://github.com/danzel))). [#1847](https://github.com/Leaflet/Leaflet/issues/1847) [#1858](https://github.com/Leaflet/Leaflet/issues/1858)
* Fixed a bug where `Marker` `setOpacity` wasn't returning the marker (by [@freepius44](https://github.com/freepius44)). [#1851](https://github.com/Leaflet/Leaflet/issues/1851)
* Fixed a bug where removing the map element from the DOM before panning transition has finished could keep a setInterval loop running forever (by [@rutkovsky](https://github.com/rutkovsky)). [#1825](https://github.com/Leaflet/Leaflet/issues/1825) [#1856](https://github.com/Leaflet/Leaflet/issues/1856)
* Fixed mobile styles to apply to `leaflet-bar` elements.
### Improvements
* Added ability to pass zoom/pan animation options to `setMaxBounds` (by [@davidjb](http://git.io/djb)). [#1834](https://github.com/Leaflet/Leaflet/pull/1834)
* Added `MultiPolyline` and `MultiPolygon` `getLatLngs` method. [#1839](https://github.com/Leaflet/Leaflet/issues/1839)
### Dev Workflow improvements
* Leaflet builds (*.js files in the `dist` folder) were removed from the repo and are now done automatically on each commit for `master` and `stable` branches by [Travis CI](travis-ci.org/Leaflet/Leaflet). The download links are on the [Leaflet download page](http://leafletjs.com/download.html).
## 0.6.2 (2013-06-28)
* Fixed a bug that caused wrong tile layers stacking order when using opacity < 1 (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1804](https://github.com/Leaflet/Leaflet/issues/1804) [#1790](https://github.com/Leaflet/Leaflet/issues/1790) [#1667](https://github.com/Leaflet/Leaflet/issues/1667)
* Fixed a regression that caused tiles selection when double-clicking absolutely positioned maps with vector layers in it in Firefox (WTF!) (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1807](https://github.com/Leaflet/Leaflet/issues/1807)
* Fixed a regression with a wrong cursor when dragging a map with vector layers, and tiles becoming selected on double click (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1800](https://github.com/Leaflet/Leaflet/issues/1800)
* Fixed a regression that caused flickering of tiles near map border on zoom animation in Chrome.
## 0.6.1 (2013-06-27)
* Fixed a regression with mouse wheel zooming too fast on Firefox (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1788](https://github.com/Leaflet/Leaflet/issues/1788)
* Fixed a regression with broken zooming on maps with EPSG3395 projection. [#1796](https://github.com/Leaflet/Leaflet/issues/1796)
* Fixed a bug where zoom buttons inherited Bootstrap link hover styles. [#1791](https://github.com/Leaflet/Leaflet/issues/1791)
## 0.6 (2013-06-26)
### Breaking changes
* Moved polyline editing code into [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw) plugin (where it fits much better along with all other editing and drawing handlers). The API remains the same.
* Dropped support for environments that augment `Object.prototype` (luckily it's a thing of the past nowadays).
* `Map` `invalidateSize` no longer fires `move` and `moveend` events if the map size didn't change. [#1819](https://github.com/Leaflet/Leaflet/issues/1819)
### Improvements
#### Usability and performance improvements
* **Improved zoom control design** - cleaner, simpler, more accessible (mostly by [@jacobtoye](https://github.com/jacobtoye)). [#1313](https://github.com/Leaflet/Leaflet/issues/1313)
* Updated `Control.Layers` icon (designed by Volker Kinkelin), added retina version and SVG source. [#1739](https://github.com/Leaflet/Leaflet/issues/1739)
* Added keyboard accessibility to markers (you can now tab to them and press enter for click behavior). [#1355](https://github.com/Leaflet/Leaflet/issues/1355)
* Improved `TileLayer` zoom animation to eliminate flickering in case one tile layer on top of another or when zooming several times quickly (by [@mourner](https://github.com/mourner) with lots of fixes from [@danzel](https://github.com/danzel)). [#1140](https://github.com/Leaflet/Leaflet/issues/1140) [#1437](https://github.com/Leaflet/Leaflet/issues/1437) [#52](https://github.com/Leaflet/Leaflet/issues/52)
* Subtly improved default popup styles
* Improved attribution control to be much less obtrusive (no "powered by", just a Leaflet link). You can still remove the prefix with `map.attributionControl.setPrefix('')` if you need.
* Improved zoom behavior so that there's no drift of coordinates when you change zoom back and forth without panning. [#426](https://github.com/Leaflet/Leaflet/issues/426)
* Improved double click behavior to zoom while keeping the clicked point fixed (by [@ansis](https://github.com/ansis)). [#1582](https://github.com/Leaflet/Leaflet/issues/1582)
* Improved dragging behavior to not get stuck if mouse moved outside of an iframe containing the map (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1277](https://github.com/Leaflet/Leaflet/issues/1277) [#1782](https://github.com/Leaflet/Leaflet/issues/1782) [#1786](https://github.com/Leaflet/Leaflet/issues/1786)
* Improved box zoom to be cancelable by pressing Escape (by [@yohanboniface](https://github.com/yohanboniface)). [#1438](https://github.com/Leaflet/Leaflet/issues/1438)
* Improved `Marker` popups to close on marker click if opened (by [@popox](https://github.com/popox)). [#1761](https://github.com/Leaflet/Leaflet/issues/1761)
* Significantly improved mass layer removal performance (by [@jfgirard](https://github.com/jfgirard) with fixes from [@danzel](https://github.com/danzel)). [#1141](https://github.com/Leaflet/Leaflet/pull/1141) [#1514](https://github.com/Leaflet/Leaflet/pull/1514)
#### API improvements
##### Layers API improvements
* Added `toGeoJSON` method to various layer types, allowing you to **save your Leaflet layers as GeoJSON**. (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1462](https://github.com/Leaflet/Leaflet/issues/1462) [#712](https://github.com/Leaflet/Leaflet/issues/712) [#1779](https://github.com/Leaflet/Leaflet/issues/1779)
* Added `GeoJSON` `coordsToLatLng` option for dealing with GeoJSON that has non-WGS84 coords (thanks to [@moonlite](https://github.com/moonlite)). [#888](https://github.com/Leaflet/Leaflet/issues/888) [#886](https://github.com/Leaflet/Leaflet/issues/886)
* Improved `Marker` to reuse icon DOM elements when changing icons on the fly (e.g. fixes problems when changing icon on mouse hover) (by [@robpvn](https://github.com/robpvn) & [@danzel](https://github.com/danzel)). [#1726](https://github.com/Leaflet/Leaflet/issues/1726) [#561](https://github.com/Leaflet/Leaflet/issues/561) [#1753](https://github.com/Leaflet/Leaflet/issues/1753) [#1754](https://github.com/Leaflet/Leaflet/pull/1754)
* Added `latlng` property to `Marker` mouse event data. [#1613](https://github.com/Leaflet/Leaflet/issues/1613)
* Added `LayerGroup` `hasLayer` method (by [@rvlasveld](https://github.com/rvlasveld)). [#1282](https://github.com/Leaflet/Leaflet/issues/1282) [#1300](https://github.com/Leaflet/Leaflet/pull/1300)
* Added `LayerGroup` `getLayers` method (by [@tmcw](https://github.com/tmcw)). [#1469](https://github.com/Leaflet/Leaflet/pull/1469)
* Added `LayerGroup` `getLayer` method (by [@gumballhead](https://github.com/gumballhead)). [#1650](https://github.com/Leaflet/Leaflet/pull/1650)
* Improved `LayerGroup` `removeLayer` method to also accept layer `id` (by [@gumballhead](https://github.com/gumballhead)). [#1642](https://github.com/Leaflet/Leaflet/pull/1642)
* Added `Path` `pointerEvents` option for setting pointer-events on SVG-powered vector layers (by [@inpursuit](https://github.com/inpursuit)). [#1053](https://github.com/Leaflet/Leaflet/pull/1053)
* Improved `Polygon` to filter out last point if it's equal to the first one (to fix GeoJSON polygon issues) (by [@snkashis](https://github.com/snkashis)). [#1153](https://github.com/Leaflet/Leaflet/pull/1153) [#1135](https://github.com/Leaflet/Leaflet/issues/1135)
* Improved paths with `clickable: false` to allow mouse events to pass through to objects underneath (by [@snkashis](https://github.com/snkashis)). [#1384](https://github.com/Leaflet/Leaflet/pull/1384) [#1281](https://github.com/Leaflet/Leaflet/issues/1281)
* Improved `L.Util.template` (and correspondingly url-related `TileLayer` options) to support functions for data values (by [@olegsmith](https://github.com/olegsmith)). [#1554](https://github.com/Leaflet/Leaflet/pull/1554)
* Added `TileLayer` `getContainer` method (by [@tmcw](https://github.com/tmcw)). [#1433](https://github.com/Leaflet/Leaflet/pull/1433)
* Fixed `TileLayer.Canvas` `redraw` method chaining (by [@jieter](https://github.com/jieter)). [#1287](https://github.com/Leaflet/Leaflet/pull/1287)
* Added `TileLayer.WMS` `crs` option to be able to use WMS of CRS other than the map CRS (by [@kengu](https://github.com/kengu)). [#942](https://github.com/Leaflet/Leaflet/issues/942) [#945](https://github.com/Leaflet/Leaflet/issues/945)
* Added `popupopen` and `popupclose` events to various layers (by [@Koc](https://github.com/Koc)). [#738](https://github.com/Leaflet/Leaflet/pull/738)
* Added `Popup` `keepInView` option (thanks to [@lapo-luchini](https://github.com/lapo-luchini)) that prevents the popup from going off-screen while it's opened. [#1308](https://github.com/Leaflet/Leaflet/pull/1308) [#1052](https://github.com/Leaflet/Leaflet/issues/1052)
* Added `Marker` `togglePopup` method (by [@popox](https://github.com/popox)). [#1761](https://github.com/Leaflet/Leaflet/issues/1761)
* Added `Popup` `closeOnClick` option that overrides the same `Map` option for specific popups (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1669](https://github.com/Leaflet/Leaflet/issues/1669)
* Improved `Marker` and `Path` `bindPopup` method to also accept `Popup` objects (by [@snkashis](https://github.com/snkashis)). [#1385](https://github.com/Leaflet/Leaflet/pull/1385) [#1208](https://github.com/Leaflet/Leaflet/issues/1208) [#1402](https://github.com/Leaflet/Leaflet/pull/1402)
* Added `Marker` `setPopupContent` method (by [@snkashis](https://github.com/snkashis)). [#1373](https://github.com/Leaflet/Leaflet/pull/1373)
##### Map API improvements
* Improved all view changing methods of `Map` (`setView`, `panTo`, `setZoom`, `fitBounds`, etc.) to accept an options object, including the ability to precisely control zoom/pan animations they cause (force disable/enable, etc.). [#1617](https://github.com/Leaflet/Leaflet/pull/1617) [#1616](https://github.com/Leaflet/Leaflet/issues/1616) [#340](https://github.com/Leaflet/Leaflet/issues/340) [#483](https://github.com/Leaflet/Leaflet/issues/483) [#1164](https://github.com/Leaflet/Leaflet/issues/1164) [#1420](https://github.com/Leaflet/Leaflet/issues/1420)
* Improved `Map` `fitBounds` method to accept `padding` (or `paddingTopLeft` and `paddingBottomRight`) options, allowing you to zoom to an area with a certain padding in pixels (usually left for controls). [#859](https://github.com/Leaflet/Leaflet/issues/859)
* Improved `Map` `invalidateSize` to accept options object (`animate` and `pan`, the latter controls if it pans the map on size change). (by [@jacobtoye](https://github.com/jacobtoye) and [@mourner](https://github.com/mourner)). [#1766](https://github.com/Leaflet/Leaflet/issues/1766) [#1767](https://github.com/Leaflet/Leaflet/issues/1767)
* Added `Map` `setZoomAround` method for zooming while keeping a certain point fixed (used by scroll and double-click zooming). [#1157](https://github.com/Leaflet/Leaflet/issues/1157)
* Added `Map` `remove` method to properly destroy the map and clean up all events, and added corresponding `unload` event (by [@jfirebaugh](https://github.com/jfirebaugh) and [@mourner](https://github.com/mourner)). [#1434](https://github.com/Leaflet/Leaflet/issues/1434) [#1101](https://github.com/Leaflet/Leaflet/issues/1101) [#1621](https://github.com/Leaflet/Leaflet/issues/1621)
* Added `Map` `tap` handler that now contains all mobile hacks for enabling quick taps and long holds and `tapTolerance` option specifying the number of pixels you can shift your finger for click to still fire.
* Added `Map` `zoomAnimationThreshold` for setting the max zoom difference with which zoom animation can occur. [#1377](https://github.com/Leaflet/Leaflet/issues/1377)
* Improved `Map` `openPopup` method to also accept `(content, latlng)` signature as a shortcut.
* Improved `Map` `closePopup` method to optionally accept a popup object to close. [#1641](https://github.com/Leaflet/Leaflet/issues/1641)
* Improved `Map` `stopLocate` method to abort resetting map view if calling `locate` with `setView` option. [#747](https://github.com/Leaflet/Leaflet/issues/747)
* Improved `Map` to throw exception if the specified container id is not found (by [@tmcw](htts://github.com/tmcw)). [#1574](https://github.com/Leaflet/Leaflet/pull/1574)
* Improved `Map` `locationfound` event to pass all location data (heading, speed, etc.). [#984](https://github.com/Leaflet/Leaflet/issues/984) [#584](https://github.com/Leaflet/Leaflet/issues/584) [#987](https://github.com/Leaflet/Leaflet/issues/987) [#1028](https://github.com/Leaflet/Leaflet/issues/1028)
* Added `Map` `resize` event. [#1564](https://github.com/Leaflet/Leaflet/issues/1564)
* Added `Map` `zoomlevelschange` event that triggers when the current zoom range (min/max) changes (by [@moonlite](https://github.com/moonlite)). [#1376](https://github.com/Leaflet/Leaflet/pull/1376)
##### Controls API improvements
* Added **generic toolbar classes** for reuse by plugin developers (used by zoom control).
* Added `Map` `baselayerchange`, `overlayadd` and `overlayremove` events fired by `Control.Layers` (by [@calvinmetcalf](https://github.com/calvinmetcalf) and [@Xelio](https://github.com/Xelio)). [#1286](https://github.com/Leaflet/Leaflet/issues/1286) [#1634](https://github.com/Leaflet/Leaflet/issues/1634)
* Added `Control` `getContainer` method. [#1409](https://github.com/Leaflet/Leaflet/issues/1409)
##### Misc API improvements
* Made Leaflet classes compatible with **CoffeeScript class inheritance** syntax (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1345](https://github.com/Leaflet/Leaflet/pull/1345) [#1314](https://github.com/Leaflet/Leaflet/issues/1314)
* Added `cleanAllEventListeners` method (aliased to `off` without arguments) to all events-enabled objects (by [@iirvine](https://github.com/iirvine)). [#1599](https://github.com/Leaflet/Leaflet/issues/1599)
* Added `addOneTimeEventListener` method (aliased to `once`) to all events-enabled objects (by [@iirvine](https://github.com/iirvine)). [#473](https://github.com/Leaflet/Leaflet/issues/473) [#1596](https://github.com/Leaflet/Leaflet/issues/1596)
* Added ability to pass coordinates as simple objects (`{lat: 50, lon: 30}` or `{lat: 50, lng: 30}`). [#1412](https://github.com/Leaflet/Leaflet/issues/1412)
* Added `LatLngBounds` `getNorth`, `getEast`, `getSouth`, `getWest` methods (by [@yohanboniface](https://github.com/yohanboniface)). [#1318](https://github.com/Leaflet/Leaflet/issues/1318)
* Added `AMD` support (Leaflet now registers itself as a `leaflet` AMD module) (with fixes from [@sheppard](https://github.com/sheppard)). [#1364](https://github.com/Leaflet/Leaflet/issues/1364) [#1778](https://github.com/Leaflet/Leaflet/issues/1778)
* Added `L.Util.trim` function (by [@kristerkari](https://github.com/kristerkari)). [#1607](https://github.com/Leaflet/Leaflet/pull/1607)
#### Development workflow improvements
* Switched from Jasmine to [Mocha](http://visionmedia.github.io/mocha/) with Expect.js (matchers) and Sinon (spies) for tests (by [@tmcw](https://github.com/tmcw) & [@jfirebaugh](https://github.com/jfirebaugh)). [#1479](https://github.com/Leaflet/Leaflet/issues/1479)
* Added [Karma](http://karma-runner.github.io) integration for running tests in a headless PhantomJS instance and code coverage reporting (by [@edjafarov](https://github.com/edjafarov)). [#1326](https://github.com/Leaflet/Leaflet/issues/1326) [#1340](https://github.com/Leaflet/Leaflet/pull/1340)
* Added [Travis CI integration](https://travis-ci.org/Leaflet/Leaflet) for linting and running tests for each commit and pull request automatically (by [@edjafarov](https://github.com/edjafarov)). [#1336](https://github.com/Leaflet/Leaflet/issues/1336)
* Significantly improved test coverage
* Added compatibility with lazy evaluation scripts (by [@kristerkari](https://github.com/kristerkari)). [#1288](https://github.com/Leaflet/Leaflet/issues/1288) [#1607](https://github.com/Leaflet/Leaflet/issues/1607) [#1288](https://github.com/Leaflet/Leaflet/issues/1288)
### Bugfixes
#### General bugfixes
* Fixed lots of issues with extent restriction by `Map` `maxBounds`. [#1491](https://github.com/Leaflet/Leaflet/issues/1491) [#1475](https://github.com/Leaflet/Leaflet/issues/1475) [#1194](https://github.com/Leaflet/Leaflet/issues/1194) [#900](https://github.com/Leaflet/Leaflet/issues/900) [#1333](https://github.com/Leaflet/Leaflet/issues/1333)
* Fixed occasional crashes by disabling zoom animation if changing zoom level by more than 4 levels. [#1377](https://github.com/Leaflet/Leaflet/issues/1377)
* Fixed a bug with that caused stuttery keyboard panning in some cases (by [@tmcw](https://github.com/tmcw)). [#1710](https://github.com/Leaflet/Leaflet/issues/1710)
* Fixed a bug that caused unwanted scrolling of the page to the top of the map on focus. [#1228](https://github.com/Leaflet/Leaflet/issues/1228) [#1540](https://github.com/Leaflet/Leaflet/issues/1540)
* Fixed a bug where clicking on a marker with an open popup caused the popup to faded in again (by [@snkashis](https://github.com/snkashis)). [#506](https://github.com/Leaflet/Leaflet/issues/560) [#1386](https://github.com/Leaflet/Leaflet/pull/1386)
* Fixed a bug where zoom buttons disabled state didn't update on min/max zoom change (by [@snkashis](https://github.com/snkashis)). [#1372](https://github.com/Leaflet/Leaflet/pull/1372) [#1328](https://github.com/Leaflet/Leaflet/issues/1328)
* Fixed a bug where scrolling slightly wouldn't always zoom out the map (by [@cschwarz](https://github.com/cschwarz)). [#1575](https://github.com/Leaflet/Leaflet/pull/1575)
* Fixed popup close button to not leave an outline after clicking on it and reopening the popup (by [@dotCypress](https://github.com/dotCypress)). [#1537](https://github.com/Leaflet/Leaflet/pull/1537)
* Fixed a bug that prevented tiles from loading during pan animation.
* Fixed a bug with `contextmenu` events on popups falling through to map (by [@snkashis](https://github.com/snkashis)). [#1730](https://github.com/Leaflet/Leaflet/issues/1730) [#1732](https://github.com/Leaflet/Leaflet/issues/1732)
* Fixed `404` tile loading errors when browsing the map off the world bounds.
* Fixed shifted mouse events in some cases with map inside a relatively positioned parent (by [@scooterw](https://github.com/scooterw) and [@jec006](https://github.com/jec006)). [#1670](https://github.com/Leaflet/Leaflet/issues/1670) [#1684](https://github.com/Leaflet/Leaflet/issues/1684) [#1745](https://github.com/Leaflet/Leaflet/issues/1745) [#1744](https://github.com/Leaflet/Leaflet/issues/1744)
* Fixed a bug where tile layer z-index order sometimes broke after view reset. [#1422](https://github.com/Leaflet/Leaflet/issues/1422)
#### Browser bugfixes
* Fixed a bug with undesirable page scrolling in Firefox 17+ when you zoom the map by scrolling (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1789](https://github.com/Leaflet/Leaflet/issues/1789) [#1788](https://github.com/Leaflet/Leaflet/issues/1788)
* Fixed a bug where mouse coordinates where shifted in Firefox if the map was inside a positioned block on a scrolled page (by [@joschka](https://github.com/joschka)). [#1365](https://github.com/Leaflet/Leaflet/pull/1365) [#1322](https://github.com/Leaflet/Leaflet/issues/1322)
* Fixed a bug where box zoom didn't work in some cases in Firefox 18+ (by [@fabriceds](https://github.com/fabriceds)). [#1405](https://github.com/Leaflet/Leaflet/pull/1405)
* Fixed a bug where `TileLayer` opacity didn't work in IE 7-8 (by [@javisantana](https://github.com/javisantana) & [@danzel](https://gi
.com/danzel)). [#1084](https://github.com/Leaflet/Leaflet/issues/1084) [#1396](https://github.com/Leaflet/Leaflet/pull/1396) [#1371](https://github.com/Leaflet/Leaflet/issues/1371)
* Fixed Leaflet not working correctly in PhantomJS (by [@rassie](https://github.com/rassie)). [#1501](https://github.com/Leaflet/Leaflet/pull/1501)
#### Mobile bugfixes
* Fixed a bug with layers control on WinPhone8/IE10 Touch (by [@danzel](https://github.com/danzel)). [#1635](https://github.com/Leaflet/Leaflet/pull/1635) [#1539](https://github.com/Leaflet/Leaflet/issues/1539)
* Fixed a bug with click sometimes firing twice on WinPhone8/IE10 Touch (by [@danzel](https://github.com/danzel)). [#1694](https://github.com/Leaflet/Leaflet/issues/1694)
* Fixed a bug in Android where click was triggered twice on one tap (by [@jerel](https://github.com/jerel) & [@mourner](https://github.com/mourner)). [#1227](https://github.com/Leaflet/Leaflet/pull/1227) [#1263](https://github.com/Leaflet/Leaflet/issues/1263) [#1785](https://github.com/Leaflet/Leaflet/issues/1785) [#1694](https://github.com/Leaflet/Leaflet/issues/1694)
* Fixed a bug in Android where click on a collapsed layers control would immediately select one of the layers (by [@danzel](https://github.com/danzel)). [#1784](https://github.com/Leaflet/Leaflet/issues/1784) [#1694](https://github.com/Leaflet/Leaflet/issues/1694)
#### API bugfixes
##### General API bugfixes
* Fixed click mouse event inside popups **not propagating outside the map** (fixes issues with jQuery.live and some mobile frameworks that rely on document click listeners). [#301](https://github.com/Leaflet/Leaflet/issues/301)
* Fixed a bug where event listener still fired even if it was removed on the same event in other listener (by [@spamdaemon](https://github.com/spamdaemon)). [#1661](https://github.com/Leaflet/Leaflet/issues/1661) [#1654](https://github.com/Leaflet/Leaflet/issues/1654)
* Fixed a bug where `L.point` and `L.latLng` factories weren't passing `null` and `undefined` values through.
* Fixed `DomEvent` `removeListener` function chaining (by [@pagameba](https://github.com/pagameba)).
* Fixed a bug where `removeEventListener` would throw an error if no events are registered on the object (by [@tjoekbezoer](https://github.com/tjoekbezoer)). [#1632](https://github.com/Leaflet/Leaflet/pull/1632) [#1631](https://github.com/Leaflet/Leaflet/issues/1631)
* Fixed a bug where `Point` `equals` and `contains` methods didn't accept points in array form.
* Fixed a bug where `LatLngBounds` `extend` of an undefined object would cause an error (by [@korzhyk](https://github.com/korzhyk)). [#1688](https://github.com/Leaflet/Leaflet/issues/1688)
* Fixed a bug where `Control.Attribution` `removeAttribution` of inexistant attribution corrupted the attribution text. [#1410](https://github.com/Leaflet/Leaflet/issues/1410)
* Fixed a bug where `setView` on an invisible map caused an error (by [@jfire](https://github.com/jfire)). [#1707](https://github.com/Leaflet/Leaflet/issues/1707)
* Fixed compatibility with Browserify (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1572](https://github.com/Leaflet/Leaflet/pull/1572)
##### Layers API bugfixes
* Fixed a bug where default marker icon path wasn't properly detected in some cases in IE6-7 (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1294](https://github.com/Leaflet/Leaflet/pull/1294)
* Fixed a bug where `TileLayer.WMS` param values weren't escaped in URLs (by [@yohanboniface](https://github.com/yohanboniface)). [#1317](https://github.com/Leaflet/Leaflet/issues/1317)
* Fixed a bug where layers that belong to multiple feature groups didn't propagate events correctly (by [@danzel](https://github.com/danzel)). [#1359](https://github.com/Leaflet/Leaflet/pull/1359)
* Fixed a bug where `TileLayer.WMS` `tileSize` option was ignored (by [@brianhatchl](https://github.com/brianhatchl)). [#1080](https://github.com/brianhatchl)
* Fixed a bug where `Polyline` constructor could overwrite the source array (by [@snkashis](https://github.com/snkashis) and [@danzel](https://github.com/danzel)). [#1439](https://github.com/Leaflet/Leaflet/pull/1439) [#1092](https://github.com/Leaflet/Leaflet/issues/1092) [#1246](https://github.com/Leaflet/Leaflet/issues/1246) [#1426](https://github.com/Leaflet/Leaflet/issues/1426)
* Fixed a bug where marker dragging disabling/enabling wouldn't always work correctly (by [@snkashis](https://github.com/snkashis) and [@escaped](https://github.com/escaped)). [#1471](https://github.com/Leaflet/Leaflet/pull/1471) [#1551](https://github.com/Leaflet/Leaflet/pull/1551)
* Fixed `TileLayer` to prevent incorrect subdomain in case of negative tile coordinates (by [@malexeev](https://github.com/malexeev)). [#1532](https://github.com/Leaflet/Leaflet/pull/1532)
* Fixed polygons to normalize holes (remove last point if it's equal to the first one) (by [@jfirebaugh](https://github.com/jfirebaugh)). [#](https://github.com/Leaflet/Leaflet/pull/1467) [#1459](https://github.com/Leaflet/Leaflet/issues/1459)
* Fixed `DivIcon` `html` option to accept `0` as a value (by [@stuporglue](https://github.com/stuporglue)). [#1633](https://github.com/Leaflet/Leaflet/pull/1633)
* Fixed a bug with Canvas-based paths throwing an error on `mousemove` in certain conditions. [#1615](https://github.com/Leaflet/Leaflet/issues/1615)
* Fixed a bug where copies of the world wouldn't load if you set `TileLayer` `bounds` (by [@ansis](https://github.com/ansis)). [#1618](https://github.com/Leaflet/Leaflet/issues/1618)
* Fixed a bug where `TileLayer` `load` event wouldn't always fire correctly. [#1565](https://github.com/Leaflet/Leaflet/issues/1565)
* Fixed `TileLayer.WMS` compatibility with some old servers that only accepted request parameters in uppercase. [#1751](https://github.com/Leaflet/Leaflet/issues/1751)
* Fixed a bug with incorrect `L.Icon.Default.imagePath` detection in some cases. [#1657](https://github.com/Leaflet/Leaflet/issues/1657)
* Fixed a bug where layer `onRemove` was still called even if it was never added (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1729](https://github.com/Leaflet/Leaflet/issues/1729)
* Fixed a bug where calling `setRadius` on a Canvas-powered `CircleMarker` would cause an infinite loop (by [@snkashis](https://github.com/snkashis)). [#1712](https://github.com/Leaflet/Leaflet/issues/1712) [#1713](https://github.com/Leaflet/Leaflet/issues/1713) [#1728](https://github.com/Leaflet/Leaflet/issues/1728)
* Renamed `marker-icon@2x.png` to `marker-icon-2x.png` to fix compatibility with Google AppEngine. [#1552](https://github.com/Leaflet/Leaflet/issues/1552) [#1553](https://github.com/Leaflet/Leaflet/issues/1553)
* Fixed a bug where `popupclose` and `popupopen` events weren't fired for multipolygons and multipolylines (by [@tmcw](https://github.com/tmcw)). [#1681](https://github.com/Leaflet/Leaflet/issues/1681)
##### Map API bugfixes
* Fixed a bug where `Map` `fitBounds` wouldn't work correctly with large bounds (thanks to [@MaZderMind](https://github.com/MaZderMind)). [#1069](https://github.com/Leaflet/Leaflet/issues/1069)
* Fixed a bug where `Map` `hasLayer` wasn't handling `null` objects (by [@rvlasveld](https://github.com/rvlasveld)). [#1282](https://github.com/Leaflet/Leaflet/issues/1282) [#1302](https://github.com/Leaflet/Leaflet/pull/1302)
* Fixed a bug where `Map` `moveend` fired before `dragend` on drag (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
* Fixed a bug where panning with inertia produced an excessive `Map` `movestart` event on inertia start (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
* Fixed a bug where `Map` `moveend` fired repeatedly on window resize even if the actual map size didn't change (by [@oslek](https://github.com/oslek)). [#1374](https://github.com/Leaflet/Leaflet/pull/1374)
* Fixed a bug where `Map` `moveend` sometimes wasn't fired after drag (particularly often when dragging with a trackpad).
* Fixed a bug that would cause an error when trying to get the state of the map in a `Map` `load` event listener. [#962](https://github.com/Leaflet/Leaflet/issues/962)
* Added `Map` `autopanstart` event back (it was removed occasionally in previous version). [#1375](https://github.com/Leaflet/Leaflet/issues/1375)
* Fixed a bug with removing previously set `Map` `maxBounds` (by [@jec006](https://github.com/jec006)). [#1749](https://github.com/Leaflet/Leaflet/issues/1749) [#1750](https://github.com/Leaflet/Leaflet/issues/1750)
## 0.5.1 (2013-02-06)
* Fixed a regression with `GeoJSON` not accepting arrays of `FeatureCollection` (by [@snkashis](https://github.com/snkashis)). [#1299](https://github.com/Leaflet/Leaflet/pull/1299) [#1298](https://github.com/Leaflet/Leaflet/issues/1298)
* Fixed a regression with `CirleMarker` `setRadius` not working if called before adding the layer to the map (by [@danzel](https://github.com/danzel)). [#1342](https://github.com/Leaflet/Leaflet/issues/1342) [#1297](https://github.com/Leaflet/Leaflet/issues/1297)
## 0.5 (2013-01-17)
### Breaking changes
Be sure to read through these changes to avoid any issues when upgrading from older versions:
* Removed default `LatLng` wrapping/clamping of coordinates (`-180, -90` to `180, 90`), wrapping moved to an explicit method (`LatLng` `wrap`).
* Disabled `Map` `worldCopyJump` option by default (jumping back to the original world copy when panning out of it). Enable it explicitly if you need it.
* Changed styles for the zoom control (you may need to update your custom styles for it).
### Improvements
#### Usability improvements
##### Interaction
* Added touch zoom, pan and double tap support for **IE10 touch devices and Metro apps** (by [@danzel](https://github.com/danzel) and [@veproza](https://github.com/veproza) with help from [@oliverheilig](https://github.com/oliverheilig)). [#1076](https://github.com/Leaflet/Leaflet/pull/1076) [#871](https://github.com/Leaflet/Leaflet/issues/871)
* **Improved panning inertia** to be much more natural and smooth.
* **Improved dragging cursors** in Chrome, Safari and Firefox (now grabbing hand cursors are used).
* Improved zoom animation curve for a better feel overall.
* Improved scroll wheel zoom to be more responsive.
* Improved panning animation performance in IE6-8.
##### Controls
* **Improved zoom control design** to look better, more neutral and in line with other controls, making it easier to customize and fit different website designs. Replaced +/- images with text.
* Improved zoom control to zoom by 3 levels if you hold shift while clicking on a button.
* Improved zoom control buttons to become visually disabled when min/max zoom is reached. [#917](https://github.com/Leaflet/Leaflet/issues/917)
* Improved scale control styles.
* Improved fallback control styles for IE6-8.
##### Other
* Added **retina support for markers** (through `Icon` `iconRetinaUrl` and `shadowRetinaUrl` options) (by [@danzel](https://github.com/danzel)). [#1048](https://github.com/Leaflet/Leaflet/issues/1048) [#1174](https://github.com/Leaflet/Leaflet/pull/1174)
* Added retina-sized default marker icon in addition to standard one (along with its SVG source and with some subtle design improvements) (by [@danzel](https://github.com/danzel)). [#1048](https://github.com/Leaflet/Leaflet/issues/1048) [#1174](https://github.com/Leaflet/Leaflet/pull/1174)
* Improved vectors updating/removing performance on Canvas backend (by [@danzel](https://github.com/danzel)). [#961](https://github.com/Leaflet/Leaflet/pull/961)
* Cut total images size from 10KB to 3.2KB with [Yahoo Smush.it](http://www.smushit.com/ysmush.it/). Thanks to Peter Rounce for suggestion.
#### API improvements
* Replaced `L.Transition` with a much better and simpler `L.PosAnimation`.
* Added `Class` `addInitHook` method for **adding constructor hooks to any classes** (great extension point for plugin authors). [#1123](https://github.com/Leaflet/Leaflet/issues/1123)
* Added `Map` `whenReady` method (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1063](https://github.com/Leaflet/Leaflet/pull/1063)
* Added optional `delta` argument to `Map` `zoomIn` and `zoomOut` (1 by default).
* Added `isValid` method to `LatLngBounds` and `Bounds` (by [@domoritz](https://github.com/domoritz)). [#972](https://github.com/Leaflet/Leaflet/pull/972)
* Added `Point` `equals` method.
* Added `Bounds` `getSize` method.
* Improved markers and vectors click event so that it propagates to map if no one is listening to it (by [@danzel](https://github.com/danzel)). [#834](https://github.com/Leaflet/Leaflet/issues/834) [#1033](https://github.com/Leaflet/Leaflet/pull/1033)
* Added `Path` `unbindPopup` and `closePopup` methods.
* Added `Path` `add` and `remove` event.
* Added `Marker` `riseOnHover` and `riseOffset` options (for bringing markers to front on hover, disabled by default) (by [jacobtoye](https://github.com/jacobtoye)). [#914](https://github.com/Leaflet/Leaflet/pull/914) [#920](https://github.com/Leaflet/Leaflet/issues/920)
* Added `Marker` `move` and `remove` events.
* Added `Marker` `contextmenu` event. [#223](https://github.com/Leaflet/Leaflet/issues/223)
* Added `Popup` `zoomAnimation` option (useful to disable when displaying flash content inside popups [#999](https://github.com/Leaflet/Leaflet/issues/999)).
* Added `FeatureGroup` `layeradd` and `layerremove` events (by [@jacobtoye](https://github.com/jacobtoye)). [#1122](https://github.com/Leaflet/Leaflet/issues/1122)
* Added `Control.Layers` `baselayerchange` event (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1064](https://github.com/Leaflet/Leaflet/pull/1064)
* Improved `Control.Layers` to support HTML in layer names (by [@aparshin](https://github.com/aparshin)). [#1055](https://github.com/Leaflet/Leaflet/pull/1055) [#1099](https://github.com/Leaflet/Leaflet/issues/1099)
* Added `CRS.Simple` to the list of built-in CRS and improved it to be more usable out of the box (it has different default scaling and transformation now), see `debug/map/simple-proj.html` for an example.
* Removed `Browser` `ua`, `gecko`, `opera` properties (no longer needed).
* Added `L.extend`, `L.bind`, `L.stamp`, `L.setOptions` shortcuts for corresponding `L.Util` methods.
* Disabled clearing of map container contents on map initialization (as a result of fixing [#278](https://github.com/Leaflet/Leaflet/issues/278)).
* Added `L.Util.isArray` function (by [@oslek](https://github.com/oslek)). [#1279](https://github.com/Leaflet/Leaflet/pull/1279)
* Added `mouseover` and `mouseout` events to canvas-based vector layers (by [@snkashis](https://github.com/snkashis)). [#1403](https://github.com/Leaflet/Leaflet/pull/1403)
* Added `Map` `eachLayer` to iterate over all layers added to the map (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1457](https://github.com/Leaflet/Leaflet/pull/1457)
* Added `TileLayer` `bounds` option to limit tile loading to a specific region (by [@adimitrov](https://github.com/adimitrov)). [#991](https://github.com/Leaflet/Leaflet/pull/991)
### Bugfixes
#### General bugfixes
* Fixed broken tiles and zooming in RTL layouts (by [@danzel](https://github.com/danzel)). [#1099](https://github.com/Leaflet/Leaflet/pull/1099) [#1095](https://github.com/Leaflet/Leaflet/issues/1095)
* Fixed a bug with pan animation where it jumped to its end position if you tried to drag the map.
* Fixed a bug where shift-clicking on a map would zoom it to the max zoom level.
* Fixed a glitch with zooming in while panning animation is running.
* Fixed a glitch with dragging the map while zoom animation is running.
* Fixed a bug where slight touchpad scrolling or one-wheel scrolling wouln't always perform zooming. [#1039](https://github.com/Leaflet/Leaflet/issues/1039)
* Fixed a bug where `panBy` wouldn't round the offset values (so it was possible to make the map blurry with it). [#1085](https://github.com/Leaflet/Leaflet/issues/1085)
* Fixed a bug where you couldn't scroll the layers control with a mouse wheel.
* Fixed a regression where WMS tiles wouldn't wrap on date lines. [#970](https://github.com/Leaflet/Leaflet/issues/970)
* Fixed a bug where mouse interaction was affected by map container border width (by [@mohlendo](https://github.com/mohlendo)). [#1204](https://github.com/Leaflet/Leaflet/issues/1205) [#1205](https://github.com/Leaflet/Leaflet/pull/1205)
* Fixed a bug with weird vector zoom animation when using Canvas for rendering (by [@danzel](https://github.com/danzel)). [#1187](https://github.com/Leaflet/Leaflet/issues/1187) [#1188](https://github.com/Leaflet/Leaflet/pull/1188)
* Fixed a bug where max bounds limitation didn't work when navigating the map with a keyboard (by [@snkashis](https://github.com/snkashis)). [#989](https://github.com/Leaflet/Leaflet/issues/989) [#1221](https://github.com/Leaflet/Leaflet/pull/1221)
#### API bugfixes
* Fixed a bug where `TileLayer` `bringToBack` didn't work properly in some cases (by [@danzel](https://github.com/danzel)). [#963](https://github.com/Leaflet/Leaflet/pull/963) [#959](https://github.com/Leaflet/Leaflet/issues/959)
* Fixed a bug where removing a tile layer while dragging would throw an error (by [@danzel](https://github.com/danzel)). [#965](https://github.com/Leaflet/Leaflet/issues/965) [#968](https://github.com/Leaflet/Leaflet/pull/968)
* Fixed a bug where middle marker wasn't removed after deleting 2 end nodes from a polyline (by [@Svad](https://github.com/Svad)). [#1022](https://github.com/Leaflet/Leaflet/issues/1022) [#1023](https://github.com/Leaflet/Leaflet/pull/1023)
* Fixed a bug where `Map` `load` event happened too late (after `moveend`, etc.) (by [@jfirebaugh](https://github.com/jfirebaugh)). [#1027](https://github.com/Leaflet/Leaflet/pull/1027)
* Fixed `Circle` `getBounds` to return correct bounds and work without adding the circle to a map. [#1068](https://github.com/Leaflet/Leaflet/issues/1068)
* Fixed a bug where removing `Popup` on `viewreset` throwed an error (by [fnicollet](https://github.com/fnicollet) and [@danzel](https://github.com/danzel)). [#1098](https://github.com/Leaflet/Leaflet/pull/1098) [#1094](https://github.com/Leaflet/Leaflet/issues/1094)
* Fixed a bug where `TileLayer.Canvas` `drawTile` didn't receive tile zoom level in arguments.
* Fixed a bug where `GeoJSON` `resetStyle` would not fully reset a layer to its default style. [#1112](https://github.com/Leaflet/Leaflet/issues/1112)
* Fixed a bug that caused infinite recursion when using `latLngBounds` factory with coordinates as string values. [#933](https://github.com/Leaflet/Leaflet/issues/933)
* Fixed chaining on `Marker` `setIcon`, `setZIndexOffset`, `update` methods. [#1176](https://github.com/Leaflet/Leaflet/issues/1176)
* Fixed a bug with mouse interaction when the map container contained children with position other than absolute. [#278](https://github.com/Leaflet/Leaflet/issues/278)
* Fixed a bug with fill/stroke opacity conflicts when using Canvas for rendering (by [@danzel](https://github.com/danzel)). [#1186](https://github.com/Leaflet/Leaflet/issues/1186) [#1889](https://github.com/Leaflet/Leaflet/pull/1189)
* Fixed a bug where `FeatureGroup` `bindPopup` didn't take options into account.
* Fixed a bug where Canvas-based vector layers didn't cleanup click event on removal properly (by [@snkashis](https://github.com/snkashis)). [#1006](https://github.com/Leaflet/Leaflet/issues/1006) [#1273](https://github.com/Leaflet/Leaflet/pull/1273)
* Fixed a bug where `CircleMarker` `setStyle` didn't take `radius` into account (by [@fdlk](https://github.com/fdlk)). [#1012](https://github.com/Leaflet/Leaflet/issues/1012) [#1013](https://github.com/Leaflet/Leaflet/pull/1013)
* Fixed a bug where null GeoJSON geometries would throw an error instead of skipping (by [@brianherbert](https://github.com/brianherbert)). [#1240](https://github.com/Leaflet/Leaflet/pull/1240)
* Fixed a bug where Canvas-based vector layers passed incorrect `layer` event property on click (by [@snkashis](https://github.com/snkashis)). [#1215](https://github.com/Leaflet/Leaflet/issues/1215) [#1243](https://github.com/Leaflet/Leaflet/pull/1243)
* Fixed a bug where `TileLayer.WMS` didn't work correctly if the base URL contained query parameters (by [@snkashis](https://github.com/snkashis)). [#973](https://github.com/Leaflet/Leaflet/issues/973) [#1231](https://github.com/Leaflet/Leaflet/pull/1231)
* Fixed a bug where removing a polyline in editing state wouldn't clean up the editing handles (by [@mehmeta](https://github.com/mehmeta)). [#1233](https://github.com/Leaflet/Leaflet/pull/1233)
* Fixed a bug where removing a vector layer with a bound popup wouldn't clean up its click event properly (by [@yohanboniface](https://github.com/yohanboniface)). [#1229](https://github.com/Leaflet/Leaflet/pull/1229)
* Fixed a bug where `GeoJSON` features with `GeometryCollection` didn't pass properties to `pointToLayer` function (by [@calvinmetcalf](https://github.com/calvinmetcalf)). [#1097](https://github.com/Leaflet/Leaflet/pull/1097)
* Fixed `FeatureGroup` `eachLayer` chaining. [#1452](https://github.com/Leaflet/Leaflet/issues/1452)
#### Browser bugfixes
* Fixed a bug with map **freezing after zoom on Android 4.1**. [#1182](https://github.com/Leaflet/Leaflet/issues/1182)
* Fixed a bug where "Not implemented" error sometimes appeared in IE6-8 (by [@bryguy](https://github.com/bryguy) and [@lookfirst](https://github.com/lookfirst)). [#892](https://github.com/Leaflet/Leaflet/issues/892) [#893](https://github.com/Leaflet/Leaflet/pull/893)
* Fixed compatibility with SmoothWheel extension for Firefox (by [@waldir](https://github.com/waldir)). [#1011](https://github.com/Leaflet/Leaflet/pull/1011)
* Fixed a bug with popup layout in IE6-7 (by [@danzel](https://github.com/danzel)). [#1117](https://github.com/Leaflet/Leaflet/issues/1117)
* Fixed a bug with incorrect box zoom opacity in IE6-7 (by [@jacobtoye](https://github.com/jacobtoye)). [#1072](https://githubcom/Leaflet/Leaflet/pull/1072)
* Fixed a bug with box zoom throwing a JS error in IE6-7 (by [@danzel](https://github.com/danzel)). [#1071](https://github.com/Leaflet/Leaflet/pull/1071)
* Fixed a bug where `TileLayer` `bringToFront/Back()` throwed an error in IE6-8. [#1168](https://github.com/Leaflet/Leaflet/issues/1168)
* Fixed array type checking in the code to be more consistent in a cross-frame environment (by [@oslek](https://github.com/oslek)). [#1279](https://github.com/Leaflet/Leaflet/pull/1279)
* Fixed a bug with `-` key not working in Firefox 15+ (thanks to [@mattesCZ](https://github.com/mattesCZ)). [#869](https://github.com/Leaflet/Leaflet/issues/869)
## 0.4.5 (2012-10-25)
* Fixed a bug with **wonky zoom animation in IE10** (by [@danzel](https://github.com/danzel)). [#1007](https://github.com/Leaflet/Leaflet/pull/1007)
* Fixed a bug with **wonky zoom animation in Chrome 23+** (by [@danzel](https://github.com/danzel)). [#1060](https://github.com/Leaflet/Leaflet/pull/1060) [#1056](https://github.com/Leaflet/Leaflet/issues/1056)
## 0.4.4 (2012-08-07)
### 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 (2012-08-01)
* 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 (2012-07-31)
* 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)
## 0.4 (2012-07-30)
### API simplification
Leaflet 0.4 contains several API improvements that allow simpler, jQuery-like syntax ([example](https://gist.github.com/3038879)) while being backwards compatible with the previous approach (so you can use both styles):
* Improved most methods and options to accept `LatLng`, `LatLngBounds`, `Point` and `Bounds` values in an array form (e.g. `map.panTo([lat, lng])` will be the same as `map.panTo(new L.LatLng(lat, lng))`)
* Added `addTo` method to all layer classes, e.g. `marker.addTo(map)` is equivalent to `map.addLayer(marker)`
* Added factory methods to most classes to be able to write code without `new` keyword, named the same as classes but starting with a lowercase letter, e.g. `L.map('map')` is the same as `new L.Map('map')`
### Notable new features
* Added configurable **panning inertia** - after a quick pan, the map slows down in the same direction.
* Added **keyboard navigation** for panning/zooming with keyboard arrows and +/- keys (by [@ericmmartinez](https://github.com/ericmmartinez)). [#663](https://github.com/Leaflet/Leaflet/pull/663) [#646](https://github.com/Leaflet/Leaflet/issues/646)
* Added smooth **zoom animation of markers, vector layers, image overlays and popups** (by [@danzel](https://github.com/danzel)). [#740](https://github.com/Leaflet/Leaflet/pull/740) [#758](https://github.com/Leaflet/Leaflet/issues/758)
* Added **Android 4+ pinch-zoom** support (by [@danzel](https://github.com/danzel)). [#774](https://github.com/Leaflet/Leaflet/pull/774)
* Added **polyline and polygon editing**. [#174](https://github.com/Leaflet/Leaflet/issues/174)
* Added an unobtrusive **scale control**.
* Added **DivIcon** class that easily allows you to create lightweight div-based markers.
* Added **Rectangle** vector layer (by [@JasonSanford](https://github.com/JasonSanford)). [#504](https://github.com/Leaflet/Leaflet/pull/504)
### Improvements
#### Usability improvements
* Improved zooming so that you don't get a blank map when you zoom in or out twice quickly (by [@danzel](https://github.com/danzel)). [#7](https://github.com/Leaflet/Leaflet/issues/7) [#729](https://github.com/Leaflet/Leaflet/pull/729)
* Drag-panning now works even when there are markers in the starting point (helps on maps with lots of markers). [#506](https://github.com/Leaflet/Leaflet/issues/506)
* Improved panning performance even more (there are no wasted frames now).
* Improved pinch-zoom performance in mobile Chrome and Firefox.
* Improved map performance on window resize.
* Replaced box-shadow with border on controls for mobile devices to improve performance.
* Slightly improved default popup styling.
* Added `TileLayer` `detectRetina` option (`false` by default) that makes tiles show in a higher resolution on iOS retina displays (by [@Mithgol](https://github.com/Mithgol)). [#586](https://github.com/Leaflet/Leaflet/pull/586)
#### GeoJSON API changes
GeoJSON API was improved to be simpler and more flexible ([example](https://gist.github.com/3062900)). The changes are not backwards-compatible, so be sure to update your old code.
* Added `style` option for styling vector layers, passed either as an object or as a function (to style vector layers according to GeoJSON properties).
* Added `filter` option to leave out features that don't correspond to a certain criteria (e.g. based on properties).
* Added `onEachFeature` option to execute certain code on each feature layer based on its properties (binding popups, etc).
* Changed `pointToLayer` function signature to provide `geojson` in addition to `latlng` when creating point features for more flexibility.
#### Icon API changes
Icon API was improved to be more flexible, but one of the changes is backwards-incompatible: you now need to pass different icon properties (like `iconUrl`) inside an options object ([example](https://gist.github.com/3076084)).
* Converted `Icon` properties to options, changed constructor signature to `Icon(options)`.
* Moved default marker icon options to `L.Icon.Default` class (which extends from `L.Icon`).
* Added `Icon` `className` option to assign a custom class to an icon.
* Added `Icon` `shadowAnchor` option to set the anchor of the shadow.
* Made all `Icon` options except `iconUrl` optional (if not specified, they'll be chosen automatically or implemented using CSS). Anchor is centered by default (if size is specified), and otherwise can be set through CSS using negative margins.
#### Control API changes
* Added `setPosition` and `getPosition` to all controls, as well as ability to pass certain position as an option when creating a control.
* Made controls implementation easier (now more magic happens under the hood).
* Replaced ugly control position constants (e.g. `L.Control.Position.TOP_LEFT`) with light strings (`'topleft'`, `'bottomright'`, etc.)
#### Other breaking API changes
* Improved `TileLayer` constructor to interpolate URL template values from options (removed third `urlParams` argument).
* Changed `TileLayer` `scheme: 'tms'` option to `tms: true`.
* Removed `Map` `locateAndSetView` method (use `locate` with `setView: true` option)
* Changed popup `minWidth` and `maxWidth` options to be applied to content element, not the whole popup.
* Moved `prefix` argument to `options` in `Control.Attribution` constructor.
* Renamed `L.VERSION` to `L.version`.
#### Other API improvements
* Improved `on` and `off` methods to also accept `(eventHash[, context])`, as well as multiple space-separated events (by [@Guiswa](https://github.com/Guiswa)). [#770](https://github.com/Leaflet/Leaflet/pull/770)
* Improved `off` to remove all listeners of the event if no function was specified (by [@Guiswa](https://github.com/Guiswa)). [#770](https://github.com/Leaflet/Leaflet/pull/770) [#691](https://github.com/Leaflet/Leaflet/issues/691)
* Added `TileLayer` `setZIndex` method for controlling the order of tile layers (thanks to [@mattcurrie](https://github.com/mattcurrie)). [#837](https://github.com/Leaflet/Leaflet/pull/837)
* Added `Control.Layers` `autoZIndex` option (on by default) to preserve the order of tile layers when switching.
* Added `TileLayer` `redraw` method for re-requesting tiles (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/Leaflet/Leaflet/issues/719)
* Added `TileLayer` `setUrl` method for dynamically changing the tile URL template.
* Added `bringToFront` and `bringToBack` methods to `TileLayer`, `ImageOverlay` and vector layers. [#185](https://github.com/Leaflet/Leaflet/issues/185) [#505](https://github.com/Leaflet/Leaflet/issues/505)
* Added `TileLayer` `loading` event that fires when its tiles start to load (thanks to [@lapinos03](https://github.com/lapinos03)). [#177](https://github.com/Leaflet/Leaflet/issues/177)
* Added `TileLayer.WMS` `setParams` method for setting WMS parameters at runtime (by [@greeninfo](https://github.com/greeninfo)). [#719](https://github.com/Leaflet/Leaflet/issues/719)
* Added `TileLayer.WMS` subdomain support (`{s}` in the url) (by [@greeninfo](https://github.com/greeninfo)). [#735](https://github.com/Leaflet/Leaflet/issues/735)
* Added `originalEvent` property to `MouseEvent` (by [@k4](https://github.com/k4)). [#521](https://github.com/Leaflet/Leaflet/pull/521)
* Added `containerPoint` property to `MouseEvent`. [#413](https://github.com/Leaflet/Leaflet/issues/413)
* Added `contextmenu` event to vector layers (by [@ErrorProne](https://github.com/ErrorProne)). [#500](https://github.com/Leaflet/Leaflet/pull/500)
* Added `LayerGroup` `eachLayer` method for iterating over its members.
* Added `FeatureGroup` `mousemove` and `contextmenu` events (by [@jacobtoye](https://github.com/jacobtoye)). [#816](https://github.com/Leaflet/Leaflet/pull/816)
* Added chaining to `DomEvent` methods.
* Added `on` and `off` aliases for `DomEvent` `addListener` and `removeListener` methods.
* Added `L_NO_TOUCH` global variable switch (set it before Leaflet inclusion) which disables touch detection, helpful for desktop apps built using QT. [#572](https://github.com/Leaflet/Leaflet/issues/572)
* Added `dashArray` option to vector layers for making dashed strokes (by [jacobtoye](https://github.com/jacobtoye)). [#821](https://github.com/Leaflet/Leaflet/pull/821) [#165](https://github.com/Leaflet/Leaflet/issues/165)
* Added `Circle` `getBounds` method. [#440](https://github.com/Leaflet/Leaflet/issues/440)
* Added `Circle` `getLatLng` and `getRadius` methods (by [@Guiswa](https://github.com/Guiswa)). [#655](https://github.com/Leaflet/Leaflet/pull/655)
* Added `openPopup` method to all vector layers. [#246](https://github.com/Leaflet/Leaflet/issues/246)
* Added public `redraw` method to vector layers (useful if you manipulate their `LatLng` points directly).
* Added `Marker` `opacity` option and `setOpacity` method.
* Added `Marker` `update` method. [#392](https://github.com/Leaflet/Leaflet/issues/392)
* Improved `Marker` `openPopup` not to raise an error if it doesn't have a popup. [#507](https://github.com/Leaflet/Leaflet/issues/507)
* Added `ImageOverlay` `opacity` option and `setOpacity` method. [#438](https://github.com/Leaflet/Leaflet/issues/438)
* Added `Popup` `maxHeight` option that makes content inside the popup scrolled if it doesn't fit the specified max height.
* Added `Popup` `openOn(map)` method (similar to `Map` `openPopup`).
* Added `Map` `getContainer` method (by [@Guiswa](https://github.com/Guiswa)). [#654](https://github.com/Leaflet/Leaflet/pull/654)
* Added `Map` `containerPointToLatLng` and `latLngToContainerPoint` methods. [#474](https://github.com/Leaflet/Leaflet/issues/474)
* Added `Map` `addHandler` method.
* Added `Map` `mouseup` and `autopanstart` events.
* Added `LatLngBounds` `pad` method that returns bounds extended by a percentage (by [@jacobtoye](https://github.com/jacobtoye)). [#492](https://github.com/Leaflet/Leaflet/pull/492)
* Moved dragging cursor styles from JS code to CSS.
### Bug fixes
#### General bugfixes
* Fixed a bug where the map was zooming incorrectly inside a `position: fixed` container (by [@chx007](https://github.com/chx007)). [#602](https://github.com/Leaflet/Leaflet/pull/602)
* Fixed a bug where scaled tiles weren't cleared up after zoom in some cases (by [@cfis](https://github.com/cfis)) [#683](https://github.com/Leaflet/Leaflet/pull/683)
* Fixed a bug where map wouldn't drag over a polygon with a `mousedown` listener. [#834](https://github.com/Leaflet/Leaflet/issues/834)
#### API bugfixes
* Fixed a regression where removeLayer would not remove corresponding attribution. [#488](https://github.com/Leaflet/Leaflet/issues/488)
* Fixed a bug where popup close button wouldn't work on manually added popups. [#423](https://github.com/Leaflet/Leaflet/issues/423)
* Fixed a bug where marker click event would stop working if you dragged it and then disabled dragging. [#434](https://github.com/Leaflet/Leaflet/issues/434)
* Fixed a bug where `TileLayer` `setOpacity` wouldn't work when setting it back to 1.
* Fixed a bug where vector layer `setStyle({stroke: false})` wouldn't remove stroke and the same for fill. [#441](https://github.com/Leaflet/Leaflet/issues/441)
* Fixed a bug where `Marker` `bindPopup` method wouldn't take `offset` option into account.
* Fixed a bug where `TileLayer` `load` event wasn't fired if some tile didn't load (by [@lapinos03](https://github.com/lapinos03) and [@cfis](https://github.com/cfis)) [#682](https://github.com/Leaflet/Leaflet/pull/682)
* Fixed error when removing `GeoJSON` layer. [#685](https://github.com/Leaflet/Leaflet/issues/685)
* Fixed error when calling `GeoJSON` `clearLayer` (by [@runderwood](https://github.com/runderwood)). [#617](https://github.com/Leaflet/Leaflet/pull/617)
* Fixed a bug where `Control` `setPosition` wasn't always working correctly (by [@ericmmartinez](https://github.com/ericmmartinez)). [#657](https://github.com/Leaflet/Leaflet/pull/657)
* Fixed a bug with `Util.bind` sometimes losing arguments (by [@johtso](https://github.com/johtso)). [#588](https://github.com/Leaflet/Leaflet/pull/588)
* Fixed a bug where `drag` event was sometimes fired after `dragend`. [#555](https://github.com/Leaflet/Leaflet/issues/555)
* Fixed a bug where `TileLayer` `load` event was firing only once (by [@lapinos03](https://github.com/lapinos03) and [shintonik](https://github.com/shintonik)). [#742](https://github.com/Leaflet/Leaflet/pull/742) [#177](https://github.com/Leaflet/Leaflet/issues/177)
* Fixed a bug where `FeatureGroup` popup events where not cleaned up after removing a layer from it (by [@danzel](https://github.com/danzel)). [#775](https://github.com/Leaflet/Leaflet/pull/775)
* Fixed a bug where `DomUtil.removeClass` didn't remove trailing spaces (by [@jieter](https://github.com/jieter)). [#784](https://github.com/Leaflet/Leaflet/pull/784)
* Fixed a bug where marker popup didn't take popup offset into account.
* Fixed a bug that led to an error when polyline was removed inside a `moveend` listener.
* Fixed a bug where `LayerGroup` `addLayer` wouldn't check if a layer has already been added (by [@danzel](https://github.com/danzel)). [798](https://github.com/Leaflet/Leaflet/pull/798)
#### Browser bugfixes
* Fixed broken zooming on IE10 beta (by [@danzel](https://github.com/danzel)). [#650](https://github.com/Leaflet/Leaflet/issues/650) [#751](https://github.com/Leaflet/Leaflet/pull/751)
* Fixed a bug that broke Leaflet for websites that had XHTML content-type header set (by [lars-sh](https://github.com/lars-sh)). [#801](https://github.com/Leaflet/Leaflet/pull/801)
* Fixed a bug that caused popups to be empty in IE when passing a DOM node as the content (by [@nrenner](https://github.com/nrenner)). [#472](https://github.com/Leaflet/Leaflet/pull/472)
* Fixed inability to use scrolled content inside popup due to mouse wheel propagation.
* Fixed a bug that caused jumping/stuttering of panning animation in some cases.
* Fixed a bug where popup size was calculated incorrectly in IE.
* Fixed a bug where cursor would flicker when dragging a marker.
* Fixed a bug where clickable paths on IE9 didn't have a hand cursor (by [naehrstoff](https://github.com/naehrstoff)). [#671](https://github.com/Leaflet/Leaflet/pull/671)
* Fixed a bug in IE with disappearing icons when changing opacity (by [@tagliala](https://github.com/tagliala) and [DamonOehlman](https://github.com/DamonOehlman)). [#667](https://github.com/Leaflet/Leaflet/pull/667) [#600](https://github.com/Leaflet/Leaflet/pull/600)
* Fixed a bug with setting opacity on IE10 (by [@danzel](https://github.com/danzel)). [796](https://github.com/Leaflet/Leaflet/pull/796)
* Fixed a bug where `Control.Layers` didn't work on IE7. [#652](https://github.com/Leaflet/Leaflet/issues/652)
* Fixed a bug that could cause false `mousemove` events on click in Chrome (by [@stsydow](https://github.com/stsydow)). [#757](https://github.com/Leaflet/Leaflet/pull/757)
* Fixed a bug in IE6-8 where adding fill or stroke on vector layers after initialization with `setStyle` would break the map. [#641](https://github.com/Leaflet/Leaflet/issues/641)
* Fixed a bug with setOpacity in IE where it would not work correctly if used more than once on the same element (by [@ajbeaven](https://github.com/ajbeaven)). [#827](https://github.com/Leaflet/Leaflet/pull/827)
* Fixed a bug in Chrome where transparent control corners sometimes couldn't be clicked through (by [@danzel](https://github.com/danzel)). [#836](https://github.com/Leaflet/Leaflet/pull/836) [#575](https://github.com/Leaflet/Leaflet/issues/575)
#### Mobile browser bugfixes
* Fixed a bug that sometimes caused map to disappear completely after zoom on iOS (by [@fr1n63](https://github.com/fr1n63)). [#580](https://github.com/Leaflet/Leaflet/issues/580) [#777](https://github.com/Leaflet/Leaflet/pull/777)
* Fixed a bug that often caused vector layers to flicker on drag end on iOS (by [@krawaller](https://github.com/krawaller)). [#18](https://github.com/Leaflet/Leaflet/issues/18)
* Fixed a bug with false map click events on pinch-zoom and zoom/layers controls click. [#485](https://github.com/Leaflet/Leaflet/issues/485)
* Fixed a bug where touching the map with two or more fingers simultaneously would raise an error.
* Fixed a bug where zoom control wasn't always visible on Android 3. [#335](https://github.com/Leaflet/Leaflet/issues/335)
* Fixed a bug where opening the layers control would propagate a click to the map (by [@jacobtoye](https://github.com/jacobtoye)). [#638](https://github.com/Leaflet/Leaflet/pull/638)
* Fixed a bug where `ImageOverlay` wouldn't stretch properly on zoom on Android 2. [#651](https://github.com/Leaflet/Leaflet/issues/651)
* Fixed a bug where `clearLayers` for vector layers on a Canvas backend (e.g. on Android 2) would take unreasonable amount of time. [#785](https://github.com/Leaflet/Leaflet/issues/785)
* Fixed a bug where `setLatLngs` and similar methods on vector layers on a Canvas backend would not update the layers immediately. [#732](https://github.com/Leaflet/Leaflet/issues/732)
## 0.3.1 (2012-02-14)
* Fixed a regression where default marker icons wouldn't work if Leaflet include url contained a query string.
* Fixed a regression where tiles sometimes flickered with black on panning in iOS.
## 0.3 (2012-02-13)
### Major features
* Added **Canvas backend** for vector layers (polylines, polygons, circles). This enables vector support on Android < 3, and it can also be optionally preferred over SVG for a performance gain in some cases. Thanks to [@florianf](https://github.com/florianf) for a big part of this work.
* Added **layers control** (`Control.Layers`) for convenient layer switching.
* Added ability to set **max bounds** within which users can pan/zoom. [#93](https://github.com/Leaflet/Leaflet/issues/93)
### Improvements
#### Usability improvements
* Map now preserves its center after resize.
* When panning to another copy of the world (that's infinite horizontally), map overlays now jump to corresponding positions. [#273](https://github.com/Leaflet/Leaflet/issues/273)
* Limited maximum zoom change on a single mouse wheel movement (so you won't zoom across the whole zoom range in one scroll). [#149](https://github.com/Leaflet/Leaflet/issues/149)
* Significantly improved line simplification performance (noticeable when rendering polylines/polygons with tens of thousands of points)
* Improved circles performance by not drawing them if they're off the clip region.
* Improved stability of zoom animation (less flickering of tiles).
#### API improvements
* Added ability to add a tile layer below all others (`map.addLayer(layer, true)`) (useful for switching base tile layers).
* Added `Map` `zoomstart` event (thanks to [@Fabiz](https://github.com/Fabiz)). [#377](https://github.com/Leaflet/Leaflet/pull/377)
* Improved `Map` `locate` method, added ability to watch location continuously and more options. [#212](https://github.com/Leaflet/Leaflet/issues/212)
* Added second argument `inside` to `Map` `getBoundsZoom` method that allows you to get appropriate zoom for the view to fit *inside* the given bounds.
* Added `hasLayer` method to `Map`.
* Added `Marker` `zIndexOffset` option to be able to set certain markers below/above others. [#65](https://github.com/Leaflet/Leaflet/issues/65)
* Added `urlParams` third optional argument to `TileLayer` constructor for convenience: an object with properties that will be evaluated in the URL template.
* Added `TileLayer` `continuousWorld` option to disable tile coordinates checking/wrapping.
* Added `TileLayer` `tileunload` event fired when tile gets removed after panning (by [@CodeJosch](https://github.com/CodeJosch)). [#256](https://github.com/Leaflet/Leaflet/pull/256)
* Added `TileLayer` `zoomOffset` option useful for non-256px tiles (by [@msaspence](https://github.com/msaspence)).
* Added `TileLayer` `zoomReverse` option to reverse zoom numbering (by [@Majiir](https://github.com/Majiir)). [#406](https://github.com/Leaflet/Leaflet/pull/406)
* Added `TileLayer.Canvas` `redraw` method (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#459](https://github.com/Leaflet/Leaflet/pull/459)
* Added `Polyline` `closestLayerPoint` method that's can be useful for interaction features (by [@anru](https://github.com/anru)). [#186](https://github.com/Leaflet/Leaflet/pull/186)
* Added `setLatLngs` method to `MultiPolyline` and `MultiPolygon` (by [@anru](https://github.com/anru)). [#194](https://github.com/Leaflet/Leaflet/pull/194)
* Added `getBounds` method to `Polyline` and `Polygon` (by [@JasonSanford](https://github.com/JasonSanford)). [#253](https://github.com/Leaflet/Leaflet/pull/253)
* Added `getBounds` method to `FeatureGroup` (by [@JasonSanford](https://github.com/JasonSanford)). [#557](https://github.com/Leaflet/Leaflet/pull/557)
* Added `FeatureGroup` `setStyle` method (also inherited by `MultiPolyline` and `MultiPolygon`). [#353](https://github.com/Leaflet/Leaflet/issues/353)
* Added `FeatureGroup` `invoke` method to call a particular method on all layers of the group with the given arguments.
* Added `ImageOverlay` `load` event. [#213](https://github.com/Leaflet/Leaflet/issues/213)
* Added `minWidth` option to `Popup` (by [@marphi](https://github.com/marphi)). [#214](https://github.com/Leaflet/Leaflet/pull/214)
* Improved `LatLng` constructor to be more tolerant (and throw descriptive error if latitude or longitude can't be interpreted as a number). [#136](https://github.com/Leaflet/Leaflet/issues/136)
* Added `LatLng` `distanceTo` method (great circle distance) (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#462](https://github.com/Leaflet/Leaflet/pull/462)
* Added `LatLngBounds` `toBBoxString` method for convenience (by [@JasonSanford](https://github.com/JasonSanford)). [#263](https://github.com/Leaflet/Leaflet/pull/263)
* Added `LatLngBounds` `intersects(otherBounds)` method (thanks to [@pagameba](https://github.com/pagameba)). [#350](https://github.com/Leaflet/Leaflet/pull/350)
* Made `LatLngBounds` `extend` method to accept other `LatLngBounds` in addition to `LatLng` (by [@JasonSanford](https://github.com/JasonSanford)). [#553](https://github.com/Leaflet/Leaflet/pull/553)
* Added `Bounds` `intersects(otherBounds)` method. [#461](https://github.com/Leaflet/Leaflet/issues/461)
* Added `L.Util.template` method for simple string template evaluation.
* Added `DomUtil.removeClass` method (by [@anru](https://github.com/anru)).
* Improved browser-specific code to rely more on feature detection rather than user agent string.
* Improved superclass access mechanism to work with inheritance chains of 3 or more classes; now you should use `Klass.superclass` instead of `this.superclass` (by [@anru](https://github.com/anru)). [#179](https://github.com/Leaflet/Leaflet/pull/179)
* Added `Map` `boxzoomstart` and `boxzoomend` events (by [@zedd45](https://github.com/zedd45)). [#554](https://github.com/Leaflet/Leaflet/pull/554)
* Added `Popup` `contentupdate` event (by [@mehmeta](https://github.com/mehmeta)). [#548](https://github.com/Leaflet/Leaflet/pull/548)
#### Breaking API changes
* `shiftDragZoom` map option/property renamed to `boxZoom`.
* Removed `mouseEventToLatLng` method (bringed back in 0.4).
#### Development workflow improvements
* Build system completely overhauled to be based on Node.js, Jake, JSHint and UglifyJS.
* All code is now linted for errors and conformity with a strict code style (with JSHint), and wont build unless the check passes.
### Bugfixes
#### General bugfixes
* Fixed a bug where `Circle` was rendered with incorrect radius (didn't take projection exagerration into account). [#331](https://github.com/Leaflet/Leaflet/issues/331)
* Fixed a bug where `Map` `getBounds` would work incorrectly on a date line cross. [#295](https://github.com/Leaflet/Leaflet/issues/295)
* Fixed a bug where polygons and polylines sometimes rendered incorrectly on some zoom levels. [#381](https://github.com/Leaflet/Leaflet/issues/381)
* Fixed a bug where fast mouse wheel zoom worked incorrectly when approaching min/max zoom values.
* Fixed a bug where `GeoJSON` `pointToLayer` option wouldn't work in a `GeometryCollection`. [#391](https://github.com/Leaflet/Leaflet/issues/391)
* Fixed a bug with incorrect rendering of GeoJSON on a date line cross. [#354](https://github.com/Leaflet/Leaflet/issues/354)
* Fixed a bug where map panning would stuck forever after releasing the mouse over an iframe or a flash object (thanks to [@sten82](https://github.com/sten82)). [#297](https://github.com/Leaflet/Leaflet/pull/297) [#64](https://github.com/Leaflet/Leaflet/issues/64)
* Fixed a bug where mouse wheel zoom worked incorrectly if map is inside scrolled container (partially by [@chrillo](https://github.com/chrillo)). [#206](https://github.com/Leaflet/Leaflet/issues/206)
* Fixed a bug where it was possible to add the same listener twice. [#281](https://github.com/Leaflet/Leaflet/issues/281)
* Fixed a bug where `Circle` was rendered with incorrect radius (didn't take projection exaggeration into account). [#331](https://github.com/Leaflet/Leaflet/issues/331)
* Fixed a bug where `Marker` `setIcon` was not working properly (by [@marphi](https://github.com/marphi)). [#218](https://github.com/Leaflet/Leaflet/pull/218) [#311](https://github.com/Leaflet/Leaflet/issues/311)
* Fixed a bug where `Marker` `setLatLng` was not working if it's set before adding the marker to a map. [#222](https://github.com/Leaflet/Leaflet/issues/222)
* Fixed a bug where marker popup would not move on `Marker` `setLatLng` (by [@tjarratt](https://github.com/tjarratt)). [#272](https://github.com/Leaflet/Leaflet/pull/272)
* Fixed a bug where static properties of a child class would not override the parent ones.
* Fixed broken popup `closePopup` option (by [@jgerigmeyer](https://github.com/jgerigmeyer)).
* Fixed a bug that caused en error when dragging marker with icon without shadow (by [@anru](https://github.com/anru)). [#178](https://github.com/Leaflet/Leaflet/issues/178)
* Fixed a typo in `Bounds` `contains` method (by [@anru](https://github.com/anru)). [#180](https://github.com/Leaflet/Leaflet/pull/180)
* Fixed a bug where creating an empty `Polygon` with `new L.Polygon()` would raise an error.
* Fixed a bug where drag event fired before the actual movement of layer (by [@anru](https://github.com/anru)). [#197](https://github.com/Leaflet/Leaflet/pull/197)
* Fixed a bug where map click caused an error if dragging is initially disabled. [#196](https://github.com/Leaflet/Leaflet/issues/196)
* Fixed a bug where map `movestart` event would fire after zoom animation.
* Fixed a bug where attribution prefix would not update on `setPrefix`. [#195](https://github.com/Leaflet/Leaflet/issues/195)
* Fixed a bug where `TileLayer` `load` event wouldn't fire in some edge cases (by [@dravnic](https://github.com/dravnic)).
* Fixed a bug related to clearing background tiles after zooming (by [@neno-giscloud](https://github.com/neno-giscloud) & [@dravnic](https://github.com/dravnic)).
* Fixed a bug that sometimes caused map flickering after zoom animation.
* Fixed a bug related to cleaning up after removing tile layers (by [@dravnic](https://github.com/dravnic)). [#276](https://github.com/Leaflet/Leaflet/pull/276)
* Fixed a bug that made selecting text in the attribution control impossible. [#279](https://github.com/Leaflet/Leaflet/issues/279)
* Fixed a bug when initializing a map in a non-empty div. [#278](https://github.com/Leaflet/Leaflet/issues/278)
* Fixed a bug where `movestart` didn't fire on panning animation.
* Fixed a bug in Elliptical Mercator formula that affeted `EPSG:3395` CRS (by [@Savvkin](https://github.com/Savvkin)). [#358](https://github.com/Leaflet/Leaflet/pull/358)
#### Browser bugfixes
* Fixed occasional crashes on Mac Safari (thanks to [@lapinos03](https://github.com/lapinos03)). [#191](https://github.com/Leaflet/Leaflet/issues/191)
* Fixed a bug where resizing the map would sometimes make it blurry on WebKit (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#453](https://github.com/Leaflet/Leaflet/pull/453)
* Fixed a bug that raised error in IE6-8 when clicking on popup close button. [#235](https://github.com/Leaflet/Leaflet/issues/235)
* Fixed a bug with Safari not redrawing UI immediately after closing a popup. [#296](https://github.com/Leaflet/Leaflet/issues/296)
* Fixed a bug that caused performance drop and high CPU usage when calling `setView` or `panTo` to the current center. [#231](https://github.com/Leaflet/Leaflet/issues/231)
* Fixed a bug that caused map overlays to appear blurry in some cases under WebKit browsers.
* Fixed a bug that was causing errors in some Webkit/Linux builds (requestAnimationFrame-related), thanks to Chris Martens.
#### Mobile browser bugfixes
* Fixed a bug that caused an error when clicking vector layers under iOS. [#204](https://github.com/Leaflet/Leaflet/issues/204)
* Fixed crash on Android 3+ when panning or zooming (by [@florian](https://github.com/florianf)). [#137](https://github.com/Leaflet/Leaflet/issues/137)
* Fixed a bug on Android 2/3 that sometimes caused the map to disappear after zooming. [#69](https://github.com/Leaflet/Leaflet/issues/69)
* Fixed a bug on Android 3 that caused tiles to shift position on a big map.
* Fixed a bug that caused the map to pan when touch-panning inside a popup. [#452](https://github.com/Leaflet/Leaflet/issues/452)
* Fixed a bug that caused click delays on zoom control.
## 0.2.1 (2011-06-18)
* Fixed regression that caused error in `TileLayer.Canvas`.
## 0.2 (2011-06-17)
### Major features
* Added **WMS** support (`TileLayer.WMS` layer).
* Added different **projections** support, having `EPSG:3857`, `EPSG:4326` and `EPSG:3395` out of the box (through `crs` option in `Map`). Thanks to [@Miroff](https://github.com/Miroff) & [@Komzpa](https://github.com/Komzpa) for great advice and explanation regarding this.
* Added **GeoJSON** layer support.
### Improvements
#### Usability improvements
* Improved panning performance in Chrome and FF considerably with the help of `requestAnimationFrame`. [#130](https://github.com/Leaflet/Leaflet/issues/130)
* Improved click responsiveness in mobile WebKit (now it happens without delay). [#26](https://github.com/Leaflet/Leaflet/issues/26)
* Added tap tolerance (so click happens even if you moved your finger slighly when tapping).
* Improved geolocation error handling: better error messages, explicit timeout, set world view on locateAndSetView failure. [#61](https://github.com/Leaflet/Leaflet/issues/61)
#### API improvements
* Added **MultiPolyline** and **MultiPolygon** layers. [#77](https://github.com/Leaflet/Leaflet/issues/77)
* Added **LayerGroup** and **FeatureGroup** layers for grouping other layers.
* Added **TileLayer.Canvas** for easy creation of canvas-based tile layers.
* Changed `Circle` to be zoom-dependent (with radius in meters); circle of a permanent size is now called `CircleMarker`.
* Added `mouseover` and `mouseout` events to map, markers and paths; added map `mousemove` event.
* Added `setLatLngs`, `spliceLatLngs`, `addLatLng`, `getLatLngs` methods to polylines and polygons.
* Added `setLatLng` and `setRadius` methods to `Circle` and `CircleMarker`.
* Improved `LatLngBounds contains` method to accept `LatLng` in addition to `LatLngBounds`, the same for `Bounds contains` and `Point`
* Improved `LatLngBounds` & `Bounds` to allow their instantiation without arguments (by [@snc](https://github.com/snc)).
* Added TMS tile numbering support through `TileLayer` `scheme: 'tms'` option (by [@tmcw](https://github.com/tmcw)).
* Added `TileLayer` `noWrap` option to disable wrapping `x` tile coordinate (by [@jasondavies](https://github.com/jasondavies)).
* Added `opacity` option and `setOpacity` method to `TileLayer`.
* Added `setLatLng` and `setIcon` methods to `Marker`.
* Added `title` option to `Marker`.
* Added `maxZoom` argument to `map.locateAndSetView` method.
* Added ability to pass Geolocation options to map `locate` and `locateAndSetView` methods (by [@JasonSanford](https://github.com/JasonSanford)).
* Improved `Popup` to accept HTML elements in addition to strings as its content.
#### Development workflow improvements
* Added `Makefile` for building `leaflet.js` on non-Windows machines (by [@tmcw](https://github.com/tmcw)).
* Improved `debug/leaflet-include.js` script to allow using it outside of `debug` folder (by [@antonj](https://github.com/antonj)).
* Improved `L` definition to be compatible with CommonJS. [#122](https://github.com/Leaflet/Leaflet/issues/122)
### Bug fixes
#### General bugfixes
* Fixed a bug where zooming is broken if the map contains a polygon and you zoom to an area where it's not visible. [#47](https://github.com/Leaflet/Leaflet/issues/47)
* Fixed a bug where closed polylines would not appear on the map.
* Fixed a bug where marker that was added, removed and then added again would not appear on the map. [#66](https://github.com/Leaflet/Leaflet/issues/66)
* Fixed a bug where tile layer that was added, removed and then added again would not appear on the map.
* Fixed a bug where some tiles would not load when panning across the date line. [#97](https://github.com/Leaflet/Leaflet/issues/97)
* Fixed a bug where map div with `position: absolute` is reset to `relative`. [#100](https://github.com/Leaflet/Leaflet/issues/100)
* Fixed a bug that caused an error when trying to add a marker without shadow in its icon.
* Fixed a bug where popup content would not update on `setContent` call. [#94](https://github.com/Leaflet/Leaflet/issues/94)
* Fixed a bug where double click zoom wouldn't work if popup is opened on map click
* Fixed a bug with click propagation on popup close button. [#99](https://github.com/Leaflet/Leaflet/issues/99)
* Fixed inability to remove ImageOverlay layer.
#### Browser bugfixes
* Fixed a bug where paths would not appear in IE8.
* Fixed a bug where there were occasional slowdowns before zoom animation in WebKit. [#123](https://github.com/Leaflet/Leaflet/issues/123)
* Fixed incorrect zoom animation & popup styling in Opera 11.11.
* Fixed popup fade animation in Firefox and Opera.
* Fixed a bug where map isn't displayed in Firefox when there's an `img { max-width: 100% }` rule.
#### Mobile browsers bugfixes
* Fixed a bug that prevented panning on some Android 2.1 (and possibly older) devices. [#84](https://github.com/Leaflet/Leaflet/issues/84)
* Disabled zoom animation on Android by default because it's buggy on some devices (will be enabled back when it's stable enough). [#32](https://github.com/Leaflet/Leaflet/issues/32)
* Fixed a bug where map would occasionally break while multi-touch-zooming on iOS. [#32](https://github.com/Leaflet/Leaflet/issues/32)
* Fixed a bug that prevented panning/clicking on Android 3 tablets. [#121](https://github.com/Leaflet/Leaflet/issues/121)
* Fixed a bug that prevented panning/clicking on Opera Mobile. [#138](https://github.com/Leaflet/Leaflet/issues/138)
* Fixed potentional memory leak on WebKit when removing tiles, thanks to [@Scalar4eg](https://github.com/Scalar4eg). [#107](https://github.com/Leaflet/Leaflet/issues/107)
## 0.1 (2011-05-13)
Initial Leaflet release.
+26
View File
@@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright (c) 2010-2023, Volodymyr Agafonkin
Copyright (c) 2010-2011, CloudMade
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER OR 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.
+55
View File
@@ -0,0 +1,55 @@
Leaflet was created 11 years ago by [Volodymyr Agafonkin](https://agafonkin.com), a Ukrainian citizen living in Kyiv.
Russian bombs are now falling over Volodymyr's hometown. His family, his friends, his neighbours, thousands and thousands of absolutely wonderful people, are either seeking refuge or fighting for their lives.
The Russian soldiers have already killed tens of thousands of civilians, including women and children, and are committing mass war crimes like gang rapes, executions, looting, and targeted bombings of civilian shelters and places of cultural significance. The death toll keeps rising, and Ukraine needs your help.
As Volodymyr [expressed a few days before the invasion](https://twitter.com/LeafletJS/status/1496051256409919489):
> If you want to help, educate yourself and others on the Russian threat, follow reputable journalists, demand severe Russian sanctions and Ukrainian support from your leaders, protest war, reach out to Ukrainian friends, donate to Ukrainian charities. Just don't be silent.
Ukrainians are recommending the [Come Back Alive](https://savelife.in.ua/en/) charity. For other options, see [StandWithUkraine](https://stand-with-ukraine.pp.ua).
If an appeal to humanity doesn't work for you, I'll appeal to your egoism: the future of Ukrainian citizens is the future of Leaflet.
It is chilling to see Leaflet being used for [documenting Russia's war crimes](https://ukraine.bellingcat.com/), [factual reporting of the war](https://liveuamap.com/) and for coordination of humanitarian efforts [in Romania](https://refugees.ro/) and [in Poland](https://dopomoha.pl/). We commend these uses of Leaflet.
If you support the actions of the Russian government (even after reading all this), do everyone else a favour and [carry some seeds in your pocket](https://www.theguardian.com/world/video/2022/feb/25/ukrainian-woman-sunflower-seeds-russian-soldiers-video).
Yours truly,<br>
Leaflet maintainers.
---
<img width="600" src="https://rawgit.com/Leaflet/Leaflet/main/src/images/logo.svg" alt="Leaflet" />
Leaflet is the leading open-source JavaScript library for **mobile-friendly interactive maps**.
Weighing just about 39 KB of gzipped JS plus 4 KB of gzipped CSS code, it has all the mapping [features][] most developers ever need.
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 being accessible on older ones too.
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.
For more info, docs and tutorials, check out the [official website][].<br>
For **Leaflet downloads** (including the built main version), check out the [download page][].
We're happy to meet new contributors.
If you want to **get involved** with Leaflet development, check out the [contribution guide][contribute].
Let's make the best mapping library that will ever exist,
and push the limits of what's possible with online maps!
[![CI](https://github.com/Leaflet/Leaflet/actions/workflows/main.yml/badge.svg)](https://github.com/Leaflet/Leaflet/actions/workflows/main.yml)
[contributors]: https://github.com/Leaflet/Leaflet/graphs/contributors
[features]: http://leafletjs.com/#features
[plugins]: http://leafletjs.com/plugins.html
[well-documented]: http://leafletjs.com/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/main/CONTRIBUTING.md "A guide to contributing to Leaflet"
[official website]: http://leafletjs.com
[download page]: http://leafletjs.com/download.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+661
View File
@@ -0,0 +1,661 @@
/* 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 {
max-width: none !important;
max-height: none !important;
}
.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;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.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-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;
}
svg.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-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
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,
.leaflet-bar a:focus {
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;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.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;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.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 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-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;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.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-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-interactive {
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;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+149
View File
@@ -0,0 +1,149 @@
{
"name": "leaflet",
"version": "1.9.4",
"homepage": "https://leafletjs.com/",
"description": "JavaScript library for mobile-friendly interactive maps",
"devDependencies": {
"@mapbox/eslint-plugin-script-tags": "^1.0.0",
"@rollup/plugin-json": "^4.1.0",
"bundlemon": "^1.4.0",
"eslint": "^8.23.0",
"eslint-config-mourner": "^2.0.3",
"git-rev-sync": "^3.0.2",
"happen": "~0.3.2",
"husky": "^8.0.1",
"karma": "^6.4.0",
"karma-chrome-launcher": "^3.1.1",
"karma-edge-launcher": "^0.4.2",
"karma-expect": "^1.1.3",
"karma-firefox-launcher": "^2.1.2",
"karma-ie-launcher": "^1.0.0",
"karma-mocha": "^2.0.1",
"karma-rollup-preprocessor": "^7.0.8",
"karma-safari-launcher": "~1.0.0",
"karma-sinon": "^1.0.5",
"leafdoc": "^2.3.0",
"lint-staged": "^13.0.3",
"mocha": "^9.2.2",
"prosthetic-hand": "^1.4.0",
"rollup": "^2.78.1",
"rollup-plugin-git-version": "^0.3.1",
"sinon": "^7.5.0",
"ssri": "^9.0.1",
"uglify-js": "^3.17.0"
},
"main": "dist/leaflet-src.js",
"style": "dist/leaflet.css",
"files": [
"dist",
"src",
"!dist/leaflet.zip",
"!*.leafdoc",
"CHANGELOG.md"
],
"scripts": {
"docs": "node ./build/docs.js && node ./build/integrity.js",
"test": "karma start ./spec/karma.conf.js",
"build": "npm run rollup && npm run uglify",
"lint": "eslint .",
"lintfix": "npm run lint -- --fix",
"rollup": "rollup -c build/rollup-config.js",
"watch": "rollup -w -c build/rollup-config.js",
"uglify": "uglifyjs dist/leaflet-src.js -c -m -o dist/leaflet.js --source-map filename=dist/leaflet.js.map --source-map content=dist/leaflet-src.js.map --source-map url=leaflet.js.map --comments",
"bundlemon": "bundlemon --subProject js --defaultCompression none && bundlemon --subProject js-gzip --defaultCompression gzip",
"serve": "cd docs && bundle exec jekyll serve",
"prepare": "husky install"
},
"eslintConfig": {
"ignorePatterns": [
"dist",
"debug",
"docs/docs/highlight",
"docs/examples/choropleth/us-states.js",
"docs/examples/geojson/sample-geojson.js",
"docs/examples/map-panes/eu-countries.js",
"docs/examples/extending/extending-2-layers.md",
"docs/_posts/2012*",
"docs/_site",
"build/integrity.js"
],
"root": true,
"env": {
"commonjs": true,
"amd": true,
"node": false
},
"extends": "mourner",
"plugins": [
"@mapbox/eslint-plugin-script-tags"
],
"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,
"no-unused-expressions": [
"error",
{
"allowShortCircuit": true
}
]
},
"overrides": [
{
"files": [
"build/**/*"
],
"env": {
"node": true
},
"rules": {
"global-require": 0
}
},
{
"files": [
"*.md"
],
"rules": {
"eol-last": 0,
"no-unused-vars": 0
}
}
]
},
"repository": {
"type": "git",
"url": "git://github.com/Leaflet/Leaflet.git"
},
"keywords": [
"gis",
"map"
],
"license": "BSD-2-Clause",
"lint-staged": {
"*.(js|md)": "eslint --cache --fix"
}
}
+24
View File
@@ -0,0 +1,24 @@
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';
+148
View File
@@ -0,0 +1,148 @@
import {Control} from './Control';
import {Map} from '../map/Map';
import * as Util from '../core/Util';
import * as DomEvent from '../dom/DomEvent';
import * as DomUtil from '../dom/DomUtil';
import Browser from '../core/Browser';
var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
/*
* @class Control.Attribution
* @aka L.Control.Attribution
* @inherits Control
*
* The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
*/
export var Attribution = Control.extend({
// @section
// @aka Control.Attribution options
options: {
position: 'bottomright',
// @option prefix: String|false = 'Leaflet'
// The HTML text shown before the attributions. Pass `false` to disable.
prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
},
initialize: function (options) {
Util.setOptions(this, options);
this._attributions = {};
},
onAdd: function (map) {
map.attributionControl = this;
this._container = DomUtil.create('div', 'leaflet-control-attribution');
DomEvent.disableClickPropagation(this._container);
// TODO ugly, refactor
for (var i in map._layers) {
if (map._layers[i].getAttribution) {
this.addAttribution(map._layers[i].getAttribution());
}
}
this._update();
map.on('layeradd', this._addAttribution, this);
return this._container;
},
onRemove: function (map) {
map.off('layeradd', this._addAttribution, this);
},
_addAttribution: function (ev) {
if (ev.layer.getAttribution) {
this.addAttribution(ev.layer.getAttribution());
ev.layer.once('remove', function () {
this.removeAttribution(ev.layer.getAttribution());
}, this);
}
},
// @method setPrefix(prefix: String|false): this
// The HTML text shown before the attributions. Pass `false` to disable.
setPrefix: function (prefix) {
this.options.prefix = prefix;
this._update();
return this;
},
// @method addAttribution(text: String): this
// Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
addAttribution: function (text) {
if (!text) { return this; }
if (!this._attributions[text]) {
this._attributions[text] = 0;
}
this._attributions[text]++;
this._update();
return this;
},
// @method removeAttribution(text: String): this
// Removes an attribution text.
removeAttribution: function (text) {
if (!text) { return this; }
if (this._attributions[text]) {
this._attributions[text]--;
this._update();
}
return this;
},
_update: function () {
if (!this._map) { return; }
var attribs = [];
for (var i in this._attributions) {
if (this._attributions[i]) {
attribs.push(i);
}
}
var prefixAndAttribs = [];
if (this.options.prefix) {
prefixAndAttribs.push(this.options.prefix);
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(', '));
}
this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
}
});
// @namespace Map
// @section Control options
// @option attributionControl: Boolean = true
// Whether a [attribution control](#control-attribution) is added to the map by default.
Map.mergeOptions({
attributionControl: true
});
Map.addInitHook(function () {
if (this.options.attributionControl) {
new Attribution().addTo(this);
}
});
// @namespace Control.Attribution
// @factory L.control.attribution(options: Control.Attribution options)
// Creates an attribution control.
export var attribution = function (options) {
return new Attribution(options);
};
+443
View File
@@ -0,0 +1,443 @@
import {Control} from './Control';
import * as Util from '../core/Util';
import * as DomEvent from '../dom/DomEvent';
import * as DomUtil from '../dom/DomUtil';
/*
* @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](https://leafletjs.com/examples/layers-control/)). 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
* {
* "<someName1>": layer1,
* "<someName2>": layer2
* }
* ```
*
* The layer names can contain HTML, which allows you to add additional styling to the items:
*
* ```js
* {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
* ```
*/
export var Layers = 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, touch, or keyboard activation.
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,
// @option sortLayers: Boolean = false
// Whether to sort the layers. When `false`, layers will keep the order
// in which they were added to the control.
sortLayers: false,
// @option sortFunction: Function = *
// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
// that will be used for sorting the layers, when `sortLayers` is `true`.
// The function receives both the `L.Layer` instances and their names, as in
// `sortFunction(layerA, layerB, nameA, nameB)`.
// By default, it sorts layers alphabetically by their name.
sortFunction: function (layerA, layerB, nameA, nameB) {
return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
}
},
initialize: function (baseLayers, overlays, options) {
Util.setOptions(this, options);
this._layerControlInputs = [];
this._layers = [];
this._lastZIndex = 0;
this._handlingClick = false;
this._preventClick = 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);
for (var i = 0; i < this._layers.length; i++) {
this._layers[i].layer.on('add remove', this._onLayerChange, this);
}
return this._container;
},
addTo: function (map) {
Control.prototype.addTo.call(this, map);
// Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
return this._expandIfNotCollapsed();
},
onRemove: function () {
this._map.off('zoomend', this._checkDisabledLayers, this);
for (var i = 0; i < this._layers.length; i++) {
this._layers[i].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(Util.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 () {
DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
this._section.style.height = null;
var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
if (acceptableHeight < this._section.clientHeight) {
DomUtil.addClass(this._section, 'leaflet-control-layers-scrollbar');
this._section.style.height = acceptableHeight + 'px';
} else {
DomUtil.removeClass(this._section, 'leaflet-control-layers-scrollbar');
}
this._checkDisabledLayers();
return this;
},
// @method collapse(): this
// Collapse the control container if expanded.
collapse: function () {
DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
return this;
},
_initLayout: function () {
var className = 'leaflet-control-layers',
container = this._container = DomUtil.create('div', className),
collapsed = this.options.collapsed;
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
container.setAttribute('aria-haspopup', true);
DomEvent.disableClickPropagation(container);
DomEvent.disableScrollPropagation(container);
var section = this._section = DomUtil.create('section', className + '-list');
if (collapsed) {
this._map.on('click', this.collapse, this);
DomEvent.on(container, {
mouseenter: this._expandSafely,
mouseleave: this.collapse
}, this);
}
var link = this._layersLink = DomUtil.create('a', className + '-toggle', container);
link.href = '#';
link.title = 'Layers';
link.setAttribute('role', 'button');
DomEvent.on(link, {
keydown: function (e) {
if (e.keyCode === 13) {
this._expandSafely();
}
},
// Certain screen readers intercept the key event and instead send a click event
click: function (e) {
DomEvent.preventDefault(e);
this._expandSafely();
}
}, this);
if (!collapsed) {
this.expand();
}
this._baseLayersList = DomUtil.create('div', className + '-base', section);
this._separator = DomUtil.create('div', className + '-separator', section);
this._overlaysList = DomUtil.create('div', className + '-overlays', section);
container.appendChild(section);
},
_getLayer: function (id) {
for (var i = 0; i < this._layers.length; i++) {
if (this._layers[i] && Util.stamp(this._layers[i].layer) === id) {
return this._layers[i];
}
}
},
_addLayer: function (layer, name, overlay) {
if (this._map) {
layer.on('add remove', this._onLayerChange, this);
}
this._layers.push({
layer: layer,
name: name,
overlay: overlay
});
if (this.options.sortLayers) {
this._layers.sort(Util.bind(function (a, b) {
return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
}, this));
}
if (this.options.autoZIndex && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
this._expandIfNotCollapsed();
},
_update: function () {
if (!this._container) { return this; }
DomUtil.empty(this._baseLayersList);
DomUtil.empty(this._overlaysList);
this._layerControlInputs = [];
var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
for (i = 0; i < this._layers.length; i++) {
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(Util.stamp(e.target));
// @namespace Map
// @section Layer events
// @event baselayerchange: LayersControlEvent
// Fired when the base layer is changed through the [layers control](#control-layers).
// @event overlayadd: LayersControlEvent
// Fired when an overlay is selected through the [layers control](#control-layers).
// @event overlayremove: LayersControlEvent
// Fired when an overlay is deselected through the [layers 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 https://stackoverflow.com/a/119079)
_createRadioElement: function (name, checked) {
var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
name + '"' + (checked ? ' checked="checked"' : '') + '/>';
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_' + Util.stamp(this), checked);
}
this._layerControlInputs.push(input);
input.layerId = Util.stamp(obj.layer);
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('span');
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 () {
// expanding the control on mobile with a click can cause adding a layer - we don't want this
if (this._preventClick) {
return;
}
var inputs = this._layerControlInputs,
input, layer;
var addedLayers = [],
removedLayers = [];
this._handlingClick = true;
for (var i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this._getLayer(input.layerId).layer;
if (input.checked) {
addedLayers.push(layer);
} else if (!input.checked) {
removedLayers.push(layer);
}
}
// Bugfix issue 2318: Should remove all old layers before readding new ones
for (i = 0; i < removedLayers.length; i++) {
if (this._map.hasLayer(removedLayers[i])) {
this._map.removeLayer(removedLayers[i]);
}
}
for (i = 0; i < addedLayers.length; i++) {
if (!this._map.hasLayer(addedLayers[i])) {
this._map.addLayer(addedLayers[i]);
}
}
this._handlingClick = false;
this._refocusOnMap();
},
_checkDisabledLayers: function () {
var inputs = this._layerControlInputs,
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);
}
},
_expandIfNotCollapsed: function () {
if (this._map && !this.options.collapsed) {
this.expand();
}
return this;
},
_expandSafely: function () {
var section = this._section;
this._preventClick = true;
DomEvent.on(section, 'click', DomEvent.preventDefault);
this.expand();
var that = this;
setTimeout(function () {
DomEvent.off(section, 'click', DomEvent.preventDefault);
that._preventClick = false;
});
}
});
// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
// Creates a layers 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.
export var layers = function (baseLayers, overlays, options) {
return new Layers(baseLayers, overlays, options);
};
+132
View File
@@ -0,0 +1,132 @@
import {Control} from './Control';
import * as DomUtil from '../dom/DomUtil';
/*
* @class Control.Scale
* @aka L.Control.Scale
* @inherits Control
*
* 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`.
*
* @example
*
* ```js
* L.control.scale().addTo(map);
* ```
*/
export var Scale = Control.extend({
// @section
// @aka Control.Scale options
options: {
position: 'bottomleft',
// @option maxWidth: Number = 100
// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
maxWidth: 100,
// @option metric: Boolean = True
// Whether to show the metric scale line (m/km).
metric: true,
// @option imperial: Boolean = True
// Whether to show the imperial scale line (mi/ft).
imperial: true
// @option updateWhenIdle: Boolean = false
// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
},
onAdd: function (map) {
var className = 'leaflet-control-scale',
container = DomUtil.create('div', className),
options = this.options;
this._addScales(options, className + '-line', container);
map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
map.whenReady(this._update, this);
return container;
},
onRemove: function (map) {
map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
},
_addScales: function (options, className, container) {
if (options.metric) {
this._mScale = DomUtil.create('div', className, container);
}
if (options.imperial) {
this._iScale = DomUtil.create('div', className, container);
}
},
_update: function () {
var map = this._map,
y = map.getSize().y / 2;
var maxMeters = map.distance(
map.containerPointToLatLng([0, y]),
map.containerPointToLatLng([this.options.maxWidth, y]));
this._updateScales(maxMeters);
},
_updateScales: function (maxMeters) {
if (this.options.metric && maxMeters) {
this._updateMetric(maxMeters);
}
if (this.options.imperial && maxMeters) {
this._updateImperial(maxMeters);
}
},
_updateMetric: function (maxMeters) {
var meters = this._getRoundNum(maxMeters),
label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
this._updateScale(this._mScale, label, meters / maxMeters);
},
_updateImperial: function (maxMeters) {
var maxFeet = maxMeters * 3.2808399,
maxMiles, miles, feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
} else {
feet = this._getRoundNum(maxFeet);
this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
}
},
_updateScale: function (scale, text, ratio) {
scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
scale.innerHTML = text;
},
_getRoundNum: function (num) {
var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
d = num / pow10;
d = d >= 10 ? 10 :
d >= 5 ? 5 :
d >= 3 ? 3 :
d >= 2 ? 2 : 1;
return pow10 * d;
}
});
// @factory L.control.scale(options?: Control.Scale options)
// Creates an scale control with the given options.
export var scale = function (options) {
return new Scale(options);
};
+146
View File
@@ -0,0 +1,146 @@
import {Control} from './Control';
import {Map} from '../map/Map';
import * as DomUtil from '../dom/DomUtil';
import * as DomEvent from '../dom/DomEvent';
/*
* @class Control.Zoom
* @aka L.Control.Zoom
* @inherits Control
*
* A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
*/
export var Zoom = Control.extend({
// @section
// @aka Control.Zoom options
options: {
position: 'topleft',
// @option zoomInText: String = '<span aria-hidden="true">+</span>'
// The text set on the 'zoom in' button.
zoomInText: '<span aria-hidden="true">+</span>',
// @option zoomInTitle: String = 'Zoom in'
// The title set on the 'zoom in' button.
zoomInTitle: 'Zoom in',
// @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
// The text set on the 'zoom out' button.
zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
// @option zoomOutTitle: String = 'Zoom out'
// The title set on the 'zoom out' button.
zoomOutTitle: 'Zoom out'
},
onAdd: function (map) {
var zoomName = 'leaflet-control-zoom',
container = DomUtil.create('div', zoomName + ' leaflet-bar'),
options = this.options;
this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
zoomName + '-in', container, this._zoomIn);
this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
zoomName + '-out', container, this._zoomOut);
this._updateDisabled();
map.on('zoomend zoomlevelschange', this._updateDisabled, this);
return container;
},
onRemove: function (map) {
map.off('zoomend zoomlevelschange', this._updateDisabled, this);
},
disable: function () {
this._disabled = true;
this._updateDisabled();
return this;
},
enable: function () {
this._disabled = false;
this._updateDisabled();
return this;
},
_zoomIn: function (e) {
if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_zoomOut: function (e) {
if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
}
},
_createButton: function (html, title, className, container, fn) {
var link = DomUtil.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.title = title;
/*
* Will force screen readers like VoiceOver to read this as "Zoom in - button"
*/
link.setAttribute('role', 'button');
link.setAttribute('aria-label', title);
DomEvent.disableClickPropagation(link);
DomEvent.on(link, 'click', DomEvent.stop);
DomEvent.on(link, 'click', fn, this);
DomEvent.on(link, 'click', this._refocusOnMap, this);
return link;
},
_updateDisabled: function () {
var map = this._map,
className = 'leaflet-disabled';
DomUtil.removeClass(this._zoomInButton, className);
DomUtil.removeClass(this._zoomOutButton, className);
this._zoomInButton.setAttribute('aria-disabled', 'false');
this._zoomOutButton.setAttribute('aria-disabled', 'false');
if (this._disabled || map._zoom === map.getMinZoom()) {
DomUtil.addClass(this._zoomOutButton, className);
this._zoomOutButton.setAttribute('aria-disabled', 'true');
}
if (this._disabled || map._zoom === map.getMaxZoom()) {
DomUtil.addClass(this._zoomInButton, className);
this._zoomInButton.setAttribute('aria-disabled', 'true');
}
}
});
// @namespace Map
// @section Control options
// @option zoomControl: Boolean = true
// Whether a [zoom control](#control-zoom) is added to the map by default.
Map.mergeOptions({
zoomControl: true
});
Map.addInitHook(function () {
if (this.options.zoomControl) {
// @section Controls
// @property zoomControl: Control.Zoom
// The default zoom control (only available if the
// [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
this.zoomControl = new Zoom();
this.addControl(this.zoomControl);
}
});
// @namespace Control.Zoom
// @factory L.control.zoom(options: Control.Zoom options)
// Creates a zoom control
export var zoom = function (options) {
return new Zoom(options);
};
+174
View File
@@ -0,0 +1,174 @@
import {Class} from '../core/Class';
import {Map} from '../map/Map';
import * as Util from '../core/Util';
import * as DomUtil from '../dom/DomUtil';
/*
* @class Control
* @aka L.Control
* @inherits Class
*
* L.Control is a base class for implementing map controls. Handles positioning.
* All other controls extend from this class.
*/
export var Control = Class.extend({
// @section
// @aka Control Options
options: {
// @option position: String = 'topright'
// The position of the control (one of the map corners). Possible values are `'topleft'`,
// `'topright'`, `'bottomleft'` or `'bottomright'`
position: 'topright'
},
initialize: function (options) {
Util.setOptions(this, options);
},
/* @section
* Classes extending L.Control will inherit the following methods:
*
* @method getPosition: string
* Returns the position of the control.
*/
getPosition: function () {
return this.options.position;
},
// @method setPosition(position: string): this
// Sets the position of the control.
setPosition: function (position) {
var map = this._map;
if (map) {
map.removeControl(this);
}
this.options.position = position;
if (map) {
map.addControl(this);
}
return this;
},
// @method getContainer: HTMLElement
// Returns the HTMLElement that contains the control.
getContainer: function () {
return this._container;
},
// @method addTo(map: Map): this
// Adds the control to the given map.
addTo: function (map) {
this.remove();
this._map = map;
var container = this._container = this.onAdd(map),
pos = this.getPosition(),
corner = map._controlCorners[pos];
DomUtil.addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
this._map.on('unload', this.remove, this);
return this;
},
// @method remove: this
// Removes the control from the map it is currently active on.
remove: function () {
if (!this._map) {
return this;
}
DomUtil.remove(this._container);
if (this.onRemove) {
this.onRemove(this._map);
}
this._map.off('unload', this.remove, this);
this._map = null;
return this;
},
_refocusOnMap: function (e) {
// if map exists and event is not a keyboard event
if (this._map && e && e.screenX > 0 && e.screenY > 0) {
this._map.getContainer().focus();
}
}
});
export var control = function (options) {
return new Control(options);
};
/* @section Extension methods
* @uninheritable
*
* Every control should extend from `L.Control` and (re-)implement the following methods.
*
* @method onAdd(map: Map): HTMLElement
* Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
*
* @method onRemove(map: Map)
* Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
*/
/* @namespace Map
* @section Methods for Layers and Controls
*/
Map.include({
// @method addControl(control: Control): this
// Adds the given control to the map
addControl: function (control) {
control.addTo(this);
return this;
},
// @method removeControl(control: Control): this
// Removes the given control from the map
removeControl: function (control) {
control.remove();
return this;
},
_initControlPos: function () {
var corners = this._controlCorners = {},
l = 'leaflet-',
container = this._controlContainer =
DomUtil.create('div', l + 'control-container', this._container);
function createCorner(vSide, hSide) {
var className = l + vSide + ' ' + l + hSide;
corners[vSide + hSide] = DomUtil.create('div', className, container);
}
createCorner('top', 'left');
createCorner('top', 'right');
createCorner('bottom', 'left');
createCorner('bottom', 'right');
},
_clearControlPos: function () {
for (var i in this._controlCorners) {
DomUtil.remove(this._controlCorners[i]);
}
DomUtil.remove(this._controlContainer);
delete this._controlCorners;
delete this._controlContainer;
}
});
+17
View File
@@ -0,0 +1,17 @@
import {Control, control} from './Control';
import {Layers, layers} from './Control.Layers';
import {Zoom, zoom} from './Control.Zoom';
import {Scale, scale} from './Control.Scale';
import {Attribution, attribution} from './Control.Attribution';
Control.Layers = Layers;
Control.Zoom = Zoom;
Control.Scale = Scale;
Control.Attribution = Attribution;
control.layers = layers;
control.zoom = zoom;
control.scale = scale;
control.attribution = attribution;
export {Control, control};
+220
View File
@@ -0,0 +1,220 @@
import * as Util from './Util';
import {svgCreate} from '../layer/vector/SVG.Util';
/*
* @namespace Browser
* @aka L.Browser
*
* A namespace with static properties for browser/feature detection used by Leaflet internally.
*
* @example
*
* ```js
* if (L.Browser.ielt9) {
* alert('Upgrade your browser, dude!');
* }
* ```
*/
var style = document.documentElement.style;
// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
var ie = 'ActiveXObject' in window;
// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
var ielt9 = ie && !document.addEventListener;
// @property edge: Boolean; `true` for the Edge web browser.
var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
// @property webkit: Boolean;
// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
var webkit = userAgentContains('webkit');
// @property android: Boolean
// **Deprecated.** `true` for any browser running on an Android platform.
var android = userAgentContains('android');
// @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
var android23 = userAgentContains('android 2') || userAgentContains('android 3');
/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
// @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
// @property opera: Boolean; `true` for the Opera browser
var opera = !!window.opera;
// @property chrome: Boolean; `true` for the Chrome browser.
var chrome = !edge && userAgentContains('chrome');
// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
// @property safari: Boolean; `true` for the Safari browser.
var safari = !chrome && userAgentContains('safari');
var phantom = userAgentContains('phantom');
// @property opera12: Boolean
// `true` for the Opera browser supporting CSS transforms (version 12 or later).
var opera12 = 'OTransition' in style;
// @property win: Boolean; `true` when the browser is running in a Windows platform
var win = navigator.platform.indexOf('Win') === 0;
// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
var ie3d = ie && ('transition' in style);
// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
var gecko3d = 'MozPerspective' in style;
// @property any3d: Boolean
// `true` for all browsers supporting CSS transforms.
var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
// @property mobile: Boolean; `true` for all browsers running in a mobile device.
var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
var mobileWebkit = mobile && webkit;
// @property mobileWebkit3d: Boolean
// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
var mobileWebkit3d = mobile && webkit3d;
// @property msPointer: Boolean
// `true` for browsers implementing the Microsoft touch events model (notably IE10).
var msPointer = !window.PointerEvent && window.MSPointerEvent;
// @property pointer: Boolean
// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
var pointer = !!(window.PointerEvent || msPointer);
// @property touchNative: Boolean
// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
// **This does not necessarily mean** that the browser is running in a computer with
// a touchscreen, it only means that the browser is capable of understanding
// touch events.
var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
// @property touch: Boolean
// `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
// Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
var touch = !window.L_NO_TOUCH && (touchNative || pointer);
// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
var mobileOpera = mobile && opera;
// @property mobileGecko: Boolean
// `true` for gecko-based browsers running in a mobile device.
var mobileGecko = mobile && gecko;
// @property retina: Boolean
// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
// @property passiveEvents: Boolean
// `true` for browsers that support passive events.
var passiveEvents = (function () {
var supportsPassiveOption = false;
try {
var opts = Object.defineProperty({}, 'passive', {
get: function () { // eslint-disable-line getter-return
supportsPassiveOption = true;
}
});
window.addEventListener('testPassiveEventSupport', Util.falseFn, opts);
window.removeEventListener('testPassiveEventSupport', Util.falseFn, opts);
} catch (e) {
// Errors can safely be ignored since this is only a browser support test.
}
return supportsPassiveOption;
}());
// @property canvas: Boolean
// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
var canvas = (function () {
return !!document.createElement('canvas').getContext;
}());
// @property svg: Boolean
// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
var inlineSvg = !!svg && (function () {
var div = document.createElement('div');
div.innerHTML = '<svg/>';
return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
})();
// @property vml: Boolean
// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
var vml = !svg && (function () {
try {
var div = document.createElement('div');
div.innerHTML = '<v:shape adj="1"/>';
var shape = div.firstChild;
shape.style.behavior = 'url(#default#VML)';
return shape && (typeof shape.adj === 'object');
} catch (e) {
return false;
}
}());
// @property mac: Boolean; `true` when the browser is running in a Mac platform
var mac = navigator.platform.indexOf('Mac') === 0;
// @property mac: Boolean; `true` when the browser is running in a Linux platform
var linux = navigator.platform.indexOf('Linux') === 0;
function userAgentContains(str) {
return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
}
export default {
ie: ie,
ielt9: ielt9,
edge: edge,
webkit: webkit,
android: android,
android23: android23,
androidStock: androidStock,
opera: opera,
chrome: chrome,
gecko: gecko,
safari: safari,
phantom: phantom,
opera12: opera12,
win: win,
ie3d: ie3d,
webkit3d: webkit3d,
gecko3d: gecko3d,
any3d: any3d,
mobile: mobile,
mobileWebkit: mobileWebkit,
mobileWebkit3d: mobileWebkit3d,
msPointer: msPointer,
pointer: pointer,
touch: touch,
touchNative: touchNative,
mobileOpera: mobileOpera,
mobileGecko: mobileGecko,
retina: retina,
passiveEvents: passiveEvents,
canvas: canvas,
svg: svg,
vml: vml,
inlineSvg: inlineSvg,
mac: mac,
linux: linux
};
+135
View File
@@ -0,0 +1,135 @@
import * as Util from './Util';
// @class Class
// @aka L.Class
// @section
// @uninheritable
// Thanks to John Resig and Dean Edwards for inspiration!
export function Class() {}
Class.extend = function (props) {
// @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included.
// Returns a Javascript function that is a class constructor (to be called with `new`).
var NewClass = function () {
Util.setOptions(this);
// call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
// call all constructor hooks
this.callInitHooks();
};
var parentProto = NewClass.__super__ = this.prototype;
var proto = Util.create(parentProto);
proto.constructor = NewClass;
NewClass.prototype = proto;
// inherit parent's statics
for (var i in this) {
if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
if (props.statics) {
Util.extend(NewClass, props.statics);
}
// mix includes into the prototype
if (props.includes) {
checkDeprecatedMixinEvents(props.includes);
Util.extend.apply(null, [proto].concat(props.includes));
}
// mix given properties into the prototype
Util.extend(proto, props);
delete proto.statics;
delete proto.includes;
// merge options
if (proto.options) {
proto.options = parentProto.options ? Util.create(parentProto.options) : {};
Util.extend(proto.options, props.options);
}
proto._initHooks = [];
// add method for calling all hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) { return; }
if (parentProto.callInitHooks) {
parentProto.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (var i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
Class.include = function (props) {
var parentOptions = this.prototype.options;
Util.extend(this.prototype, props);
if (props.options) {
this.prototype.options = parentOptions;
this.mergeOptions(props.options);
}
return this;
};
// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
Class.mergeOptions = function (options) {
Util.extend(this.prototype.options, options);
return this;
};
// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
Class.addInitHook = function (fn) { // (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
return this;
};
function checkDeprecatedMixinEvents(includes) {
/* global L: true */
if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
includes = Util.isArray(includes) ? includes : [includes];
for (var i = 0; i < includes.length; i++) {
if (includes[i] === L.Mixin.Events) {
console.warn('Deprecated include of L.Mixin.Events: ' +
'this property will be removed in future releases, ' +
'please inherit from L.Evented instead.', new Error().stack);
}
}
}
+197
View File
@@ -0,0 +1,197 @@
@class Class
@aka L.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.
@example
```js
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");
```
@section Class Factories
@example
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:
```js
new L.Map('map'); // becomes:
L.map('map');
```
The factories are implemented very easily, and you can do this for your own classes:
```js
L.map = function (id, options) {
return new L.Map(id, options);
};
```
@section Inheritance
@example
You use L.Class.extend to define new classes, but you can use the same method on any class to inherit from it:
```js
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:
```js
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(this, "Yo");
},
greet: function (name) {
MyClass.prototype.greet.call(this, 'bro ' + name + '!');
}
});
var a = new MyChildClass();
a.greet('Jason'); // alerts "Yo, bro Jason!"
```
@section Options
@example
`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:
```js
var MyClass = L.Class.extend({
options: {
myOption1: 'foo',
myOption2: 'bar'
}
});
var MyChildClass = MyClass.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`](#util-setoptions), a method for
conveniently merging options passed to constructor with the defaults
defines in the class:
```js
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}
```
Note that the options object allows any keys, not just
the options defined by the class and its base classes.
This means you can use the options object to store
application specific information, as long as you avoid
keys that are already used by the class in question.
@section Includes
@example
`includes` is a special class property that merges all specified objects into the class (such objects are called mixins).
```js
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:
```js
MyClass.include(MyMixin);
```
`statics` is just a convenience property that injects specified object properties as the static properties of the class, useful for defining constants:
```js
var MyClass = L.Class.extend({
statics: {
FOO: 'bar',
BLA: 5
}
});
MyClass.FOO; // 'bar'
```
@section Constructor hooks
@example
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:
```js
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:
```js
MyClass.addInitHook('methodName', arg1, arg2, …);
```
+344
View File
@@ -0,0 +1,344 @@
import {Class} from './Class';
import * as Util from './Util';
/*
* @class Evented
* @aka L.Evented
* @inherits Class
*
* 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
*
* ```js
* 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:
*
* ```js
* function onClick(e) { ... }
*
* map.on('click', onClick);
* map.off('click', onClick);
* ```
*/
export var Events = {
/* @method on(type: String, fn: Function, context?: Object): this
* Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
*
* @alternative
* @method on(eventMap: Object): this
* Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
*/
on: function (types, fn, context) {
// types can be a map of types/handlers
if (typeof types === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn);
}
} else {
// types can be a string of space-separated words
types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context);
}
}
return this;
},
/* @method off(type: String, fn?: Function, context?: Object): this
* Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
*
* @alternative
* @method off(eventMap: Object): this
* Removes a set of type/listener pairs.
*
* @alternative
* @method off: this
* Removes all listeners to all events on the object. This includes implicitly attached events.
*/
off: function (types, fn, context) {
if (!arguments.length) {
// clear all listeners if called without arguments
delete this._events;
} else if (typeof types === 'object') {
for (var type in types) {
this._off(type, types[type], fn);
}
} else {
types = Util.splitWords(types);
var removeAll = arguments.length === 1;
for (var i = 0, len = types.length; i < len; i++) {
if (removeAll) {
this._off(types[i]);
} else {
this._off(types[i], fn, context);
}
}
}
return this;
},
// attach listener (without syntactic sugar now)
_on: function (type, fn, context, _once) {
if (typeof fn !== 'function') {
console.warn('wrong listener type: ' + typeof fn);
return;
}
// check if fn already there
if (this._listens(type, fn, context) !== false) {
return;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
var newListener = {fn: fn, ctx: context};
if (_once) {
newListener.once = true;
}
this._events = this._events || {};
this._events[type] = this._events[type] || [];
this._events[type].push(newListener);
},
_off: function (type, fn, context) {
var listeners,
i,
len;
if (!this._events) {
return;
}
listeners = this._events[type];
if (!listeners) {
return;
}
if (arguments.length === 1) { // remove all
if (this._firingCount) {
// Set all removed listeners to noop
// so they are not called if remove happens in fire
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = Util.falseFn;
}
}
// clear all listeners for a type if function isn't specified
delete this._events[type];
return;
}
if (typeof fn !== 'function') {
console.warn('wrong listener type: ' + typeof fn);
return;
}
// find fn and remove it
var index = this._listens(type, fn, context);
if (index !== false) {
var listener = listeners[index];
if (this._firingCount) {
// set the removed listener to noop so that's not called if remove happens in fire
listener.fn = Util.falseFn;
/* copy array in case events are being fired */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(index, 1);
}
},
// @method fire(type: String, data?: Object, propagate?: Boolean): this
// Fires an event of the specified type. You can optionally provide a data
// object — the first argument of the listener function will contain its
// properties. The event can optionally be propagated to event parents.
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
var event = Util.extend({}, data, {
type: type,
target: this,
sourceTarget: data && data.sourceTarget || this
});
if (this._events) {
var listeners = this._events[type];
if (listeners) {
this._firingCount = (this._firingCount + 1) || 1;
for (var i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
// off overwrites l.fn, so we need to copy fn to a var
var fn = l.fn;
if (l.once) {
this.off(type, fn, l.ctx);
}
fn.call(l.ctx || this, event);
}
this._firingCount--;
}
}
if (propagate) {
// propagate the event to parents (set with addEventParent)
this._propagateEvent(event);
}
return this;
},
// @method listens(type: String, propagate?: Boolean): Boolean
// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
// Returns `true` if a particular event type has any listeners attached to it.
// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
listens: function (type, fn, context, propagate) {
if (typeof type !== 'string') {
console.warn('"string" type argument expected');
}
// we don't overwrite the input `fn` value, because we need to use it for propagation
var _fn = fn;
if (typeof fn !== 'function') {
propagate = !!fn;
_fn = undefined;
context = undefined;
}
var listeners = this._events && this._events[type];
if (listeners && listeners.length) {
if (this._listens(type, _fn, context) !== false) {
return true;
}
}
if (propagate) {
// also check parents for listeners if event propagates
for (var id in this._eventParents) {
if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
}
}
return false;
},
// returns the index (number) or false
_listens: function (type, fn, context) {
if (!this._events) {
return false;
}
var listeners = this._events[type] || [];
if (!fn) {
return !!listeners.length;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return i;
}
}
return false;
},
// @method once(…): this
// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
once: function (types, fn, context) {
// types can be a map of types/handlers
if (typeof types === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn, true);
}
} else {
// types can be a string of space-separated words
types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context, true);
}
}
return this;
},
// @method addEventParent(obj: Evented): this
// Adds an event parent - an `Evented` that will receive propagated events
addEventParent: function (obj) {
this._eventParents = this._eventParents || {};
this._eventParents[Util.stamp(obj)] = obj;
return this;
},
// @method removeEventParent(obj: Evented): this
// Removes an event parent, so it will stop receiving propagated events
removeEventParent: function (obj) {
if (this._eventParents) {
delete this._eventParents[Util.stamp(obj)];
}
return this;
},
_propagateEvent: function (e) {
for (var id in this._eventParents) {
this._eventParents[id].fire(e.type, Util.extend({
layer: e.target,
propagatedFrom: e.target
}, e), true);
}
}
};
// aliases; we should ditch those eventually
// @method addEventListener(…): this
// Alias to [`on(…)`](#evented-on)
Events.addEventListener = Events.on;
// @method removeEventListener(…): this
// Alias to [`off(…)`](#evented-off)
// @method clearAllEventListeners(…): this
// Alias to [`off()`](#evented-off)
Events.removeEventListener = Events.clearAllEventListeners = Events.off;
// @method addOneTimeEventListener(…): this
// Alias to [`once(…)`](#evented-once)
Events.addOneTimeEventListener = Events.once;
// @method fireEvent(…): this
// Alias to [`fire(…)`](#evented-fire)
Events.fireEvent = Events.fire;
// @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens)
Events.hasEventListeners = Events.listens;
export var Evented = Class.extend(Events);
+143
View File
@@ -0,0 +1,143 @@
@namespace Event objects
Whenever a class inheriting from `Evented` fires an event, a listener function
will be called with an event argument, which is a plain object containing
information about the event. For example:
```js
map.on('click', function(ev) {
alert(ev.latlng); // ev is an event object (MouseEvent in this case)
});
```
The information available depends on the event type:
@miniclass Event (Event objects)
@section
The base event object. All other event objects contain these properties too.
@property type: String
The event type (e.g. `'click'`).
@property target: Object
The object that fired the event. For propagated events, the last object in
the propagation chain that fired the event.
@property sourceTarget: Object
The object that originally fired the event. For non-propagated events, this will
be the same as the `target`.
@property propagatedFrom: Object
For propagated events, the last object that propagated the event to its
event parent.
@property layer: Object
**Deprecated.** The same as `propagatedFrom`.
@miniclass KeyboardEvent (Event objects)
@inherits Event
@property originalEvent: DOMEvent
The original [DOM `KeyboardEvent`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) that triggered this Leaflet event.
@miniclass MouseEvent (Event objects)
@inherits Event
@property latlng: LatLng
The geographical point where the mouse event occurred.
@property layerPoint: Point
Pixel coordinates of the point where the mouse event occurred relative to the map layer.
@property containerPoint: Point
Pixel coordinates of the point where the mouse event occurred relative to the map сontainer.
@property originalEvent: DOMEvent
The original [DOM `MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) or [DOM `TouchEvent`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent) that triggered this Leaflet event.
@miniclass LocationEvent (Event objects)
@inherits Event
@property latlng: LatLng
Detected geographical location of the user.
@property bounds: LatLngBounds
Geographical bounds of the area user is located in (with respect to the accuracy of location).
@property accuracy: Number
Accuracy of location in meters.
@property altitude: Number
Height of the position above the WGS84 ellipsoid in meters.
@property altitudeAccuracy: Number
Accuracy of altitude in meters.
@property heading: Number
The direction of travel in degrees counting clockwise from true North.
@property speed: Number
Current velocity in meters per second.
@property timestamp: Number
The time when the position was acquired.
@miniclass ErrorEvent (Event objects)
@inherits Event
@property message: String
Error message.
@property code: Number
Error code (if applicable).
@miniclass LayerEvent (Event objects)
@inherits Event
@property layer: Layer
The layer that was added or removed.
@miniclass LayersControlEvent (Event objects)
@inherits Event
@property layer: Layer
The layer that was added or removed.
@property name: String
The name of the layer that was added or removed.
@miniclass TileEvent (Event objects)
@inherits Event
@property tile: HTMLElement
The tile element (image).
@property coords: Point
Point object with the tile's `x`, `y`, and `z` (zoom level) coordinates.
@miniclass TileErrorEvent (Event objects)
@inherits Event
@property tile: HTMLElement
The tile element (image).
@property coords: Point
Point object with the tile's `x`, `y`, and `z` (zoom level) coordinates.
@property error: *
Error passed to the tile's `done()` callback.
@miniclass ResizeEvent (Event objects)
@inherits Event
@property oldSize: Point
The old size before resize event.
@property newSize: Point
The new size after the resize event.
@miniclass GeoJSONEvent (Event objects)
@inherits Event
@property layer: Layer
The layer for the GeoJSON feature that is being added to the map.
@property properties: Object
GeoJSON properties of the feature.
@property geometryType: String
GeoJSON geometry type of the feature.
@property id: String
GeoJSON ID of the feature (if present).
@miniclass PopupEvent (Event objects)
@inherits Event
@property popup: Popup
The popup that was opened or closed.
@miniclass TooltipEvent (Event objects)
@inherits Event
@property tooltip: Tooltip
The tooltip that was opened or closed.
@miniclass DragEndEvent (Event objects)
@inherits Event
@property distance: Number
The distance in pixels the draggable element was moved by.
@miniclass ZoomAnimEvent (Event objects)
@inherits Event
@property center: LatLng; The current center of the map
@property zoom: Number; The current zoom level of the map
@property noUpdate: Boolean; Whether layers should update their contents due to this event
+57
View File
@@ -0,0 +1,57 @@
import {Class} from './Class';
/*
L.Handler is a base class for handler classes that are used internally to inject
interaction features like dragging to classes like Map and Marker.
*/
// @class Handler
// @aka L.Handler
// Abstract class for map interaction handlers
export var Handler = Class.extend({
initialize: function (map) {
this._map = map;
},
// @method enable(): this
// Enables the handler
enable: function () {
if (this._enabled) { return this; }
this._enabled = true;
this.addHooks();
return this;
},
// @method disable(): this
// Disables the handler
disable: function () {
if (!this._enabled) { return this; }
this._enabled = false;
this.removeHooks();
return this;
},
// @method enabled(): Boolean
// Returns `true` if the handler is enabled
enabled: function () {
return !!this._enabled;
}
// @section Extension methods
// Classes inheriting from `Handler` must implement the two following methods:
// @method addHooks()
// Called when the handler is enabled, should add event hooks.
// @method removeHooks()
// Called when the handler is disabled, should remove the event hooks added previously.
});
// @section There is static function which can be called without instantiating L.Handler:
// @function addTo(map: Map, name: String): this
// Adds a new Handler to the given map with the given name.
Handler.addTo = function (map, name) {
map.addHandler(name, this);
return this;
};
+241
View File
@@ -0,0 +1,241 @@
/*
* @namespace Util
*
* Various utility functions, used by Leaflet internally.
*/
// @function extend(dest: Object, src?: Object): Object
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
export function extend(dest) {
var i, j, len, src;
for (j = 1, len = arguments.length; j < len; j++) {
src = arguments[j];
for (i in src) {
dest[i] = src[i];
}
}
return dest;
}
// @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
export var create = Object.create || (function () {
function F() {}
return function (proto) {
F.prototype = proto;
return new F();
};
})();
// @function bind(fn: Function, …): Function
// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
// Has a `L.bind()` shortcut.
export function bind(fn, obj) {
var slice = Array.prototype.slice;
if (fn.bind) {
return fn.bind.apply(fn, slice.call(arguments, 1));
}
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
};
}
// @property lastId: Number
// Last unique ID used by [`stamp()`](#util-stamp)
export var lastId = 0;
// @function stamp(obj: Object): Number
// Returns the unique ID of an object, assigning it one if it doesn't have it.
export function stamp(obj) {
if (!('_leaflet_id' in obj)) {
obj['_leaflet_id'] = ++lastId;
}
return obj._leaflet_id;
}
// @function throttle(fn: Function, time: Number, context: Object): Function
// Returns a function which executes function `fn` with the given scope `context`
// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
// `fn` will be called no more than one time per given amount of `time`. The arguments
// received by the bound function will be any arguments passed when binding the
// function, followed by any arguments passed when invoking the bound function.
// Has an `L.throttle` shortcut.
export function throttle(fn, time, context) {
var lock, args, wrapperFn, later;
later = function () {
// reset lock and call if queued
lock = false;
if (args) {
wrapperFn.apply(context, args);
args = false;
}
};
wrapperFn = function () {
if (lock) {
// called too soon, queue to call later
args = arguments;
} else {
// call and lock until later
fn.apply(context, arguments);
setTimeout(later, time);
lock = true;
}
};
return wrapperFn;
}
// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
// Returns the number `num` modulo `range` in such a way so it lies within
// `range[0]` and `range[1]`. The returned value will be always smaller than
// `range[1]` unless `includeMax` is set to `true`.
export function wrapNum(x, range, includeMax) {
var max = range[1],
min = range[0],
d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
}
// @function falseFn(): Function
// Returns a function which always returns `false`.
export function falseFn() { return false; }
// @function formatNum(num: Number, precision?: Number|false): Number
// Returns the number `num` rounded with specified `precision`.
// The default `precision` value is 6 decimal places.
// `false` can be passed to skip any processing (can be useful to avoid round-off errors).
export function formatNum(num, precision) {
if (precision === false) { return num; }
var pow = Math.pow(10, precision === undefined ? 6 : precision);
return Math.round(num * pow) / pow;
}
// @function trim(str: String): String
// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
export function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
export function splitWords(str) {
return trim(str).split(/\s+/);
}
// @function setOptions(obj: Object, options: Object): Object
// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
export function setOptions(obj, options) {
if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
obj.options = obj.options ? create(obj.options) : {};
}
for (var i in options) {
obj.options[i] = options[i];
}
return obj.options;
}
// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
// be appended at the end. If `uppercase` is `true`, the parameter names will
// be uppercased (e.g. `'?A=foo&B=bar'`)
export function getParamString(obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
}
var templateRe = /\{ *([\w_ -]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
// `('Hello foo, bar')`. You can also specify functions instead of strings for
// data values — they will be evaluated passing `data` as an argument.
export function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
}
// @function isArray(obj): Boolean
// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
export var isArray = Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
};
// @function indexOf(array: Array, el: Object): Number
// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
export function indexOf(array, el) {
for (var i = 0; i < array.length; i++) {
if (array[i] === el) { return i; }
}
return -1;
}
// @property emptyImageUrl: String
// Data URI string containing a base64-encoded empty GIF image.
// Used as a hack to free memory from unused images on WebKit-powered
// mobile devices (by setting image `src` to this string).
export var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
// inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
return window['webkit' + name] || window['moz' + name] || window['ms' + name];
}
var lastTime = 0;
// fallback for IE 7-8
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
export var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
export var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
// `context` if given. When `immediate` is set, `fn` is called immediately if
// the browser doesn't have native support for
// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
export function requestAnimFrame(fn, context, immediate) {
if (immediate && requestFn === timeoutDefer) {
fn.call(context);
} else {
return requestFn.call(window, bind(fn, context));
}
}
// @function cancelAnimFrame(id: Number): undefined
// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
export function cancelAnimFrame(id) {
if (id) {
cancelFn.call(window, id);
}
}
+15
View File
@@ -0,0 +1,15 @@
import Browser from './Browser';
export {Browser};
export {Class} from './Class';
import {Evented} from './Events';
import {Events} from './Events';
export {Evented};
export var Mixin = {Events: Events};
export {Handler} from './Handler';
import * as Util from './Util';
export {Util};
export {extend, bind, stamp, setOptions} from './Util';
+91
View File
@@ -0,0 +1,91 @@
import * as DomEvent from './DomEvent';
/*
* Extends the event handling code with double tap support for mobile browsers.
*
* Note: currently most browsers fire native dblclick, with only a few exceptions
* (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
*/
function makeDblclick(event) {
// in modern browsers `type` cannot be just overridden:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
var newEvent = {},
prop, i;
for (i in event) {
prop = event[i];
newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
}
event = newEvent;
newEvent.type = 'dblclick';
newEvent.detail = 2;
newEvent.isTrusted = false;
newEvent._simulated = true; // for debug purposes
return newEvent;
}
var delay = 200;
export function addDoubleTapListener(obj, handler) {
// Most browsers handle double tap natively
obj.addEventListener('dblclick', handler);
// On some platforms the browser doesn't fire native dblclicks for touch events.
// It seems that in all such cases `detail` property of `click` event is always `1`.
// So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
var last = 0,
detail;
function simDblclick(e) {
if (e.detail !== 1) {
detail = e.detail; // keep in sync to avoid false dblclick in some cases
return;
}
if (e.pointerType === 'mouse' ||
(e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
return;
}
// When clicking on an <input>, the browser generates a click on its
// <label> (and vice versa) triggering two clicks in quick succession.
// This ignores clicks on elements which are a label with a 'for'
// attribute (or children of such a label), but not children of
// a <input>.
var path = DomEvent.getPropagationPath(e);
if (path.some(function (el) {
return el instanceof HTMLLabelElement && el.attributes.for;
}) &&
!path.some(function (el) {
return (
el instanceof HTMLInputElement ||
el instanceof HTMLSelectElement
);
})
) {
return;
}
var now = Date.now();
if (now - last <= delay) {
detail++;
if (detail === 2) {
handler(makeDblclick(e));
}
} else {
detail = 1;
}
last = now;
}
obj.addEventListener('click', simDblclick);
return {
dblclick: handler,
simDblclick: simDblclick
};
}
export function removeDoubleTapListener(obj, handlers) {
obj.removeEventListener('dblclick', handlers.dblclick);
obj.removeEventListener('click', handlers.simDblclick);
}
+97
View File
@@ -0,0 +1,97 @@
import * as DomEvent from './DomEvent';
import Browser from '../core/Browser';
import {falseFn} from '../core/Util';
/*
* Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
*/
var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
var pEvent = {
touchstart : POINTER_DOWN,
touchmove : POINTER_MOVE,
touchend : POINTER_UP,
touchcancel : POINTER_CANCEL
};
var handle = {
touchstart : _onPointerStart,
touchmove : _handlePointer,
touchend : _handlePointer,
touchcancel : _handlePointer
};
var _pointers = {};
var _pointerDocListener = false;
// Provides a touch events wrapper for (ms)pointer events.
// ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
export function addPointerListener(obj, type, handler) {
if (type === 'touchstart') {
_addPointerDocListener();
}
if (!handle[type]) {
console.warn('wrong event specified:', type);
return falseFn;
}
handler = handle[type].bind(this, handler);
obj.addEventListener(pEvent[type], handler, false);
return handler;
}
export function removePointerListener(obj, type, handler) {
if (!pEvent[type]) {
console.warn('wrong event specified:', type);
return;
}
obj.removeEventListener(pEvent[type], handler, false);
}
function _globalPointerDown(e) {
_pointers[e.pointerId] = e;
}
function _globalPointerMove(e) {
if (_pointers[e.pointerId]) {
_pointers[e.pointerId] = e;
}
}
function _globalPointerUp(e) {
delete _pointers[e.pointerId];
}
function _addPointerDocListener() {
// need to keep track of what pointers and how many are active to provide e.touches emulation
if (!_pointerDocListener) {
// we listen document as any drags that end by moving the touch off the screen get fired there
document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
document.addEventListener(POINTER_UP, _globalPointerUp, true);
document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
_pointerDocListener = true;
}
}
function _handlePointer(handler, e) {
if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
e.touches = [];
for (var i in _pointers) {
e.touches.push(_pointers[i]);
}
e.changedTouches = [e];
handler(e);
}
function _onPointerStart(handler, e) {
// IE10 specific: MsTouch needs preventDefault. See #2000
if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
DomEvent.preventDefault(e);
}
_handlePointer(handler, e);
}
+315
View File
@@ -0,0 +1,315 @@
import {Point} from '../geometry/Point';
import * as Util from '../core/Util';
import Browser from '../core/Browser';
import {addPointerListener, removePointerListener} from './DomEvent.Pointer';
import {addDoubleTapListener, removeDoubleTapListener} from './DomEvent.DoubleTap';
import {getScale} from './DomUtil';
/*
* @namespace DomEvent
* Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
*/
// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Adds a listener function (`fn`) to a particular DOM event type of the
// element `el`. You can optionally specify the context of the listener
// (object the `this` keyword will point to). You can also pass several
// space-separated types (e.g. `'click dblclick'`).
// @alternative
// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
export function on(obj, types, fn, context) {
if (types && typeof types === 'object') {
for (var type in types) {
addOne(obj, type, types[type], fn);
}
} else {
types = Util.splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
addOne(obj, types[i], fn, context);
}
}
return this;
}
var eventsKey = '_leaflet_events';
// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
// Removes a previously added listener function.
// Note that if you passed a custom context to on, you must pass the same
// context to `off` in order to remove the listener.
// @alternative
// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
// @alternative
// @function off(el: HTMLElement, types: String): this
// Removes all previously added listeners of given types.
// @alternative
// @function off(el: HTMLElement): this
// Removes all previously added listeners from given HTMLElement
export function off(obj, types, fn, context) {
if (arguments.length === 1) {
batchRemove(obj);
delete obj[eventsKey];
} else if (types && typeof types === 'object') {
for (var type in types) {
removeOne(obj, type, types[type], fn);
}
} else {
types = Util.splitWords(types);
if (arguments.length === 2) {
batchRemove(obj, function (type) {
return Util.indexOf(types, type) !== -1;
});
} else {
for (var i = 0, len = types.length; i < len; i++) {
removeOne(obj, types[i], fn, context);
}
}
}
return this;
}
function batchRemove(obj, filterFn) {
for (var id in obj[eventsKey]) {
var type = id.split(/\d/)[0];
if (!filterFn || filterFn(type)) {
removeOne(obj, type, null, null, id);
}
}
}
var mouseSubst = {
mouseenter: 'mouseover',
mouseleave: 'mouseout',
wheel: !('onwheel' in window) && 'mousewheel'
};
function addOne(obj, type, fn, context) {
var id = type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : '');
if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
var handler = function (e) {
return fn.call(context || obj, e || window.event);
};
var originalHandler = handler;
if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
// Needs DomEvent.Pointer.js
handler = addPointerListener(obj, type, handler);
} else if (Browser.touch && (type === 'dblclick')) {
handler = addDoubleTapListener(obj, handler);
} else if ('addEventListener' in obj) {
if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
} else if (type === 'mouseenter' || type === 'mouseleave') {
handler = function (e) {
e = e || window.event;
if (isExternalTarget(obj, e)) {
originalHandler(e);
}
};
obj.addEventListener(mouseSubst[type], handler, false);
} else {
obj.addEventListener(type, originalHandler, false);
}
} else {
obj.attachEvent('on' + type, handler);
}
obj[eventsKey] = obj[eventsKey] || {};
obj[eventsKey][id] = handler;
}
function removeOne(obj, type, fn, context, id) {
id = id || type + Util.stamp(fn) + (context ? '_' + Util.stamp(context) : '');
var handler = obj[eventsKey] && obj[eventsKey][id];
if (!handler) { return this; }
if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
removePointerListener(obj, type, handler);
} else if (Browser.touch && (type === 'dblclick')) {
removeDoubleTapListener(obj, handler);
} else if ('removeEventListener' in obj) {
obj.removeEventListener(mouseSubst[type] || type, handler, false);
} else {
obj.detachEvent('on' + type, handler);
}
obj[eventsKey][id] = null;
}
// @function stopPropagation(ev: DOMEvent): this
// Stop the given event from propagation to parent elements. Used inside the listener functions:
// ```js
// L.DomEvent.on(div, 'click', function (ev) {
// L.DomEvent.stopPropagation(ev);
// });
// ```
export function stopPropagation(e) {
if (e.stopPropagation) {
e.stopPropagation();
} else if (e.originalEvent) { // In case of Leaflet event.
e.originalEvent._stopped = true;
} else {
e.cancelBubble = true;
}
return this;
}
// @function disableScrollPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
export function disableScrollPropagation(el) {
addOne(el, 'wheel', stopPropagation);
return this;
}
// @function disableClickPropagation(el: HTMLElement): this
// Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
// `'mousedown'` and `'touchstart'` events (plus browser variants).
export function disableClickPropagation(el) {
on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
el['_leaflet_disable_click'] = true;
return this;
}
// @function preventDefault(ev: DOMEvent): this
// Prevents the default action of the DOM Event `ev` from happening (such as
// following a link in the href of the a element, or doing a POST request
// with page reload when a `<form>` is submitted).
// Use it inside listener functions.
export function preventDefault(e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
return this;
}
// @function stop(ev: DOMEvent): this
// Does `stopPropagation` and `preventDefault` at the same time.
export function stop(e) {
preventDefault(e);
stopPropagation(e);
return this;
}
// @function getPropagationPath(ev: DOMEvent): Array
// Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
// Returns an array containing the `HTMLElement`s that the given DOM event
// should propagate to (if not stopped).
export function getPropagationPath(ev) {
if (ev.composedPath) {
return ev.composedPath();
}
var path = [];
var el = ev.target;
while (el) {
path.push(el);
el = el.parentNode;
}
return path;
}
// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
// Gets normalized mouse position from a DOM event relative to the
// `container` (border excluded) or to the whole page if not specified.
export function getMousePosition(e, container) {
if (!container) {
return new Point(e.clientX, e.clientY);
}
var scale = getScale(container),
offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
return new Point(
// offset.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.clientX - offset.left) / scale.x - container.clientLeft,
(e.clientY - offset.top) / scale.y - container.clientTop
);
}
// except , Safari and
// We need double the scroll pixels (see #7403 and #4538) for all Browsers
// except OSX (Mac) -> 3x, Chrome running on Linux 1x
var wheelPxFactor =
(Browser.linux && Browser.chrome) ? window.devicePixelRatio :
Browser.mac ? window.devicePixelRatio * 3 :
window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
// @function getWheelDelta(ev: DOMEvent): Number
// Gets normalized wheel delta from a wheel DOM event, in vertical
// pixels scrolled (negative if scrolling down).
// Events from pointing devices without precise scrolling are mapped to
// a best guess of 60 pixels.
export function getWheelDelta(e) {
return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
(e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
(e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
(e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
(e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
(e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
0;
}
// check if element really left/entered the event target (for mouseenter/mouseleave)
export function isExternalTarget(el, e) {
var related = e.relatedTarget;
if (!related) { return true; }
try {
while (related && (related !== el)) {
related = related.parentNode;
}
} catch (err) {
return false;
}
return (related !== el);
}
// @function addListener(…): this
// Alias to [`L.DomEvent.on`](#domevent-on)
export {on as addListener};
// @function removeListener(…): this
// Alias to [`L.DomEvent.off`](#domevent-off)
export {off as removeListener};
+349
View File
@@ -0,0 +1,349 @@
import * as DomEvent from './DomEvent';
import * as Util from '../core/Util';
import {Point} from '../geometry/Point';
import Browser from '../core/Browser';
/*
* @namespace DomUtil
*
* Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
* tree, used by Leaflet internally.
*
* Most functions expecting or returning a `HTMLElement` also work for
* SVG elements. The only difference is that classes refer to CSS classes
* in HTML and SVG classes in SVG.
*/
// @property TRANSFORM: String
// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
export var TRANSFORM = testProp(
['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
// webkitTransition comes first because some browser versions that drop vendor prefix don't do
// the same for the transitionend event, in particular the Android 4.1 stock browser
// @property TRANSITION: String
// Vendor-prefixed transition style name.
export var TRANSITION = testProp(
['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
// @property TRANSITION_END: String
// Vendor-prefixed transitionend event name.
export var TRANSITION_END =
TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
// @function get(id: String|HTMLElement): HTMLElement
// Returns an element given its DOM id, or returns the element itself
// if it was passed directly.
export function get(id) {
return typeof id === 'string' ? document.getElementById(id) : id;
}
// @function getStyle(el: HTMLElement, styleAttrib: String): String
// Returns the value for a certain style attribute on an element,
// including computed values or values set through CSS.
export function getStyle(el, style) {
var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
if ((!value || value === 'auto') && document.defaultView) {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return value === 'auto' ? null : value;
}
// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
export function create(tagName, className, container) {
var el = document.createElement(tagName);
el.className = className || '';
if (container) {
container.appendChild(el);
}
return el;
}
// @function remove(el: HTMLElement)
// Removes `el` from its parent element
export function remove(el) {
var parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
// @function empty(el: HTMLElement)
// Removes all of `el`'s children elements from `el`
export function empty(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}
// @function toFront(el: HTMLElement)
// Makes `el` the last child of its parent, so it renders in front of the other children.
export function toFront(el) {
var parent = el.parentNode;
if (parent && parent.lastChild !== el) {
parent.appendChild(el);
}
}
// @function toBack(el: HTMLElement)
// Makes `el` the first child of its parent, so it renders behind the other children.
export function toBack(el) {
var parent = el.parentNode;
if (parent && parent.firstChild !== el) {
parent.insertBefore(el, parent.firstChild);
}
}
// @function hasClass(el: HTMLElement, name: String): Boolean
// Returns `true` if the element's class attribute contains `name`.
export function hasClass(el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
var className = getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
}
// @function addClass(el: HTMLElement, name: String)
// Adds `name` to the element's class attribute.
export function addClass(el, name) {
if (el.classList !== undefined) {
var classes = Util.splitWords(name);
for (var i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!hasClass(el, name)) {
var className = getClass(el);
setClass(el, (className ? className + ' ' : '') + name);
}
}
// @function removeClass(el: HTMLElement, name: String)
// Removes `name` from the element's class attribute.
export function removeClass(el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
}
// @function setClass(el: HTMLElement, name: String)
// Sets the element's class.
export function setClass(el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
}
// @function getClass(el: HTMLElement): String
// Returns the element's class.
export function getClass(el) {
// Check if the element is an SVGElementInstance and use the correspondingElement instead
// (Required for linked SVG elements in IE11.)
if (el.correspondingElement) {
el = el.correspondingElement;
}
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
}
// @function setOpacity(el: HTMLElement, opacity: Number)
// Set the opacity of an element (including old IE support).
// `opacity` must be a number from `0` to `1`.
export function setOpacity(el, value) {
if ('opacity' in el.style) {
el.style.opacity = value;
} else if ('filter' in el.style) {
_setOpacityIE(el, value);
}
}
function _setOpacityIE(el, value) {
var filter = false,
filterName = 'DXImageTransform.Microsoft.Alpha';
// filters collection throws an error if we try to retrieve a filter that doesn't exist
try {
filter = el.filters.item(filterName);
} catch (e) {
// don't set opacity to 1 if we haven't already set an opacity,
// it isn't needed and breaks transparent pngs.
if (value === 1) { return; }
}
value = Math.round(value * 100);
if (filter) {
filter.Enabled = (value !== 100);
filter.Opacity = value;
} else {
el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
}
}
// @function testProp(props: String[]): String|false
// Goes through the array of style names and returns the first name
// that is a valid style name for an element. If no such name is found,
// it returns false. Useful for vendor-prefixed styles like `transform`.
export function testProp(props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
}
// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
// and optionally scaled by `scale`. Does not have an effect if the
// browser doesn't support 3D CSS transforms.
export function setTransform(el, offset, scale) {
var pos = offset || new Point(0, 0);
el.style[TRANSFORM] =
(Browser.ie3d ?
'translate(' + pos.x + 'px,' + pos.y + 'px)' :
'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
(scale ? ' scale(' + scale + ')' : '');
}
// @function setPosition(el: HTMLElement, position: Point)
// Sets the position of `el` to coordinates specified by `position`,
// using CSS translate or top/left positioning depending on the browser
// (used by Leaflet internally to position its layers).
export function setPosition(el, point) {
/*eslint-disable */
el._leaflet_pos = point;
/* eslint-enable */
if (Browser.any3d) {
setTransform(el, point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
}
// @function getPosition(el: HTMLElement): Point
// Returns the coordinates of an element previously positioned with setPosition.
export function getPosition(el) {
// this method is only used for elements previously positioned using setPosition,
// so it's safe to cache the position for performance
return el._leaflet_pos || new Point(0, 0);
}
// @function disableTextSelection()
// Prevents the user from generating `selectstart` DOM events, usually generated
// when the user drags the mouse through a page with text. Used internally
// by Leaflet to override the behaviour of any click-and-drag interaction on
// the map. Affects drag interactions on the whole document.
// @function enableTextSelection()
// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
export var disableTextSelection;
export var enableTextSelection;
var _userSelect;
if ('onselectstart' in document) {
disableTextSelection = function () {
DomEvent.on(window, 'selectstart', DomEvent.preventDefault);
};
enableTextSelection = function () {
DomEvent.off(window, 'selectstart', DomEvent.preventDefault);
};
} else {
var userSelectProperty = testProp(
['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
disableTextSelection = function () {
if (userSelectProperty) {
var style = document.documentElement.style;
_userSelect = style[userSelectProperty];
style[userSelectProperty] = 'none';
}
};
enableTextSelection = function () {
if (userSelectProperty) {
document.documentElement.style[userSelectProperty] = _userSelect;
_userSelect = undefined;
}
};
}
// @function disableImageDrag()
// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
// for `dragstart` DOM events, usually generated when the user drags an image.
export function disableImageDrag() {
DomEvent.on(window, 'dragstart', DomEvent.preventDefault);
}
// @function enableImageDrag()
// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
export function enableImageDrag() {
DomEvent.off(window, 'dragstart', DomEvent.preventDefault);
}
var _outlineElement, _outlineStyle;
// @function preventOutline(el: HTMLElement)
// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
// of the element `el` invisible. Used internally by Leaflet to prevent
// focusable elements from displaying an outline when the user performs a
// drag interaction on them.
export function preventOutline(element) {
while (element.tabIndex === -1) {
element = element.parentNode;
}
if (!element.style) { return; }
restoreOutline();
_outlineElement = element;
_outlineStyle = element.style.outlineStyle;
element.style.outlineStyle = 'none';
DomEvent.on(window, 'keydown', restoreOutline);
}
// @function restoreOutline()
// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
export function restoreOutline() {
if (!_outlineElement) { return; }
_outlineElement.style.outlineStyle = _outlineStyle;
_outlineElement = undefined;
_outlineStyle = undefined;
DomEvent.off(window, 'keydown', restoreOutline);
}
// @function getSizedParentNode(el: HTMLElement): HTMLElement
// Finds the closest parent node which size (width and height) is not null.
export function getSizedParentNode(element) {
do {
element = element.parentNode;
} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
return element;
}
// @function getScale(el: HTMLElement): Object
// Computes the CSS scale currently applied on the element.
// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
export function getScale(element) {
var rect = element.getBoundingClientRect(); // Read-only in old browsers.
return {
x: rect.width / element.offsetWidth || 1,
y: rect.height / element.offsetHeight || 1,
boundingClientRect: rect
};
}
+220
View File
@@ -0,0 +1,220 @@
import {Evented} from '../core/Events';
import Browser from '../core/Browser';
import * as DomEvent from './DomEvent';
import * as DomUtil from './DomUtil';
import * as Util from '../core/Util';
import {Point} from '../geometry/Point';
/*
* @class Draggable
* @aka L.Draggable
* @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();
* ```
*/
var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
export var Draggable = Evented.extend({
options: {
// @section
// @aka Draggable 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
},
// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
initialize: function (element, dragStartTarget, preventOutline, options) {
Util.setOptions(this, options);
this._element = element;
this._dragStartTarget = dragStartTarget || element;
this._preventOutline = preventOutline;
},
// @method enable()
// Enables the dragging ability
enable: function () {
if (this._enabled) { return; }
DomEvent.on(this._dragStartTarget, START, this._onDown, this);
this._enabled = true;
},
// @method disable()
// Disables the dragging ability
disable: function () {
if (!this._enabled) { return; }
// If we're currently dragging this draggable,
// disabling it counts as first ending the drag.
if (Draggable._dragging === this) {
this.finishDrag(true);
}
DomEvent.off(this._dragStartTarget, START, this._onDown, this);
this._enabled = false;
this._moved = false;
},
_onDown: function (e) {
// Ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (!this._enabled) { return; }
this._moved = false;
if (DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
if (e.touches && e.touches.length !== 1) {
// Finish dragging to avoid conflict with touchZoom
if (Draggable._dragging === this) {
this.finishDrag();
}
return;
}
if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
Draggable._dragging = this; // Prevent dragging multiple objects at once.
if (this._preventOutline) {
DomUtil.preventOutline(this._element);
}
DomUtil.disableImageDrag();
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,
sizedParent = DomUtil.getSizedParentNode(this._element);
this._startPoint = new Point(first.clientX, first.clientY);
this._startPos = DomUtil.getPosition(this._element);
// Cache the scale, so that we can continuously compensate for it during drag (_onMove).
this._parentScale = DomUtil.getScale(sizedParent);
var mouseevent = e.type === 'mousedown';
DomEvent.on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
DomEvent.on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
},
_onMove: function (e) {
// Ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (!this._enabled) { return; }
if (e.touches && e.touches.length > 1) {
this._moved = true;
return;
}
var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
if (!offset.x && !offset.y) { return; }
if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
// We assume that the parent container's position, border and scale do not change for the duration of the drag.
// Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
// and we can use the cached value for the scale.
offset.x /= this._parentScale.x;
offset.y /= this._parentScale.y;
DomEvent.preventDefault(e);
if (!this._moved) {
// @event dragstart: Event
// Fired when a drag starts
this.fire('dragstart');
this._moved = true;
DomUtil.addClass(document.body, 'leaflet-dragging');
this._lastTarget = e.target || e.srcElement;
// IE and Edge do not give the <use> element, so fetch it
// if necessary
if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
this._lastTarget = this._lastTarget.correspondingUseElement;
}
DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
}
this._newPos = this._startPos.add(offset);
this._moving = true;
this._lastEvent = e;
this._updatePosition();
},
_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);
DomUtil.setPosition(this._element, this._newPos);
// @event drag: Event
// Fired continuously during dragging.
this.fire('drag', e);
},
_onUp: function () {
// Ignore the event if disabled; this happens in IE11
// under some circumstances, see #3666.
if (!this._enabled) { return; }
this.finishDrag();
},
finishDrag: function (noInertia) {
DomUtil.removeClass(document.body, 'leaflet-dragging');
if (this._lastTarget) {
DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
this._lastTarget = null;
}
DomEvent.off(document, 'mousemove touchmove', this._onMove, this);
DomEvent.off(document, 'mouseup touchend touchcancel', this._onUp, this);
DomUtil.enableImageDrag();
DomUtil.enableTextSelection();
var fireDragend = this._moved && this._moving;
this._moving = false;
Draggable._dragging = false;
if (fireDragend) {
// @event dragend: DragEndEvent
// Fired when the drag ends.
this.fire('dragend', {
noInertia: noInertia,
distance: this._newPos.distanceTo(this._startPos)
});
}
}
});
+113
View File
@@ -0,0 +1,113 @@
import * as Util from '../core/Util';
import {Evented} from '../core/Events';
import * as DomUtil from '../dom/DomUtil';
/*
* @class PosAnimation
* @aka L.PosAnimation
* @inherits Evented
* Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
*
* @example
* ```js
* var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
*
* myPositionMarker.on("click", function() {
* var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
* pos.y -= 25;
* var fx = new L.PosAnimation();
*
* fx.once('end',function() {
* pos.y += 25;
* fx.run(myPositionMarker._icon, pos, 0.8);
* });
*
* fx.run(myPositionMarker._icon, pos, 0.3);
* });
*
* ```
*
* @constructor L.PosAnimation()
* Creates a `PosAnimation` object.
*
*/
export var PosAnimation = Evented.extend({
// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
// Run an animation of a given element to a new position, optionally setting
// duration in seconds (`0.25` by default) and easing linearity factor (3rd
// argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
// `0.5` by default).
run: function (el, newPos, duration, easeLinearity) {
this.stop();
this._el = el;
this._inProgress = true;
this._duration = duration || 0.25;
this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
this._startPos = DomUtil.getPosition(el);
this._offset = newPos.subtract(this._startPos);
this._startTime = +new Date();
// @event start: Event
// Fired when the animation starts
this.fire('start');
this._animate();
},
// @method stop()
// Stops the animation (if currently running).
stop: function () {
if (!this._inProgress) { return; }
this._step(true);
this._complete();
},
_animate: function () {
// animation loop
this._animId = Util.requestAnimFrame(this._animate, this);
this._step();
},
_step: function (round) {
var elapsed = (+new Date()) - this._startTime,
duration = this._duration * 1000;
if (elapsed < duration) {
this._runFrame(this._easeOut(elapsed / duration), round);
} else {
this._runFrame(1);
this._complete();
}
},
_runFrame: function (progress, round) {
var pos = this._startPos.add(this._offset.multiplyBy(progress));
if (round) {
pos._round();
}
DomUtil.setPosition(this._el, pos);
// @event step: Event
// Fired continuously during the animation.
this.fire('step');
},
_complete: function () {
Util.cancelAnimFrame(this._animId);
this._inProgress = false;
// @event end: Event
// Fired when the animation ends.
this.fire('end');
},
_easeOut: function (t) {
return 1 - Math.pow(1 - t, this._easeOutPower);
}
});
+9
View File
@@ -0,0 +1,9 @@
export {PosAnimation} from './PosAnimation';
import * as DomEvent from './DomEvent';
export {DomEvent};
import * as DomUtil from './DomUtil';
export {DomUtil};
export {Draggable} from './Draggable';
+137
View File
@@ -0,0 +1,137 @@
import * as Util from '../core/Util';
import {Earth} from './crs/CRS.Earth';
import {toLatLngBounds} from './LatLngBounds';
/* @class LatLng
* @aka L.LatLng
*
* Represents a geographical point with a certain latitude and longitude.
*
* @example
*
* ```
* 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));
* ```
*
* Note that `LatLng` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
export function LatLng(lat, lng, alt) {
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
// @property lat: Number
// Latitude in degrees
this.lat = +lat;
// @property lng: Number
// Longitude in degrees
this.lng = +lng;
// @property alt: Number
// Altitude in meters (optional)
if (alt !== undefined) {
this.alt = +alt;
}
}
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function (obj, maxMargin) {
if (!obj) { return false; }
obj = toLatLng(obj);
var margin = Math.max(
Math.abs(this.lat - obj.lat),
Math.abs(this.lng - obj.lng));
return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
},
// @method toString(): String
// Returns a string representation of the point (for debugging purposes).
toString: function (precision) {
return 'LatLng(' +
Util.formatNum(this.lat, precision) + ', ' +
Util.formatNum(this.lng, precision) + ')';
},
// @method distanceTo(otherLatLng: LatLng): Number
// Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
distanceTo: function (other) {
return Earth.distance(this, toLatLng(other));
},
// @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap: function () {
return Earth.wrapLatLng(this);
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
toBounds: function (sizeInMeters) {
var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
return toLatLngBounds(
[this.lat - latAccuracy, this.lng - lngAccuracy],
[this.lat + latAccuracy, this.lng + lngAccuracy]);
},
clone: function () {
return new LatLng(this.lat, this.lng, this.alt);
}
};
// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
// @alternative
// @factory L.latLng(coords: Array): LatLng
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
// @alternative
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
export function toLatLng(a, b, c) {
if (a instanceof LatLng) {
return a;
}
if (Util.isArray(a) && typeof a[0] !== 'object') {
if (a.length === 3) {
return new LatLng(a[0], a[1], a[2]);
}
if (a.length === 2) {
return new LatLng(a[0], a[1]);
}
return null;
}
if (a === undefined || a === null) {
return a;
}
if (typeof a === 'object' && 'lat' in a) {
return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
}
if (b === undefined) {
return null;
}
return new LatLng(a, b, c);
}
+251
View File
@@ -0,0 +1,251 @@
import {LatLng, toLatLng} from './LatLng';
/*
* @class LatLngBounds
* @aka L.LatLngBounds
*
* Represents a rectangular geographical area on a map.
*
* @example
*
* ```js
* var corner1 = L.latLng(40.712, -74.227),
* corner2 = L.latLng(40.774, -74.125),
* bounds = L.latLngBounds(corner1, corner2);
* ```
*
* 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:
*
* ```js
* map.fitBounds([
* [40.712, -74.227],
* [40.774, -74.125]
* ]);
* ```
*
* Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
*
* Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
export function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
if (!corner1) { return; }
var latlngs = corner2 ? [corner1, corner2] : corner1;
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
}
LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point
// @alternative
// @method extend(otherBounds: LatLngBounds): this
// Extend the bounds to contain the given bounds
extend: function (obj) {
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) { return this; }
} else {
return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
}
if (!sw && !ne) {
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
ne.lat = Math.max(ne2.lat, ne.lat);
ne.lng = Math.max(ne2.lng, ne.lng);
}
return this;
},
// @method pad(bufferRatio: Number): LatLngBounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad: function (bufferRatio) {
var sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new LatLngBounds(
new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
// @method getCenter(): LatLng
// Returns the center point of the bounds.
getCenter: function () {
return new LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
// @method getSouthWest(): LatLng
// Returns the south-west point of the bounds.
getSouthWest: function () {
return this._southWest;
},
// @method getNorthEast(): LatLng
// Returns the north-east point of the bounds.
getNorthEast: function () {
return this._northEast;
},
// @method getNorthWest(): LatLng
// Returns the north-west point of the bounds.
getNorthWest: function () {
return new LatLng(this.getNorth(), this.getWest());
},
// @method getSouthEast(): LatLng
// Returns the south-east point of the bounds.
getSouthEast: function () {
return new LatLng(this.getSouth(), this.getEast());
},
// @method getWest(): Number
// Returns the west longitude of the bounds
getWest: function () {
return this._southWest.lng;
},
// @method getSouth(): Number
// Returns the south latitude of the bounds
getSouth: function () {
return this._southWest.lat;
},
// @method getEast(): Number
// Returns the east longitude of the bounds
getEast: function () {
return this._northEast.lng;
},
// @method getNorth(): Number
// Returns the north latitude of the bounds
getNorth: function () {
return this._northEast.lat;
},
// @method contains(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = toLatLng(obj);
} else {
obj = toLatLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
},
// @method intersects(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects: function (bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
return latIntersects && lngIntersects;
},
// @method overlaps(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps: function (bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
return latOverlaps && lngOverlaps;
},
// @method toBBoxString(): String
// Returns 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.
toBBoxString: function () {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
equals: function (bounds, maxMargin) {
if (!bounds) { return false; }
bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
this._northEast.equals(bounds.getNorthEast(), maxMargin);
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid: function () {
return !!(this._southWest && this._northEast);
}
};
// TODO International date line?
// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
export function toLatLngBounds(a, b) {
if (a instanceof LatLngBounds) {
return a;
}
return new LatLngBounds(a, b);
}
+20
View File
@@ -0,0 +1,20 @@
import {Earth} from './CRS.Earth';
import {Mercator} from '../projection/Projection.Mercator';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.EPSG3395
*
* Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
*/
export var EPSG3395 = Util.extend({}, Earth, {
code: 'EPSG:3395',
projection: Mercator,
transformation: (function () {
var scale = 0.5 / (Math.PI * Mercator.R);
return toTransformation(scale, 0.5, -scale, 0.5);
}())
});
+27
View File
@@ -0,0 +1,27 @@
import {Earth} from './CRS.Earth';
import {SphericalMercator} from '../projection/Projection.SphericalMercator';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.EPSG3857
*
* The 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.
*/
export var EPSG3857 = Util.extend({}, Earth, {
code: 'EPSG:3857',
projection: SphericalMercator,
transformation: (function () {
var scale = 0.5 / (Math.PI * SphericalMercator.R);
return toTransformation(scale, 0.5, -scale, 0.5);
}())
});
export var EPSG900913 = Util.extend({}, EPSG3857, {
code: 'EPSG:900913'
});
+23
View File
@@ -0,0 +1,23 @@
import {Earth} from './CRS.Earth';
import {LonLat} from '../projection/Projection.LonLat';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.EPSG4326
*
* A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
*
* Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
* which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
* with this CRS, ensure that there are two 256x256 pixel tiles covering the
* whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
* or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
*/
export var EPSG4326 = Util.extend({}, Earth, {
code: 'EPSG:4326',
projection: LonLat,
transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
});
+33
View File
@@ -0,0 +1,33 @@
import {CRS} from './CRS';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Earth
*
* Serves as the base for CRS that are global such that they cover the earth.
* Can only be used as the base for other CRS and cannot be used directly,
* since it does not have a `code`, `projection` or `transformation`. `distance()` returns
* meters.
*/
export var Earth = Util.extend({}, CRS, {
wrapLng: [-180, 180],
// Mean Earth Radius, as recommended for use by
// the International Union of Geodesy and Geophysics,
// see https://rosettacode.org/wiki/Haversine_formula
R: 6371000,
// distance between two geographical points using spherical law of cosines approximation
distance: function (latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1.lat * rad,
lat2 = latlng2.lat * rad,
sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return this.R * c;
}
});
+36
View File
@@ -0,0 +1,36 @@
import {CRS} from './CRS';
import {LonLat} from '../projection/Projection.LonLat';
import {toTransformation} from '../../geometry/Transformation';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Simple
*
* A 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). `distance()` returns
* simple euclidean distance.
*/
export var Simple = Util.extend({}, CRS, {
projection: LonLat,
transformation: toTransformation(1, 0, -1, 0),
scale: function (zoom) {
return Math.pow(2, zoom);
},
zoom: function (scale) {
return Math.log(scale) / Math.LN2;
},
distance: function (latlng1, latlng2) {
var dx = latlng2.lng - latlng1.lng,
dy = latlng2.lat - latlng1.lat;
return Math.sqrt(dx * dx + dy * dy);
},
infinite: true
});
+139
View File
@@ -0,0 +1,139 @@
import {Bounds} from '../../geometry/Bounds';
import {LatLng} from '../LatLng';
import {LatLngBounds} from '../LatLngBounds';
import * as Util from '../../core/Util';
/*
* @namespace CRS
* @crs L.CRS.Base
* Object that defines coordinate reference systems for projecting
* geographical points into pixel (screen) coordinates and back (and to
* coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
* [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
*
* Leaflet defines the most usual CRSs by default. If you want to use a
* CRS not defined by default, take a look at the
* [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
*
* Note that the CRS instances do not inherit from Leaflet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
export var CRS = {
// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
// Projects geographical coordinates into pixel coordinates for a given zoom.
latLngToPoint: function (latlng, zoom) {
var projectedPoint = this.projection.project(latlng),
scale = this.scale(zoom);
return this.transformation._transform(projectedPoint, scale);
},
// @method pointToLatLng(point: Point, zoom: Number): LatLng
// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
// zoom into geographical coordinates.
pointToLatLng: function (point, zoom) {
var scale = this.scale(zoom),
untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint);
},
// @method project(latlng: LatLng): Point
// Projects geographical coordinates into coordinates in units accepted for
// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
project: function (latlng) {
return this.projection.project(latlng);
},
// @method unproject(point: Point): LatLng
// Given a projected coordinate returns the corresponding LatLng.
// The inverse of `project`.
unproject: function (point) {
return this.projection.unproject(point);
},
// @method scale(zoom: Number): Number
// Returns 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.
scale: function (zoom) {
return 256 * Math.pow(2, zoom);
},
// @method zoom(scale: Number): Number
// Inverse of `scale()`, returns the zoom level corresponding to a scale
// factor of `scale`.
zoom: function (scale) {
return Math.log(scale / 256) / Math.LN2;
},
// @method getProjectedBounds(zoom: Number): Bounds
// Returns the projection's bounds scaled and transformed for the provided `zoom`.
getProjectedBounds: function (zoom) {
if (this.infinite) { return null; }
var b = this.projection.bounds,
s = this.scale(zoom),
min = this.transformation.transform(b.min, s),
max = this.transformation.transform(b.max, s);
return new Bounds(min, max);
},
// @method distance(latlng1: LatLng, latlng2: LatLng): Number
// Returns the distance between two geographical coordinates.
// @property code: String
// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
//
// @property wrapLng: Number[]
// An array of two numbers defining whether the longitude (horizontal) coordinate
// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
//
// @property wrapLat: Number[]
// Like `wrapLng`, but for the latitude (vertical) axis.
// wrapLng: [min, max],
// wrapLat: [min, max],
// @property infinite: Boolean
// If true, the coordinate space will be unbounded (infinite in both axes)
infinite: false,
// @method wrapLatLng(latlng: LatLng): LatLng
// Returns a `LatLng` where lat and lng has been wrapped according to the
// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
wrapLatLng: function (latlng) {
var lng = this.wrapLng ? Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
lat = this.wrapLat ? Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
alt = latlng.alt;
return new LatLng(lat, lng, alt);
},
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
// Returns a `LatLngBounds` with the same size as the given one, ensuring
// that its center is within the CRS's bounds.
// Only accepts actual `L.LatLngBounds` instances, not arrays.
wrapLatLngBounds: function (bounds) {
var center = bounds.getCenter(),
newCenter = this.wrapLatLng(center),
latShift = center.lat - newCenter.lat,
lngShift = center.lng - newCenter.lng;
if (latShift === 0 && lngShift === 0) {
return bounds;
}
var sw = bounds.getSouthWest(),
ne = bounds.getNorthEast(),
newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
return new LatLngBounds(newSw, newNe);
}
};
+15
View File
@@ -0,0 +1,15 @@
import {CRS} from './CRS';
import {Earth} from './CRS.Earth';
import {EPSG3395} from './CRS.EPSG3395';
import {EPSG3857, EPSG900913} from './CRS.EPSG3857';
import {EPSG4326} from './CRS.EPSG4326';
import {Simple} from './CRS.Simple';
CRS.Earth = Earth;
CRS.EPSG3395 = EPSG3395;
CRS.EPSG3857 = EPSG3857;
CRS.EPSG900913 = EPSG900913;
CRS.EPSG4326 = EPSG4326;
CRS.Simple = Simple;
export {CRS};
+7
View File
@@ -0,0 +1,7 @@
export {LatLng, toLatLng as latLng} from './LatLng';
export {LatLngBounds, toLatLngBounds as latLngBounds} from './LatLngBounds';
import * as Projection from './projection/index';
export {Projection};
export * from './crs/index';
@@ -0,0 +1,28 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @section
* Leaflet comes with a set of already defined Projections out of the box:
*
* @projection L.Projection.LonLat
*
* Equirectangular, 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:4326` and `Simple` CRS.
*/
export var LonLat = {
project: function (latlng) {
return new Point(latlng.lng, latlng.lat);
},
unproject: function (point) {
return new LatLng(point.y, point.x);
},
bounds: new Bounds([-180, -90], [180, 90])
};
@@ -0,0 +1,49 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @projection L.Projection.Mercator
*
* Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
*/
export var Mercator = {
R: 6378137,
R_MINOR: 6356752.314245179,
bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
project: function (latlng) {
var d = Math.PI / 180,
r = this.R,
y = latlng.lat * d,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
con = e * Math.sin(y);
var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
y = -r * Math.log(Math.max(ts, 1E-10));
return new Point(latlng.lng * d * r, y);
},
unproject: function (point) {
var d = 180 / Math.PI,
r = this.R,
tmp = this.R_MINOR / r,
e = Math.sqrt(1 - tmp * tmp),
ts = Math.exp(-point.y / r),
phi = Math.PI / 2 - 2 * Math.atan(ts);
for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
con = e * Math.sin(phi);
con = Math.pow((1 - con) / (1 + con), e / 2);
dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
phi += dphi;
}
return new LatLng(phi * d, point.x * d / r);
}
};
@@ -0,0 +1,44 @@
import {LatLng} from '../LatLng';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @namespace Projection
* @projection L.Projection.SphericalMercator
*
* Spherical 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.
*/
var earthRadius = 6378137;
export var SphericalMercator = {
R: earthRadius,
MAX_LATITUDE: 85.0511287798,
project: function (latlng) {
var d = Math.PI / 180,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
sin = Math.sin(lat * d);
return new Point(
this.R * latlng.lng * d,
this.R * Math.log((1 + sin) / (1 - sin)) / 2);
},
unproject: function (point) {
var d = 180 / Math.PI;
return new LatLng(
(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
point.x * d / this.R);
},
bounds: (function () {
var d = earthRadius * Math.PI;
return new Bounds([-d, -d], [d, d]);
})()
};
+26
View File
@@ -0,0 +1,26 @@
/*
* @class Projection
* An object with methods for projecting geographical coordinates of the world onto
* a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
* @property bounds: Bounds
* The bounds (specified in CRS units) where the projection is valid
* @method project(latlng: LatLng): Point
* Projects geographical coordinates into a 2D point.
* Only accepts actual `L.LatLng` instances, not arrays.
* @method unproject(point: Point): LatLng
* The inverse of `project`. Projects a 2D point into a geographical location.
* Only accepts actual `L.Point` instances, not arrays.
* Note that the projection instances do not inherit from Leaflet's `Class` object,
* and can't be instantiated. Also, new classes can't inherit from them,
* and methods can't be added to them with the `include` function.
*/
export {LonLat} from './Projection.LonLat';
export {Mercator} from './Projection.Mercator';
export {SphericalMercator} from './Projection.SphericalMercator';
+219
View File
@@ -0,0 +1,219 @@
import {Point, toPoint} from './Point';
/*
* @class Bounds
* @aka L.Bounds
*
* Represents a rectangular area in pixel coordinates.
*
* @example
*
* ```js
* 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:
*
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
*
* Note that `Bounds` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
export function Bounds(a, b) {
if (!a) { return; }
var points = b ? [a, b] : a;
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
}
Bounds.prototype = {
// @method extend(point: Point): this
// Extends the bounds to contain the given point.
// @alternative
// @method extend(otherBounds: Bounds): this
// Extend the bounds to contain the given bounds
extend: function (obj) {
var min2, max2;
if (!obj) { return this; }
if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
min2 = max2 = toPoint(obj);
} else {
obj = toBounds(obj);
min2 = obj.min;
max2 = obj.max;
if (!min2 || !max2) { return this; }
}
// @property min: Point
// The top left corner of the rectangle.
// @property max: Point
// The bottom right corner of the rectangle.
if (!this.min && !this.max) {
this.min = min2.clone();
this.max = max2.clone();
} else {
this.min.x = Math.min(min2.x, this.min.x);
this.max.x = Math.max(max2.x, this.max.x);
this.min.y = Math.min(min2.y, this.min.y);
this.max.y = Math.max(max2.y, this.max.y);
}
return this;
},
// @method getCenter(round?: Boolean): Point
// Returns the center point of the bounds.
getCenter: function (round) {
return toPoint(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
// @method getBottomLeft(): Point
// Returns the bottom-left point of the bounds.
getBottomLeft: function () {
return toPoint(this.min.x, this.max.y);
},
// @method getTopRight(): Point
// Returns the top-right point of the bounds.
getTopRight: function () { // -> Point
return toPoint(this.max.x, this.min.y);
},
// @method getTopLeft(): Point
// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
getTopLeft: function () {
return this.min; // left, top
},
// @method getBottomRight(): Point
// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
getBottomRight: function () {
return this.max; // right, bottom
},
// @method getSize(): Point
// Returns the size of the given bounds
getSize: function () {
return this.max.subtract(this.min);
},
// @method contains(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains(point: Point): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) {
var min, max;
if (typeof obj[0] === 'number' || obj instanceof Point) {
obj = toPoint(obj);
} else {
obj = toBounds(obj);
}
if (obj instanceof Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
// @method intersects(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds
// intersect if they have at least one point in common.
intersects: function (bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
},
// @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds
// overlap if their intersection is an area.
overlaps: function (bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xOverlaps = (max2.x > min.x) && (min2.x < max.x),
yOverlaps = (max2.y > min.y) && (min2.y < max.y);
return xOverlaps && yOverlaps;
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid: function () {
return !!(this.min && this.max);
},
// @method pad(bufferRatio: Number): Bounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad: function (bufferRatio) {
var min = this.min,
max = this.max,
heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
return toBounds(
toPoint(min.x - heightBuffer, min.y - widthBuffer),
toPoint(max.x + heightBuffer, max.y + widthBuffer));
},
// @method equals(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle is equivalent to the given bounds.
equals: function (bounds) {
if (!bounds) { return false; }
bounds = toBounds(bounds);
return this.min.equals(bounds.getTopLeft()) &&
this.max.equals(bounds.getBottomRight());
},
};
// @factory L.bounds(corner1: Point, corner2: Point)
// Creates a Bounds object from two corners coordinate pairs.
// @alternative
// @factory L.bounds(points: Point[])
// Creates a Bounds object from the given array of points.
export function toBounds(a, b) {
if (!a || a instanceof Bounds) {
return a;
}
return new Bounds(a, b);
}
+306
View File
@@ -0,0 +1,306 @@
import {Point, toPoint} from './Point';
import * as Util from '../core/Util';
import {toLatLng} from '../geo/LatLng';
import {centroid} from './PolyUtil';
import {toLatLngBounds} from '../geo/LatLngBounds';
/*
* @namespace LineUtil
*
* Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
*/
// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
// Improves rendering performance dramatically by lessening the number of points to draw.
// @function simplify(points: Point[], tolerance: Number): Point[]
// Dramatically reduces the number of points in a polyline while retaining
// its shape and returns a new array of simplified points, using the
// [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
// Used for a huge performance boost when processing/displaying Leaflet polylines for
// each zoom level and also reducing visual noise. tolerance affects the amount of
// simplification (lesser value means higher quality but slower and with more points).
// Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
export function simplify(points, tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = _reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = _simplifyDP(points, sqTolerance);
return points;
}
// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
// Returns the distance between point `p` and segment `p1` to `p2`.
export function pointToSegmentDistance(p, p1, p2) {
return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
}
// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
// Returns the closest point from a point `p` on a segment `p1` to `p2`.
export function closestPointOnSegment(p, p1, p2) {
return _sqClosestPointOnSegment(p, p1, p2);
}
// Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
function _simplifyDP(points, sqTolerance) {
var len = points.length,
ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
function _simplifyDPStep(points, markers, sqTolerance, first, last) {
var maxSqDist = 0,
index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
_simplifyDPStep(points, markers, sqTolerance, first, index);
_simplifyDPStep(points, markers, sqTolerance, index, last);
}
}
// reduce points that are too close to each other to a single point
function _reducePoints(points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (_sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
}
var _lastCode;
// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
// Clips the segment a to b by rectangular bounds with the
// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
// (modifying the segment points directly!). Used by Leaflet to only show polyline
// points that are on the screen or near, increasing performance.
export function clipSegment(a, b, bounds, useLastCode, round) {
var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
codeB = _getBitCode(b, bounds),
codeOut, p, newCode;
// save 2nd code to avoid calculating it on the next segment
_lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
}
// if a,b is outside the clip window (trivial reject)
if (codeA & codeB) {
return false;
}
// other cases
codeOut = codeA || codeB;
p = _getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = _getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
export function _getEdgeIntersection(a, b, code, bounds, round) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max,
x, y;
if (code & 8) { // top
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) { // bottom
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) { // right
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) { // left
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new Point(x, y, round);
}
export function _getBitCode(p, bounds) {
var code = 0;
if (p.x < bounds.min.x) { // left
code |= 1;
} else if (p.x > bounds.max.x) { // right
code |= 2;
}
if (p.y < bounds.min.y) { // bottom
code |= 4;
} else if (p.y > bounds.max.y) { // top
code |= 8;
}
return code;
}
// square distance (to avoid unnecessary Math.sqrt calls)
function _sqDist(p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
}
// return closest point on segment or distance to that point
export function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new Point(x, y);
}
// @function isFlat(latlngs: LatLng[]): Boolean
// Returns true if `latlngs` is a flat array, false is nested.
export function isFlat(latlngs) {
return !Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
}
export function _flat(latlngs) {
console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
return isFlat(latlngs);
}
/* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
* Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
*/
export function polylineCenter(latlngs, crs) {
var i, halfDist, segDist, dist, p1, p2, ratio, center;
if (!latlngs || latlngs.length === 0) {
throw new Error('latlngs not passed');
}
if (!isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
var centroidLatLng = toLatLng([0, 0]);
var bounds = toLatLngBounds(latlngs);
var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
// tests showed that below 1700 rounding errors are happening
if (areaBounds < 1700) {
// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
centroidLatLng = centroid(latlngs);
}
var len = latlngs.length;
var points = [];
for (i = 0; i < len; i++) {
var latlng = toLatLng(latlngs[i]);
points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
}
for (i = 0, halfDist = 0; i < len - 1; i++) {
halfDist += points[i].distanceTo(points[i + 1]) / 2;
}
// The line is so small in the current view that all points are on the same pixel.
if (halfDist === 0) {
center = points[0];
} else {
for (i = 0, dist = 0; i < len - 1; i++) {
p1 = points[i];
p2 = points[i + 1];
segDist = p1.distanceTo(p2);
dist += segDist;
if (dist > halfDist) {
ratio = (dist - halfDist) / segDist;
center = [
p2.x - ratio * (p2.x - p1.x),
p2.y - ratio * (p2.y - p1.y)
];
break;
}
}
}
var latlngCenter = crs.unproject(toPoint(center));
return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
}
+222
View File
@@ -0,0 +1,222 @@
import {isArray, formatNum} from '../core/Util';
/*
* @class Point
* @aka L.Point
*
* Represents a point with `x` and `y` coordinates in pixels.
*
* @example
*
* ```js
* 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:
*
* ```js
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
*
* Note that `Point` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
export function Point(x, y, round) {
// @property x: Number; The `x` coordinate of the point
this.x = (round ? Math.round(x) : x);
// @property y: Number; The `y` coordinate of the point
this.y = (round ? Math.round(y) : y);
}
var trunc = Math.trunc || function (v) {
return v > 0 ? Math.floor(v) : Math.ceil(v);
};
Point.prototype = {
// @method clone(): Point
// Returns a copy of the current point.
clone: function () {
return new Point(this.x, this.y);
},
// @method add(otherPoint: Point): Point
// Returns the result of addition of the current and the given points.
add: function (point) {
// non-destructive, returns a new point
return this.clone()._add(toPoint(point));
},
_add: function (point) {
// destructive, used directly for performance in situations where it's safe to modify existing point
this.x += point.x;
this.y += point.y;
return this;
},
// @method subtract(otherPoint: Point): Point
// Returns the result of subtraction of the given point from the current.
subtract: function (point) {
return this.clone()._subtract(toPoint(point));
},
_subtract: function (point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
// @method divideBy(num: Number): Point
// Returns the result of division of the current point by the given number.
divideBy: function (num) {
return this.clone()._divideBy(num);
},
_divideBy: function (num) {
this.x /= num;
this.y /= num;
return this;
},
// @method multiplyBy(num: Number): Point
// Returns the result of multiplication of the current point by the given number.
multiplyBy: function (num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy: function (num) {
this.x *= num;
this.y *= num;
return this;
},
// @method scaleBy(scale: Point): Point
// Multiply each coordinate of the current point by each coordinate of
// `scale`. In linear algebra terms, multiply the point by the
// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
// defined by `scale`.
scaleBy: function (point) {
return new Point(this.x * point.x, this.y * point.y);
},
// @method unscaleBy(scale: Point): Point
// Inverse of `scaleBy`. Divide each coordinate of the current point by
// each coordinate of `scale`.
unscaleBy: function (point) {
return new Point(this.x / point.x, this.y / point.y);
},
// @method round(): Point
// Returns a copy of the current point with rounded coordinates.
round: function () {
return this.clone()._round();
},
_round: function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
// @method floor(): Point
// Returns a copy of the current point with floored coordinates (rounded down).
floor: function () {
return this.clone()._floor();
},
_floor: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
// @method ceil(): Point
// Returns a copy of the current point with ceiled coordinates (rounded up).
ceil: function () {
return this.clone()._ceil();
},
_ceil: function () {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
},
// @method trunc(): Point
// Returns a copy of the current point with truncated coordinates (rounded towards zero).
trunc: function () {
return this.clone()._trunc();
},
_trunc: function () {
this.x = trunc(this.x);
this.y = trunc(this.y);
return this;
},
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo: function (point) {
point = toPoint(point);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
// @method equals(otherPoint: Point): Boolean
// Returns `true` if the given point has the same coordinates.
equals: function (point) {
point = toPoint(point);
return point.x === this.x &&
point.y === this.y;
},
// @method contains(otherPoint: Point): Boolean
// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
contains: function (point) {
point = toPoint(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
},
// @method toString(): String
// Returns a string representation of the point for debugging purposes.
toString: function () {
return 'Point(' +
formatNum(this.x) + ', ' +
formatNum(this.y) + ')';
}
};
// @factory L.point(x: Number, y: Number, round?: Boolean)
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
// @alternative
// @factory L.point(coords: Number[])
// Expects an array of the form `[x, y]` instead.
// @alternative
// @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead.
export function toPoint(x, y, round) {
if (x instanceof Point) {
return x;
}
if (isArray(x)) {
return new Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
if (typeof x === 'object' && 'x' in x && 'y' in x) {
return new Point(x.x, x.y);
}
return new Point(x, y, round);
}
+129
View File
@@ -0,0 +1,129 @@
import * as LineUtil from './LineUtil';
import {toLatLng} from '../geo/LatLng';
import {toPoint} from './Point';
import {toLatLngBounds} from '../geo/LatLngBounds';
/*
* @namespace PolyUtil
* Various utility functions for polygon geometries.
*/
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
* Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
* Used by Leaflet to only show polygon points that are on the screen or near, increasing
* performance. Note that polygon points needs different algorithm for clipping
* than polyline, so there's a separate method for it.
*/
export function clipPolygon(points, bounds, round) {
var clippedPoints,
edges = [1, 4, 2, 8],
i, j, k,
a, b,
len, edge, p;
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = LineUtil._getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
// if a is inside the clip window
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
}
/* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
* Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
*/
export function polygonCenter(latlngs, crs) {
var i, j, p1, p2, f, area, x, y, center;
if (!latlngs || latlngs.length === 0) {
throw new Error('latlngs not passed');
}
if (!LineUtil.isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
var centroidLatLng = toLatLng([0, 0]);
var bounds = toLatLngBounds(latlngs);
var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
// tests showed that below 1700 rounding errors are happening
if (areaBounds < 1700) {
// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
centroidLatLng = centroid(latlngs);
}
var len = latlngs.length;
var points = [];
for (i = 0; i < len; i++) {
var latlng = toLatLng(latlngs[i]);
points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
}
area = x = y = 0;
// polygon centroid algorithm;
for (i = 0, j = len - 1; i < len; j = i++) {
p1 = points[i];
p2 = points[j];
f = p1.y * p2.x - p2.y * p1.x;
x += (p1.x + p2.x) * f;
y += (p1.y + p2.y) * f;
area += f * 3;
}
if (area === 0) {
// Polygon is so small that all points are on same pixel.
center = points[0];
} else {
center = [x / area, y / area];
}
var latlngCenter = crs.unproject(toPoint(center));
return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
}
/* @function centroid(latlngs: LatLng[]): LatLng
* Returns the 'center of mass' of the passed LatLngs.
*/
export function centroid(coords) {
var latSum = 0;
var lngSum = 0;
var len = 0;
for (var i = 0; i < coords.length; i++) {
var latlng = toLatLng(coords[i]);
latSum += latlng.lat;
lngSum += latlng.lng;
len++;
}
return toLatLng([latSum / len, lngSum / len]);
}
+79
View File
@@ -0,0 +1,79 @@
import {Point} from './Point';
import * as Util from '../core/Util';
/*
* @class Transformation
* @aka L.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.
*
* @example
*
* ```js
* var transformation = 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)
* ```
*/
// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
// Creates a `Transformation` object with the given coefficients.
export function Transformation(a, b, c, d) {
if (Util.isArray(a)) {
// use array properties
this._a = a[0];
this._b = a[1];
this._c = a[2];
this._d = a[3];
return;
}
this._a = a;
this._b = b;
this._c = c;
this._d = d;
}
Transformation.prototype = {
// @method transform(point: Point, scale?: Number): Point
// Returns a transformed point, optionally multiplied by the given scale.
// Only accepts actual `L.Point` instances, not arrays.
transform: function (point, scale) { // (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform: function (point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
// @method untransform(point: Point, scale?: Number): Point
// Returns the reverse transformation of the given point, optionally divided
// by the given scale. Only accepts actual `L.Point` instances, not arrays.
untransform: function (point, scale) {
scale = scale || 1;
return new Point(
(point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c);
}
};
// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// Instantiates a Transformation object with the given coefficients.
// @alternative
// @factory L.transformation(coefficients: Array): Transformation
// Expects an coefficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`.
export function toTransformation(a, b, c, d) {
return new Transformation(a, b, c, d);
}
+8
View File
@@ -0,0 +1,8 @@
export {Point, toPoint as point} from './Point';
export {Bounds, toBounds as bounds} from './Bounds';
export {Transformation, toTransformation as transformation} from './Transformation';
import * as LineUtil from './LineUtil';
export {LineUtil};
import * as PolyUtil from './PolyUtil';
export {PolyUtil};
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="26" width="26"><path d="M.032 17.056l13-8 13 8-13 8-13-8" fill="#b9b9b9"/><path d="M.032 17.056l-.032.93 13 8 13-8 .032-.93-13 8z" fill="#737373"/><path d="M0 13.076l13-8 13 8-13 8-13-8" fill="#cdcdcd"/><path d="M0 13.076v.91l13 8 13-8v-.91l-13 8z" fill="#737373"/><path d="M0 8.986l13-8 13 8-13 8-13-8" fill-opacity=".585" stroke="#797979" stroke-width=".1" fill="#e9e9e9"/><path d="M0 8.986v1l13 8 13-8v-1l-13 8z" fill="#737373"/></svg>

After

Width:  |  Height:  |  Size: 486 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 500 820" version="1.1" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule: evenodd; clip-rule: evenodd; stroke-linecap: round;"><defs><linearGradient x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.30025e-15,-37.566,37.566,2.30025e-15,416.455,540.999)" id="map-marker-38-f"><stop offset="0" stop-color="rgb(18,111,198)"/><stop offset="1" stop-color="rgb(76,156,209)"/></linearGradient><linearGradient x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.16666e-15,-19.053,19.053,1.16666e-15,414.482,522.486)" id="map-marker-38-s"><stop offset="0" stop-color="rgb(46,108,151)"/><stop offset="1" stop-color="rgb(56,131,183)"/></linearGradient></defs><g transform="matrix(19.5417,0,0,19.5417,-7889.1,-9807.44)"><path d="M416.544,503.612C409.971,503.612 404.5,509.303 404.5,515.478C404.5,518.256 406.064,521.786 407.194,524.224L416.5,542.096L425.762,524.224C426.892,521.786 428.5,518.433 428.5,515.478C428.5,509.303 423.117,503.612 416.544,503.612ZM416.544,510.767C419.128,510.784 421.223,512.889 421.223,515.477C421.223,518.065 419.128,520.14 416.544,520.156C413.96,520.139 411.865,518.066 411.865,515.477C411.865,512.889 413.96,510.784 416.544,510.767Z" stroke-width="1.1px" fill="url(#map-marker-38-f)" stroke="url(#map-marker-38-s)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+348
View File
@@ -0,0 +1,348 @@
import {Map} from '../map/Map';
import {Layer} from './Layer';
import {FeatureGroup} from './FeatureGroup';
import * as Util from '../core/Util';
import {toLatLng, LatLng} from '../geo/LatLng';
import {toPoint} from '../geometry/Point';
import * as DomUtil from '../dom/DomUtil';
/*
* @class DivOverlay
* @inherits Interactive layer
* @aka L.DivOverlay
* Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
*/
// @namespace DivOverlay
export var DivOverlay = Layer.extend({
// @section
// @aka DivOverlay options
options: {
// @option interactive: Boolean = false
// If true, the popup/tooltip will listen to the mouse events.
interactive: false,
// @option offset: Point = Point(0, 0)
// The offset of the overlay position.
offset: [0, 0],
// @option className: String = ''
// A custom CSS class name to assign to the overlay.
className: '',
// @option pane: String = undefined
// `Map pane` where the overlay will be added.
pane: undefined,
// @option content: String|HTMLElement|Function = ''
// Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
// passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
content: ''
},
initialize: function (options, source) {
if (options && (options instanceof LatLng || Util.isArray(options))) {
this._latlng = toLatLng(options);
Util.setOptions(this, source);
} else {
Util.setOptions(this, options);
this._source = source;
}
if (this.options.content) {
this._content = this.options.content;
}
},
// @method openOn(map: Map): this
// Adds the overlay to the map.
// Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
openOn: function (map) {
map = arguments.length ? map : this._source._map; // experimental, not the part of public api
if (!map.hasLayer(this)) {
map.addLayer(this);
}
return this;
},
// @method close(): this
// Closes the overlay.
// Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
// and `layer.closePopup()`/`.closeTooltip()`.
close: function () {
if (this._map) {
this._map.removeLayer(this);
}
return this;
},
// @method toggle(layer?: Layer): this
// Opens or closes the overlay bound to layer depending on its current state.
// Argument may be omitted only for overlay bound to layer.
// Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
toggle: function (layer) {
if (this._map) {
this.close();
} else {
if (arguments.length) {
this._source = layer;
} else {
layer = this._source;
}
this._prepareOpen();
// open the overlay on the map
this.openOn(layer._map);
}
return this;
},
onAdd: function (map) {
this._zoomAnimated = map._zoomAnimated;
if (!this._container) {
this._initLayout();
}
if (map._fadeAnimated) {
DomUtil.setOpacity(this._container, 0);
}
clearTimeout(this._removeTimeout);
this.getPane().appendChild(this._container);
this.update();
if (map._fadeAnimated) {
DomUtil.setOpacity(this._container, 1);
}
this.bringToFront();
if (this.options.interactive) {
DomUtil.addClass(this._container, 'leaflet-interactive');
this.addInteractiveTarget(this._container);
}
},
onRemove: function (map) {
if (map._fadeAnimated) {
DomUtil.setOpacity(this._container, 0);
this._removeTimeout = setTimeout(Util.bind(DomUtil.remove, undefined, this._container), 200);
} else {
DomUtil.remove(this._container);
}
if (this.options.interactive) {
DomUtil.removeClass(this._container, 'leaflet-interactive');
this.removeInteractiveTarget(this._container);
}
},
// @namespace DivOverlay
// @method getLatLng: LatLng
// Returns the geographical point of the overlay.
getLatLng: function () {
return this._latlng;
},
// @method setLatLng(latlng: LatLng): this
// Sets the geographical point where the overlay will open.
setLatLng: function (latlng) {
this._latlng = toLatLng(latlng);
if (this._map) {
this._updatePosition();
this._adjustPan();
}
return this;
},
// @method getContent: String|HTMLElement
// Returns the content of the overlay.
getContent: function () {
return this._content;
},
// @method setContent(htmlContent: String|HTMLElement|Function): this
// Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
// The function should return a `String` or `HTMLElement` to be used in the overlay.
setContent: function (content) {
this._content = content;
this.update();
return this;
},
// @method getElement: String|HTMLElement
// Returns the HTML container of the overlay.
getElement: function () {
return this._container;
},
// @method update: null
// Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
update: function () {
if (!this._map) { return; }
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
getEvents: function () {
var events = {
zoom: this._updatePosition,
viewreset: this._updatePosition
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @method isOpen: Boolean
// Returns `true` when the overlay is visible on the map.
isOpen: function () {
return !!this._map && this._map.hasLayer(this);
},
// @method bringToFront: this
// Brings this overlay in front of other overlays (in the same map pane).
bringToFront: function () {
if (this._map) {
DomUtil.toFront(this._container);
}
return this;
},
// @method bringToBack: this
// Brings this overlay to the back of other overlays (in the same map pane).
bringToBack: function () {
if (this._map) {
DomUtil.toBack(this._container);
}
return this;
},
// prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
_prepareOpen: function (latlng) {
var source = this._source;
if (!source._map) { return false; }
if (source instanceof FeatureGroup) {
source = null;
var layers = this._source._layers;
for (var id in layers) {
if (layers[id]._map) {
source = layers[id];
break;
}
}
if (!source) { return false; } // Unable to get source layer.
// set overlay source to this layer
this._source = source;
}
if (!latlng) {
if (source.getCenter) {
latlng = source.getCenter();
} else if (source.getLatLng) {
latlng = source.getLatLng();
} else if (source.getBounds) {
latlng = source.getBounds().getCenter();
} else {
throw new Error('Unable to get source layer LatLng.');
}
}
this.setLatLng(latlng);
if (this._map) {
// update the overlay (content, layout, etc...)
this.update();
}
return true;
},
_updateContent: function () {
if (!this._content) { return; }
var node = this._contentNode;
var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
if (typeof content === 'string') {
node.innerHTML = content;
} else {
while (node.hasChildNodes()) {
node.removeChild(node.firstChild);
}
node.appendChild(content);
}
// @namespace DivOverlay
// @section DivOverlay events
// @event contentupdate: Event
// Fired when the content of the overlay is updated
this.fire('contentupdate');
},
_updatePosition: function () {
if (!this._map) { return; }
var pos = this._map.latLngToLayerPoint(this._latlng),
offset = toPoint(this.options.offset),
anchor = this._getAnchor();
if (this._zoomAnimated) {
DomUtil.setPosition(this._container, pos.add(anchor));
} else {
offset = offset.add(pos).add(anchor);
}
var bottom = this._containerBottom = -offset.y,
left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
// bottom position the overlay in case the height of the overlay changes (images loading etc)
this._container.style.bottom = bottom + 'px';
this._container.style.left = left + 'px';
},
_getAnchor: function () {
return [0, 0];
}
});
Map.include({
_initOverlay: function (OverlayClass, content, latlng, options) {
var overlay = content;
if (!(overlay instanceof OverlayClass)) {
overlay = new OverlayClass(options).setContent(content);
}
if (latlng) {
overlay.setLatLng(latlng);
}
return overlay;
}
});
Layer.include({
_initOverlay: function (OverlayClass, old, content, options) {
var overlay = content;
if (overlay instanceof OverlayClass) {
Util.setOptions(overlay, options);
overlay._source = this;
} else {
overlay = (old && !options) ? old : new OverlayClass(options, this);
overlay.setContent(content);
}
return overlay;
}
});
+94
View File
@@ -0,0 +1,94 @@
import {LayerGroup} from './LayerGroup';
import {LatLngBounds} from '../geo/LatLngBounds';
/*
* @class FeatureGroup
* @aka L.FeatureGroup
* @inherits LayerGroup
*
* Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
* * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
* * Events are propagated to the `FeatureGroup`, so if the group has an event
* handler, it will handle events from any of the layers. This includes mouse events
* and custom events.
* * Has `layeradd` and `layerremove` events
*
* @example
*
* ```js
* L.featureGroup([marker1, marker2, polyline])
* .bindPopup('Hello world!')
* .on('click', function() { alert('Clicked on a member of the group!'); })
* .addTo(map);
* ```
*/
export var FeatureGroup = LayerGroup.extend({
addLayer: function (layer) {
if (this.hasLayer(layer)) {
return this;
}
layer.addEventParent(this);
LayerGroup.prototype.addLayer.call(this, layer);
// @event layeradd: LayerEvent
// Fired when a layer is added to this `FeatureGroup`
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);
LayerGroup.prototype.removeLayer.call(this, layer);
// @event layerremove: LayerEvent
// Fired when a layer is removed from this `FeatureGroup`
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 back 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 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[], options?: Object)
// Create a feature group, optionally given an initial set of layers and an `options` object.
export var featureGroup = function (layers, options) {
return new FeatureGroup(layers, options);
};
+452
View File
@@ -0,0 +1,452 @@
import {LayerGroup} from './LayerGroup';
import {FeatureGroup} from './FeatureGroup';
import * as Util from '../core/Util';
import {Marker} from './marker/Marker';
import {Circle} from './vector/Circle';
import {CircleMarker} from './vector/CircleMarker';
import {Polyline} from './vector/Polyline';
import {Polygon} from './vector/Polygon';
import {LatLng} from '../geo/LatLng';
import * as LineUtil from '../geometry/LineUtil';
import {toLatLng} from '../geo/LatLng';
/*
* @class GeoJSON
* @aka L.GeoJSON
* @inherits FeatureGroup
*
* Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
* GeoJSON data and display it on the map. Extends `FeatureGroup`.
*
* @example
*
* ```js
* L.geoJSON(data, {
* style: function (feature) {
* return {color: feature.properties.color};
* }
* }).bindPopup(function (layer) {
* return layer.feature.properties.description;
* }).addTo(map);
* ```
*/
export var GeoJSON = FeatureGroup.extend({
/* @section
* @aka GeoJSON options
*
* @option pointToLayer: Function = *
* A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
* called when data is added, passing the GeoJSON point feature and its `LatLng`.
* The default is to spawn a default `Marker`:
* ```js
* function(geoJsonPoint, latlng) {
* return L.marker(latlng);
* }
* ```
*
* @option style: Function = *
* A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
* called internally when data is added.
* The default value is to not override any defaults:
* ```js
* function (geoJsonFeature) {
* return {}
* }
* ```
*
* @option onEachFeature: Function = *
* A `Function` that will be called once for each created `Feature`, after it has
* been created and styled. Useful for attaching events and popups to features.
* The default is to do nothing with the newly created layers:
* ```js
* function (feature, layer) {}
* ```
*
* @option filter: Function = *
* A `Function` that will be used to decide whether to include a feature or not.
* The default is to include all features:
* ```js
* function (geoJsonFeature) {
* return true;
* }
* ```
* Note: dynamically changing the `filter` option will have effect only on newly
* added data. It will _not_ re-evaluate already included features.
*
* @option coordsToLatLng: Function = *
* A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
* The default is the `coordsToLatLng` static method.
*
* @option markersInheritOptions: Boolean = false
* Whether default Markers for "Point" type Features inherit from group options.
*/
initialize: function (geojson, options) {
Util.setOptions(this, options);
this._layers = {};
if (geojson) {
this.addData(geojson);
}
},
// @method addData( <GeoJSON> data ): this
// Adds a GeoJSON object to the layer.
addData: function (geojson) {
var features = Util.isArray(geojson) ? geojson : geojson.features,
i, len, feature;
if (features) {
for (i = 0, len = features.length; i < len; i++) {
// only add this if geometry or geometries are set and not null
feature = features[i];
if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
this.addData(feature);
}
}
return this;
}
var options = this.options;
if (options.filter && !options.filter(geojson)) { return this; }
var layer = geometryToLayer(geojson, options);
if (!layer) {
return this;
}
layer.feature = asFeature(geojson);
layer.defaultOptions = layer.options;
this.resetStyle(layer);
if (options.onEachFeature) {
options.onEachFeature(geojson, layer);
}
return this.addLayer(layer);
},
// @method resetStyle( <Path> layer? ): this
// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
// If `layer` is omitted, the style of all features in the current layer is reset.
resetStyle: function (layer) {
if (layer === undefined) {
return this.eachLayer(this.resetStyle, this);
}
// reset any custom styles
layer.options = Util.extend({}, layer.defaultOptions);
this._setLayerStyle(layer, this.options.style);
return this;
},
// @method setStyle( <Function> style ): this
// Changes styles of GeoJSON vector layers with the given style function.
setStyle: function (style) {
return this.eachLayer(function (layer) {
this._setLayerStyle(layer, style);
}, this);
},
_setLayerStyle: function (layer, style) {
if (layer.setStyle) {
if (typeof style === 'function') {
style = style(layer.feature);
}
layer.setStyle(style);
}
}
});
// @section
// There are several static functions which can be called without instantiating L.GeoJSON:
// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
// Creates a `Layer` from a given GeoJSON feature. Can use a custom
// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
// functions if provided as options.
export function geometryToLayer(geojson, options) {
var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
coords = geometry ? geometry.coordinates : null,
layers = [],
pointToLayer = options && options.pointToLayer,
_coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
latlng, latlngs, i, len;
if (!coords && !geometry) {
return null;
}
switch (geometry.type) {
case 'Point':
latlng = _coordsToLatLng(coords);
return _pointToLayer(pointToLayer, geojson, latlng, options);
case 'MultiPoint':
for (i = 0, len = coords.length; i < len; i++) {
latlng = _coordsToLatLng(coords[i]);
layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
}
return new FeatureGroup(layers);
case 'LineString':
case 'MultiLineString':
latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
return new Polyline(latlngs, options);
case 'Polygon':
case 'MultiPolygon':
latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
return new Polygon(latlngs, options);
case 'GeometryCollection':
for (i = 0, len = geometry.geometries.length; i < len; i++) {
var geoLayer = geometryToLayer({
geometry: geometry.geometries[i],
type: 'Feature',
properties: geojson.properties
}, options);
if (geoLayer) {
layers.push(geoLayer);
}
}
return new FeatureGroup(layers);
case 'FeatureCollection':
for (i = 0, len = geometry.features.length; i < len; i++) {
var featureLayer = geometryToLayer(geometry.features[i], options);
if (featureLayer) {
layers.push(featureLayer);
}
}
return new FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
}
function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
return pointToLayerFn ?
pointToLayerFn(geojson, latlng) :
new Marker(latlng, options && options.markersInheritOptions && options);
}
// @function coordsToLatLng(coords: Array): LatLng
// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
export function coordsToLatLng(coords) {
return new LatLng(coords[1], coords[0], coords[2]);
}
// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
export function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
var latlngs = [];
for (var i = 0, len = coords.length, latlng; i < len; i++) {
latlng = levelsDeep ?
coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
(_coordsToLatLng || coordsToLatLng)(coords[i]);
latlngs.push(latlng);
}
return latlngs;
}
// @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
export function latLngToCoords(latlng, precision) {
latlng = toLatLng(latlng);
return latlng.alt !== undefined ?
[Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision), Util.formatNum(latlng.alt, precision)] :
[Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision)];
}
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
export function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
var coords = [];
for (var i = 0, len = latlngs.length; i < len; i++) {
// Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
coords.push(levelsDeep ?
latLngsToCoords(latlngs[i], LineUtil.isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
latLngToCoords(latlngs[i], precision));
}
if (!levelsDeep && closed && coords.length > 0) {
coords.push(coords[0].slice());
}
return coords;
}
export function getFeature(layer, newGeometry) {
return layer.feature ?
Util.extend({}, layer.feature, {geometry: newGeometry}) :
asFeature(newGeometry);
}
// @function asFeature(geojson: Object): Object
// Normalize GeoJSON geometries/features into GeoJSON features.
export function asFeature(geojson) {
if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
return geojson;
}
return {
type: 'Feature',
properties: {},
geometry: geojson
};
}
var PointToGeoJSON = {
toGeoJSON: function (precision) {
return getFeature(this, {
type: 'Point',
coordinates: latLngToCoords(this.getLatLng(), precision)
});
}
};
// @namespace Marker
// @section Other methods
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
Marker.include(PointToGeoJSON);
// @namespace CircleMarker
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);
// @namespace Polyline
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
Polyline.include({
toGeoJSON: function (precision) {
var multi = !LineUtil.isFlat(this._latlngs);
var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'LineString',
coordinates: coords
});
}
});
// @namespace Polygon
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
Polygon.include({
toGeoJSON: function (precision) {
var holes = !LineUtil.isFlat(this._latlngs),
multi = holes && !LineUtil.isFlat(this._latlngs[0]);
var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
if (!holes) {
coords = [coords];
}
return getFeature(this, {
type: (multi ? 'Multi' : '') + 'Polygon',
coordinates: coords
});
}
});
// @namespace LayerGroup
LayerGroup.include({
toMultiPoint: function (precision) {
var coords = [];
this.eachLayer(function (layer) {
coords.push(layer.toGeoJSON(precision).geometry.coordinates);
});
return getFeature(this, {
type: 'MultiPoint',
coordinates: coords
});
},
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
toGeoJSON: function (precision) {
var type = this.feature && this.feature.geometry && this.feature.geometry.type;
if (type === 'MultiPoint') {
return this.toMultiPoint(precision);
}
var isGeometryCollection = type === 'GeometryCollection',
jsons = [];
this.eachLayer(function (layer) {
if (layer.toGeoJSON) {
var json = layer.toGeoJSON(precision);
if (isGeometryCollection) {
jsons.push(json.geometry);
} else {
var feature = asFeature(json);
// Squash nested feature collections
if (feature.type === 'FeatureCollection') {
jsons.push.apply(jsons, feature.features);
} else {
jsons.push(feature);
}
}
}
});
if (isGeometryCollection) {
return getFeature(this, {
geometries: jsons,
type: 'GeometryCollection'
});
}
return {
type: 'FeatureCollection',
features: jsons
};
}
});
// @namespace GeoJSON
// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
// Creates a GeoJSON layer. Optionally accepts an object in
// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
// (you can alternatively add it later with `addData` method) and an `options` object.
export function geoJSON(geojson, options) {
return new GeoJSON(geojson, options);
}
// Backward compatibility.
export var geoJson = geoJSON;
+270
View File
@@ -0,0 +1,270 @@
import {Layer} from './Layer';
import * as Util from '../core/Util';
import {toLatLngBounds} from '../geo/LatLngBounds';
import {Bounds} from '../geometry/Bounds';
import * as DomUtil from '../dom/DomUtil';
/*
* @class ImageOverlay
* @aka L.ImageOverlay
* @inherits Interactive layer
*
* Used to load and display a single image over specific bounds of the map. Extends `Layer`.
*
* @example
*
* ```js
* var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
* imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
* L.imageOverlay(imageUrl, imageBounds).addTo(map);
* ```
*/
export var ImageOverlay = Layer.extend({
// @section
// @aka ImageOverlay options
options: {
// @option opacity: Number = 1.0
// The opacity of the image overlay.
opacity: 1,
// @option alt: String = ''
// Text for the `alt` attribute of the image (useful for accessibility).
alt: '',
// @option interactive: Boolean = false
// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
interactive: false,
// @option crossOrigin: Boolean|String = false
// Whether the crossOrigin attribute will be added to the image.
// If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
crossOrigin: false,
// @option errorOverlayUrl: String = ''
// URL to the overlay image to show in place of the overlay that failed to load.
errorOverlayUrl: '',
// @option zIndex: Number = 1
// The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
zIndex: 1,
// @option className: String = ''
// A custom class name to assign to the image. Empty by default.
className: ''
},
initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
this._url = url;
this._bounds = toLatLngBounds(bounds);
Util.setOptions(this, options);
},
onAdd: function () {
if (!this._image) {
this._initImage();
if (this.options.opacity < 1) {
this._updateOpacity();
}
}
if (this.options.interactive) {
DomUtil.addClass(this._image, 'leaflet-interactive');
this.addInteractiveTarget(this._image);
}
this.getPane().appendChild(this._image);
this._reset();
},
onRemove: function () {
DomUtil.remove(this._image);
if (this.options.interactive) {
this.removeInteractiveTarget(this._image);
}
},
// @method setOpacity(opacity: Number): this
// Sets the opacity of the overlay.
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._image) {
this._updateOpacity();
}
return this;
},
setStyle: function (styleOpts) {
if (styleOpts.opacity) {
this.setOpacity(styleOpts.opacity);
}
return this;
},
// @method bringToFront(): this
// Brings the layer to the top of all overlays.
bringToFront: function () {
if (this._map) {
DomUtil.toFront(this._image);
}
return this;
},
// @method bringToBack(): this
// Brings the layer to the bottom of all overlays.
bringToBack: function () {
if (this._map) {
DomUtil.toBack(this._image);
}
return this;
},
// @method setUrl(url: String): this
// Changes the URL of the image.
setUrl: function (url) {
this._url = url;
if (this._image) {
this._image.src = url;
}
return this;
},
// @method setBounds(bounds: LatLngBounds): this
// Update the bounds that this ImageOverlay covers
setBounds: function (bounds) {
this._bounds = toLatLngBounds(bounds);
if (this._map) {
this._reset();
}
return this;
},
getEvents: function () {
var events = {
zoom: this._reset,
viewreset: this._reset
};
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @method setZIndex(value: Number): this
// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
setZIndex: function (value) {
this.options.zIndex = value;
this._updateZIndex();
return this;
},
// @method getBounds(): LatLngBounds
// Get the bounds that this ImageOverlay covers
getBounds: function () {
return this._bounds;
},
// @method getElement(): HTMLElement
// Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
// used by this overlay.
getElement: function () {
return this._image;
},
_initImage: function () {
var wasElementSupplied = this._url.tagName === 'IMG';
var img = this._image = wasElementSupplied ? this._url : DomUtil.create('img');
DomUtil.addClass(img, 'leaflet-image-layer');
if (this._zoomAnimated) { DomUtil.addClass(img, 'leaflet-zoom-animated'); }
if (this.options.className) { DomUtil.addClass(img, this.options.className); }
img.onselectstart = Util.falseFn;
img.onmousemove = Util.falseFn;
// @event load: Event
// Fired when the ImageOverlay layer has loaded its image
img.onload = Util.bind(this.fire, this, 'load');
img.onerror = Util.bind(this._overlayOnError, this, 'error');
if (this.options.crossOrigin || this.options.crossOrigin === '') {
img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
if (this.options.zIndex) {
this._updateZIndex();
}
if (wasElementSupplied) {
this._url = img.src;
return;
}
img.src = this._url;
img.alt = this.options.alt;
},
_animateZoom: function (e) {
var scale = this._map.getZoomScale(e.zoom),
offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
DomUtil.setTransform(this._image, offset, scale);
},
_reset: function () {
var image = this._image,
bounds = new Bounds(
this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
size = bounds.getSize();
DomUtil.setPosition(image, bounds.min);
image.style.width = size.x + 'px';
image.style.height = size.y + 'px';
},
_updateOpacity: function () {
DomUtil.setOpacity(this._image, this.options.opacity);
},
_updateZIndex: function () {
if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
this._image.style.zIndex = this.options.zIndex;
}
},
_overlayOnError: function () {
// @event error: Event
// Fired when the ImageOverlay layer fails to load its image
this.fire('error');
var errorUrl = this.options.errorOverlayUrl;
if (errorUrl && this._url !== errorUrl) {
this._url = errorUrl;
this._image.src = errorUrl;
}
},
// @method getCenter(): LatLng
// Returns the center of the ImageOverlay.
getCenter: function () {
return this._bounds.getCenter();
}
});
// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
// Instantiates an image overlay object given the URL of the image and the
// geographical bounds it is tied to.
export var imageOverlay = function (url, bounds, options) {
return new ImageOverlay(url, bounds, options);
};
+39
View File
@@ -0,0 +1,39 @@
@class Interactive layer
@inherits Layer
Some `Layer`s can be made interactive - when the user interacts
with such a layer, mouse events like `click` and `mouseover` can be handled.
Use the [event handling methods](#evented-method) to handle these events.
@option interactive: Boolean = true
If `false`, the layer will not emit mouse events and will act as a part of the underlying map.
@option bubblingMouseEvents: Boolean = true
When `true`, a mouse event on this layer will trigger the same event on the map
(unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
@section Mouse events
@event click: MouseEvent
Fired when the user clicks (or taps) the layer.
@event dblclick: MouseEvent
Fired when the user double-clicks (or double-taps) the layer.
@event mousedown: MouseEvent
Fired when the user pushes the mouse button on the layer.
@event mouseup: MouseEvent
Fired when the user releases the mouse button pushed on the layer.
@event mouseover: MouseEvent
Fired when the mouse enters the layer.
@event mouseout: MouseEvent
Fired when the mouse leaves the layer.
@event contextmenu: MouseEvent
Fired when the user right-clicks on the layer, 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).
+275
View File
@@ -0,0 +1,275 @@
import {Evented} from '../core/Events';
import {Map} from '../map/Map';
import * as Util from '../core/Util';
/*
* @class Layer
* @inherits Evented
* @aka L.Layer
* @aka ILayer
*
* A set of methods from the Layer base class that all Leaflet layers use.
* Inherits all methods, options and events from `L.Evented`.
*
* @example
*
* ```js
* var layer = L.marker(latlng).addTo(map);
* layer.addTo(map);
* layer.remove();
* ```
*
* @event add: Event
* Fired after the layer is added to a map
*
* @event remove: Event
* Fired after the layer is removed from a map
*/
export var Layer = Evented.extend({
// Classes extending `L.Layer` will inherit the following options:
options: {
// @option pane: String = 'overlayPane'
// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
pane: 'overlayPane',
// @option attribution: String = null
// String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
attribution: null,
bubblingMouseEvents: true
},
/* @section
* Classes extending `L.Layer` will inherit the following methods:
*
* @method addTo(map: Map|LayerGroup): this
* Adds the layer to the given map or layer group.
*/
addTo: function (map) {
map.addLayer(this);
return this;
},
// @method remove: this
// Removes the layer from the map it is currently active on.
remove: function () {
return this.removeFrom(this._map || this._mapToAdd);
},
// @method removeFrom(map: Map): this
// Removes the layer from the given map
//
// @alternative
// @method removeFrom(group: LayerGroup): this
// Removes the layer from the given `LayerGroup`
removeFrom: function (obj) {
if (obj) {
obj.removeLayer(this);
}
return this;
},
// @method getPane(name? : String): HTMLElement
// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
getPane: function (name) {
return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
},
addInteractiveTarget: function (targetEl) {
this._map._targets[Util.stamp(targetEl)] = this;
return this;
},
removeInteractiveTarget: function (targetEl) {
delete this._map._targets[Util.stamp(targetEl)];
return this;
},
// @method getAttribution: String
// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
getAttribution: function () {
return this.options.attribution;
},
_layerAdd: function (e) {
var map = e.target;
// check in case layer gets added and then removed before the map is ready
if (!map.hasLayer(this)) { return; }
this._map = map;
this._zoomAnimated = map._zoomAnimated;
if (this.getEvents) {
var events = this.getEvents();
map.on(events, this);
this.once('remove', function () {
map.off(events, this);
}, this);
}
this.onAdd(map);
this.fire('add');
map.fire('layeradd', {layer: this});
}
});
/* @section Extension methods
* @uninheritable
*
* Every layer should extend from `L.Layer` and (re-)implement the following methods.
*
* @method onAdd(map: Map): this
* Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
*
* @method onRemove(map: Map): this
* Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
*
* @method getEvents(): Object
* This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
*
* @method getAttribution(): String
* This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
*
* @method beforeAdd(map: Map): this
* Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
*/
/* @namespace Map
* @section Layer events
*
* @event layeradd: LayerEvent
* Fired when a new layer is added to the map.
*
* @event layerremove: LayerEvent
* Fired when some layer is removed from the map
*
* @section Methods for Layers and Controls
*/
Map.include({
// @method addLayer(layer: Layer): this
// Adds the given layer to the map
addLayer: function (layer) {
if (!layer._layerAdd) {
throw new Error('The provided object is not a Layer.');
}
var id = Util.stamp(layer);
if (this._layers[id]) { return this; }
this._layers[id] = layer;
layer._mapToAdd = this;
if (layer.beforeAdd) {
layer.beforeAdd(this);
}
this.whenReady(layer._layerAdd, layer);
return this;
},
// @method removeLayer(layer: Layer): this
// Removes the given layer from the map.
removeLayer: function (layer) {
var id = Util.stamp(layer);
if (!this._layers[id]) { return this; }
if (this._loaded) {
layer.onRemove(this);
}
delete this._layers[id];
if (this._loaded) {
this.fire('layerremove', {layer: layer});
layer.fire('remove');
}
layer._map = layer._mapToAdd = null;
return this;
},
// @method hasLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the map
hasLayer: function (layer) {
return Util.stamp(layer) in this._layers;
},
/* @method eachLayer(fn: Function, context?: Object): this
* Iterates over the layers of the map, optionally specifying context of the iterator function.
* ```
* map.eachLayer(function(layer){
* layer.bindPopup('Hello');
* });
* ```
*/
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
_addLayers: function (layers) {
layers = layers ? (Util.isArray(layers) ? layers : [layers]) : [];
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
},
_addZoomLimit: function (layer) {
if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
this._zoomBoundLayers[Util.stamp(layer)] = layer;
this._updateZoomLevels();
}
},
_removeZoomLimit: function (layer) {
var id = Util.stamp(layer);
if (this._zoomBoundLayers[id]) {
delete this._zoomBoundLayers[id];
this._updateZoomLevels();
}
},
_updateZoomLevels: function () {
var minZoom = Infinity,
maxZoom = -Infinity,
oldZoomSpan = this._getZoomSpan();
for (var i in this._zoomBoundLayers) {
var options = this._zoomBoundLayers[i].options;
minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
}
this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
// @section Map state change events
// @event zoomlevelschange: Event
// Fired when the number of zoomlevels on the map is changed due
// to adding or removing a layer.
if (oldZoomSpan !== this._getZoomSpan()) {
this.fire('zoomlevelschange');
}
if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
this.setZoom(this._layersMaxZoom);
}
if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
this.setZoom(this._layersMinZoom);
}
}
});
+159
View File
@@ -0,0 +1,159 @@
import {Layer} from './Layer';
import * as Util from '../core/Util';
/*
* @class LayerGroup
* @aka L.LayerGroup
* @inherits Interactive layer
*
* 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`.
*
* @example
*
* ```js
* L.layerGroup([marker1, marker2])
* .addLayer(polyline)
* .addTo(map);
* ```
*/
export var LayerGroup = Layer.extend({
initialize: function (layers, options) {
Util.setOptions(this, options);
this._layers = {};
var i, len;
if (layers) {
for (i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
}
},
// @method addLayer(layer: Layer): this
// Adds the given layer to the group.
addLayer: function (layer) {
var id = this.getLayerId(layer);
this._layers[id] = layer;
if (this._map) {
this._map.addLayer(layer);
}
return this;
},
// @method removeLayer(layer: Layer): this
// Removes the given layer from the group.
// @alternative
// @method removeLayer(id: Number): this
// Removes the layer with the given internal ID from the group.
removeLayer: function (layer) {
var id = layer in this._layers ? layer : this.getLayerId(layer);
if (this._map && this._layers[id]) {
this._map.removeLayer(this._layers[id]);
}
delete this._layers[id];
return this;
},
// @method hasLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the group.
// @alternative
// @method hasLayer(id: Number): Boolean
// Returns `true` if the given internal ID is currently added to the group.
hasLayer: function (layer) {
var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
return layerId in this._layers;
},
// @method clearLayers(): this
// Removes all the layers from the group.
clearLayers: function () {
return this.eachLayer(this.removeLayer, this);
},
// @method invoke(methodName: String, …): this
// Calls `methodName` on every layer contained in this group, passing any
// additional parameters. Has no effect if the layers contained do not
// implement `methodName`.
invoke: function (methodName) {
var args = Array.prototype.slice.call(arguments, 1),
i, layer;
for (i in this._layers) {
layer = this._layers[i];
if (layer[methodName]) {
layer[methodName].apply(layer, args);
}
}
return this;
},
onAdd: function (map) {
this.eachLayer(map.addLayer, map);
},
onRemove: function (map) {
this.eachLayer(map.removeLayer, map);
},
// @method eachLayer(fn: Function, context?: Object): this
// Iterates over the layers of the group, optionally specifying context of the iterator function.
// ```js
// group.eachLayer(function (layer) {
// layer.bindPopup('Hello');
// });
// ```
eachLayer: function (method, context) {
for (var i in this._layers) {
method.call(context, this._layers[i]);
}
return this;
},
// @method getLayer(id: Number): Layer
// Returns the layer with the given internal ID.
getLayer: function (id) {
return this._layers[id];
},
// @method getLayers(): Layer[]
// Returns an array of all the layers added to the group.
getLayers: function () {
var layers = [];
this.eachLayer(layers.push, layers);
return layers;
},
// @method setZIndex(zIndex: Number): this
// Calls `setZIndex` on every layer contained in this group, passing the z-index.
setZIndex: function (zIndex) {
return this.invoke('setZIndex', zIndex);
},
// @method getLayerId(layer: Layer): Number
// Returns the internal ID for a layer
getLayerId: function (layer) {
return Util.stamp(layer);
}
});
// @factory L.layerGroup(layers?: Layer[], options?: Object)
// Create a layer group, optionally given an initial set of layers and an `options` object.
export var layerGroup = function (layers, options) {
return new LayerGroup(layers, options);
};
+506
View File
@@ -0,0 +1,506 @@
import {DivOverlay} from './DivOverlay';
import * as DomEvent from '../dom/DomEvent';
import * as DomUtil from '../dom/DomUtil';
import {Point, toPoint} from '../geometry/Point';
import {Map} from '../map/Map';
import {Layer} from './Layer';
import {Path} from './vector/Path';
import {FeatureGroup} from './FeatureGroup';
/*
* @class Popup
* @inherits DivOverlay
* @aka L.Popup
* Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
* open popups while making sure that only one popup is open at one time
* (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
*
* @example
*
* If you want to just bind a popup to marker click and then open it, it's really easy:
*
* ```js
* marker.bindPopup(popupContent).openPopup();
* ```
* Path overlays like polylines also have a `bindPopup` method.
*
* A popup can be also standalone:
*
* ```js
* var popup = L.popup()
* .setLatLng(latlng)
* .setContent('<p>Hello world!<br />This is a nice popup.</p>')
* .openOn(map);
* ```
* or
* ```js
* var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
* .openOn(map);
* ```
*/
// @namespace Popup
export var Popup = DivOverlay.extend({
// @section
// @aka Popup options
options: {
// @option pane: String = 'popupPane'
// `Map pane` where the popup will be added.
pane: 'popupPane',
// @option offset: Point = Point(0, 7)
// The offset of the popup position.
offset: [0, 7],
// @option maxWidth: Number = 300
// Max width of the popup, in pixels.
maxWidth: 300,
// @option minWidth: Number = 50
// Min width of the popup, in pixels.
minWidth: 50,
// @option maxHeight: Number = null
// If set, creates a scrollable container of the given height
// inside a popup if its content exceeds it.
// The scrollable container can be styled using the
// `leaflet-popup-scrolled` CSS class selector.
maxHeight: null,
// @option autoPan: Boolean = true
// Set it to `false` if you don't want the map to do panning animation
// to fit the opened popup.
autoPan: true,
// @option autoPanPaddingTopLeft: Point = null
// The margin between the popup and the top left corner of the map
// view after autopanning was performed.
autoPanPaddingTopLeft: null,
// @option autoPanPaddingBottomRight: Point = null
// The margin between the popup and the bottom right corner of the map
// view after autopanning was performed.
autoPanPaddingBottomRight: null,
// @option autoPanPadding: Point = Point(5, 5)
// Equivalent of setting both top left and bottom right autopan padding to the same value.
autoPanPadding: [5, 5],
// @option keepInView: Boolean = false
// Set it to `true` if you want to prevent users from panning the popup
// off of the screen while it is open.
keepInView: false,
// @option closeButton: Boolean = true
// Controls the presence of a close button in the popup.
closeButton: true,
// @option autoClose: Boolean = true
// Set it to `false` if you want to override the default behavior of
// the popup closing when another popup is opened.
autoClose: true,
// @option closeOnEscapeKey: Boolean = true
// Set it to `false` if you want to override the default behavior of
// the ESC key for closing of the popup.
closeOnEscapeKey: true,
// @option closeOnClick: Boolean = *
// Set it if you want to override the default behavior of the popup closing when user clicks
// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
// @option className: String = ''
// A custom CSS class name to assign to the popup.
className: ''
},
// @namespace Popup
// @method openOn(map: Map): this
// Alternative to `map.openPopup(popup)`.
// Adds the popup to the map and closes the previous one.
openOn: function (map) {
map = arguments.length ? map : this._source._map; // experimental, not the part of public api
if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
map.removeLayer(map._popup);
}
map._popup = this;
return DivOverlay.prototype.openOn.call(this, map);
},
onAdd: function (map) {
DivOverlay.prototype.onAdd.call(this, map);
// @namespace Map
// @section Popup events
// @event popupopen: PopupEvent
// Fired when a popup is opened in the map
map.fire('popupopen', {popup: this});
if (this._source) {
// @namespace Layer
// @section Popup events
// @event popupopen: PopupEvent
// Fired when a popup bound to this layer is opened
this._source.fire('popupopen', {popup: this}, true);
// For non-path layers, we toggle the popup when clicking
// again the layer, so prevent the map to reopen it.
if (!(this._source instanceof Path)) {
this._source.on('preclick', DomEvent.stopPropagation);
}
}
},
onRemove: function (map) {
DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map
// @section Popup events
// @event popupclose: PopupEvent
// Fired when a popup in the map is closed
map.fire('popupclose', {popup: this});
if (this._source) {
// @namespace Layer
// @section Popup events
// @event popupclose: PopupEvent
// Fired when a popup bound to this layer is closed
this._source.fire('popupclose', {popup: this}, true);
if (!(this._source instanceof Path)) {
this._source.off('preclick', DomEvent.stopPropagation);
}
}
},
getEvents: function () {
var events = DivOverlay.prototype.getEvents.call(this);
if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
events.preclick = this.close;
}
if (this.options.keepInView) {
events.moveend = this._adjustPan;
}
return events;
},
_initLayout: function () {
var prefix = 'leaflet-popup',
container = this._container = DomUtil.create('div',
prefix + ' ' + (this.options.className || '') +
' leaflet-zoom-animated');
var wrapper = this._wrapper = DomUtil.create('div', prefix + '-content-wrapper', container);
this._contentNode = DomUtil.create('div', prefix + '-content', wrapper);
DomEvent.disableClickPropagation(container);
DomEvent.disableScrollPropagation(this._contentNode);
DomEvent.on(container, 'contextmenu', DomEvent.stopPropagation);
this._tipContainer = DomUtil.create('div', prefix + '-tip-container', container);
this._tip = DomUtil.create('div', prefix + '-tip', this._tipContainer);
if (this.options.closeButton) {
var closeButton = this._closeButton = DomUtil.create('a', prefix + '-close-button', container);
closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
closeButton.setAttribute('aria-label', 'Close popup');
closeButton.href = '#close';
closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
DomEvent.on(closeButton, 'click', function (ev) {
DomEvent.preventDefault(ev);
this.close();
}, this);
}
},
_updateLayout: function () {
var container = this._contentNode,
style = container.style;
style.width = '';
style.whiteSpace = 'nowrap';
var width = container.offsetWidth;
width = Math.min(width, this.options.maxWidth);
width = Math.max(width, this.options.minWidth);
style.width = (width + 1) + 'px';
style.whiteSpace = '';
style.height = '';
var height = container.offsetHeight,
maxHeight = this.options.maxHeight,
scrolledClass = 'leaflet-popup-scrolled';
if (maxHeight && height > maxHeight) {
style.height = maxHeight + 'px';
DomUtil.addClass(container, scrolledClass);
} else {
DomUtil.removeClass(container, scrolledClass);
}
this._containerWidth = this._container.offsetWidth;
},
_animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
anchor = this._getAnchor();
DomUtil.setPosition(this._container, pos.add(anchor));
},
_adjustPan: function () {
if (!this.options.autoPan) { return; }
if (this._map._panAnim) { this._map._panAnim.stop(); }
// We can endlessly recurse if keepInView is set and the view resets.
// Let's guard against that by exiting early if we're responding to our own autopan.
if (this._autopanning) {
this._autopanning = false;
return;
}
var map = this._map,
marginBottom = parseInt(DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
containerHeight = this._container.offsetHeight + marginBottom,
containerWidth = this._containerWidth,
layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
layerPos._add(DomUtil.getPosition(this._container));
var containerPos = map.layerPointToContainerPoint(layerPos),
padding = toPoint(this.options.autoPanPadding),
paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
size = map.getSize(),
dx = 0,
dy = 0;
if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
dx = containerPos.x + containerWidth - size.x + paddingBR.x;
}
if (containerPos.x - dx - paddingTL.x < 0) { // left
dx = containerPos.x - paddingTL.x;
}
if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
dy = containerPos.y + containerHeight - size.y + paddingBR.y;
}
if (containerPos.y - dy - paddingTL.y < 0) { // top
dy = containerPos.y - paddingTL.y;
}
// @namespace Map
// @section Popup events
// @event autopanstart: Event
// Fired when the map starts autopanning when opening a popup.
if (dx || dy) {
// Track that we're autopanning, as this function will be re-ran on moveend
if (this.options.keepInView) {
this._autopanning = true;
}
map
.fire('autopanstart')
.panBy([dx, dy]);
}
},
_getAnchor: function () {
// Where should we anchor the popup on the source layer?
return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
}
});
// @namespace Popup
// @factory L.popup(options?: Popup options, source?: Layer)
// Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
// @alternative
// @factory L.popup(latlng: LatLng, options?: Popup options)
// Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
export var popup = function (options, source) {
return new Popup(options, source);
};
/* @namespace Map
* @section Interaction Options
* @option closePopupOnClick: Boolean = true
* Set it to `false` if you don't want popups to close when user clicks the map.
*/
Map.mergeOptions({
closePopupOnClick: true
});
// @namespace Map
// @section Methods for Layers and Controls
Map.include({
// @method openPopup(popup: Popup): this
// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
// @alternative
// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
// Creates a popup with the specified content and options and opens it in the given point on a map.
openPopup: function (popup, latlng, options) {
this._initOverlay(Popup, popup, latlng, options)
.openOn(this);
return this;
},
// @method closePopup(popup?: Popup): this
// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
closePopup: function (popup) {
popup = arguments.length ? popup : this._popup;
if (popup) {
popup.close();
}
return this;
}
});
/*
* @namespace Layer
* @section Popup methods example
*
* All layers share a set of methods convenient for binding popups to it.
*
* ```js
* var layer = L.Polygon(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.
*/
// @section Popup methods
Layer.include({
// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
// Binds a popup to the layer with the passed `content` and sets up the
// necessary event listeners. If a `Function` is passed it will receive
// the layer as the first argument and should return a `String` or `HTMLElement`.
bindPopup: function (content, options) {
this._popup = this._initOverlay(Popup, this._popup, content, options);
if (!this._popupHandlersAdded) {
this.on({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = true;
}
return this;
},
// @method unbindPopup(): this
// Removes the popup previously bound with `bindPopup`.
unbindPopup: function () {
if (this._popup) {
this.off({
click: this._openPopup,
keypress: this._onKeyPress,
remove: this.closePopup,
move: this._movePopup
});
this._popupHandlersAdded = false;
this._popup = null;
}
return this;
},
// @method openPopup(latlng?: LatLng): this
// Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
openPopup: function (latlng) {
if (this._popup) {
if (!(this instanceof FeatureGroup)) {
this._popup._source = this;
}
if (this._popup._prepareOpen(latlng || this._latlng)) {
// open the popup on the map
this._popup.openOn(this._map);
}
}
return this;
},
// @method closePopup(): this
// Closes the popup bound to this layer if it is open.
closePopup: function () {
if (this._popup) {
this._popup.close();
}
return this;
},
// @method togglePopup(): this
// Opens or closes the popup bound to this layer depending on its current state.
togglePopup: function () {
if (this._popup) {
this._popup.toggle(this);
}
return this;
},
// @method isPopupOpen(): boolean
// Returns `true` if the popup bound to this layer is currently open.
isPopupOpen: function () {
return (this._popup ? this._popup.isOpen() : false);
},
// @method setPopupContent(content: String|HTMLElement|Popup): this
// Sets the content of the popup bound to this layer.
setPopupContent: function (content) {
if (this._popup) {
this._popup.setContent(content);
}
return this;
},
// @method getPopup(): Popup
// Returns the popup bound to this layer.
getPopup: function () {
return this._popup;
},
_openPopup: function (e) {
if (!this._popup || !this._map) {
return;
}
// prevent map click
DomEvent.stop(e);
var target = e.layer || e.target;
if (this._popup._source === target && !(target instanceof Path)) {
// treat it like a marker and figure out
// if we should toggle it open/closed
if (this._map.hasLayer(this._popup)) {
this.closePopup();
} else {
this.openPopup(e.latlng);
}
return;
}
this._popup._source = target;
this.openPopup(e.latlng);
},
_movePopup: function (e) {
this._popup.setLatLng(e.latlng);
},
_onKeyPress: function (e) {
if (e.originalEvent.keyCode === 13) {
this._openPopup(e);
}
}
});
+50
View File
@@ -0,0 +1,50 @@
import {ImageOverlay} from './ImageOverlay';
import * as DomUtil from '../dom/DomUtil';
import * as Util from '../core/Util';
/*
* @class SVGOverlay
* @aka L.SVGOverlay
* @inherits ImageOverlay
*
* Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
*
* An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
*
* @example
*
* ```js
* var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
* svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
* svgElement.setAttribute('viewBox', "0 0 200 200");
* svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
* var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
* L.svgOverlay(svgElement, svgElementBounds).addTo(map);
* ```
*/
export var SVGOverlay = ImageOverlay.extend({
_initImage: function () {
var el = this._image = this._url;
DomUtil.addClass(el, 'leaflet-image-layer');
if (this._zoomAnimated) { DomUtil.addClass(el, 'leaflet-zoom-animated'); }
if (this.options.className) { DomUtil.addClass(el, this.options.className); }
el.onselectstart = Util.falseFn;
el.onmousemove = Util.falseFn;
}
// @method getElement(): SVGElement
// Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
// used by this overlay.
});
// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
// A viewBox attribute is required on the SVG element to zoom in and out properly.
export function svgOverlay(el, bounds, options) {
return new SVGOverlay(el, bounds, options);
}
+444
View File
@@ -0,0 +1,444 @@
import {DivOverlay} from './DivOverlay';
import {toPoint} from '../geometry/Point';
import {Map} from '../map/Map';
import {Layer} from './Layer';
import * as DomUtil from '../dom/DomUtil';
import * as DomEvent from '../dom/DomEvent';
import * as Util from '../core/Util';
import {FeatureGroup} from './FeatureGroup';
/*
* @class Tooltip
* @inherits DivOverlay
* @aka L.Tooltip
* Used to display small texts on top of map layers.
*
* @example
* If you want to just bind a tooltip to marker:
*
* ```js
* marker.bindTooltip("my tooltip text").openTooltip();
* ```
* Path overlays like polylines also have a `bindTooltip` method.
*
* A tooltip can be also standalone:
*
* ```js
* var tooltip = L.tooltip()
* .setLatLng(latlng)
* .setContent('Hello world!<br />This is a nice tooltip.')
* .addTo(map);
* ```
* or
* ```js
* var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
* .addTo(map);
* ```
*
*
* Note about tooltip offset. Leaflet takes two options in consideration
* for computing tooltip offsetting:
* - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
* Add a positive x offset to move the tooltip to the right, and a positive y offset to
* move it to the bottom. Negatives will move to the left and top.
* - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
* should adapt this value if you use a custom icon.
*/
// @namespace Tooltip
export var Tooltip = DivOverlay.extend({
// @section
// @aka Tooltip options
options: {
// @option pane: String = 'tooltipPane'
// `Map pane` where the tooltip will be added.
pane: 'tooltipPane',
// @option offset: Point = Point(0, 0)
// Optional offset of the tooltip position.
offset: [0, 0],
// @option direction: String = 'auto'
// Direction where to open the tooltip. Possible values are: `right`, `left`,
// `top`, `bottom`, `center`, `auto`.
// `auto` will dynamically switch between `right` and `left` according to the tooltip
// position on the map.
direction: 'auto',
// @option permanent: Boolean = false
// Whether to open the tooltip permanently or only on mouseover.
permanent: false,
// @option sticky: Boolean = false
// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
sticky: false,
// @option opacity: Number = 0.9
// Tooltip container opacity.
opacity: 0.9
},
onAdd: function (map) {
DivOverlay.prototype.onAdd.call(this, map);
this.setOpacity(this.options.opacity);
// @namespace Map
// @section Tooltip events
// @event tooltipopen: TooltipEvent
// Fired when a tooltip is opened in the map.
map.fire('tooltipopen', {tooltip: this});
if (this._source) {
this.addEventParent(this._source);
// @namespace Layer
// @section Tooltip events
// @event tooltipopen: TooltipEvent
// Fired when a tooltip bound to this layer is opened.
this._source.fire('tooltipopen', {tooltip: this}, true);
}
},
onRemove: function (map) {
DivOverlay.prototype.onRemove.call(this, map);
// @namespace Map
// @section Tooltip events
// @event tooltipclose: TooltipEvent
// Fired when a tooltip in the map is closed.
map.fire('tooltipclose', {tooltip: this});
if (this._source) {
this.removeEventParent(this._source);
// @namespace Layer
// @section Tooltip events
// @event tooltipclose: TooltipEvent
// Fired when a tooltip bound to this layer is closed.
this._source.fire('tooltipclose', {tooltip: this}, true);
}
},
getEvents: function () {
var events = DivOverlay.prototype.getEvents.call(this);
if (!this.options.permanent) {
events.preclick = this.close;
}
return events;
},
_initLayout: function () {
var prefix = 'leaflet-tooltip',
className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
this._contentNode = this._container = DomUtil.create('div', className);
this._container.setAttribute('role', 'tooltip');
this._container.setAttribute('id', 'leaflet-tooltip-' + Util.stamp(this));
},
_updateLayout: function () {},
_adjustPan: function () {},
_setPosition: function (pos) {
var subX, subY,
map = this._map,
container = this._container,
centerPoint = map.latLngToContainerPoint(map.getCenter()),
tooltipPoint = map.layerPointToContainerPoint(pos),
direction = this.options.direction,
tooltipWidth = container.offsetWidth,
tooltipHeight = container.offsetHeight,
offset = toPoint(this.options.offset),
anchor = this._getAnchor();
if (direction === 'top') {
subX = tooltipWidth / 2;
subY = tooltipHeight;
} else if (direction === 'bottom') {
subX = tooltipWidth / 2;
subY = 0;
} else if (direction === 'center') {
subX = tooltipWidth / 2;
subY = tooltipHeight / 2;
} else if (direction === 'right') {
subX = 0;
subY = tooltipHeight / 2;
} else if (direction === 'left') {
subX = tooltipWidth;
subY = tooltipHeight / 2;
} else if (tooltipPoint.x < centerPoint.x) {
direction = 'right';
subX = 0;
subY = tooltipHeight / 2;
} else {
direction = 'left';
subX = tooltipWidth + (offset.x + anchor.x) * 2;
subY = tooltipHeight / 2;
}
pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
DomUtil.removeClass(container, 'leaflet-tooltip-right');
DomUtil.removeClass(container, 'leaflet-tooltip-left');
DomUtil.removeClass(container, 'leaflet-tooltip-top');
DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
DomUtil.setPosition(container, pos);
},
_updatePosition: function () {
var pos = this._map.latLngToLayerPoint(this._latlng);
this._setPosition(pos);
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
if (this._container) {
DomUtil.setOpacity(this._container, opacity);
}
},
_animateZoom: function (e) {
var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
this._setPosition(pos);
},
_getAnchor: function () {
// Where should we anchor the tooltip on the source layer?
return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
}
});
// @namespace Tooltip
// @factory L.tooltip(options?: Tooltip options, source?: Layer)
// Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
// @alternative
// @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
// Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
export var tooltip = function (options, source) {
return new Tooltip(options, source);
};
// @namespace Map
// @section Methods for Layers and Controls
Map.include({
// @method openTooltip(tooltip: Tooltip): this
// Opens the specified tooltip.
// @alternative
// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
// Creates a tooltip with the specified content and options and open it.
openTooltip: function (tooltip, latlng, options) {
this._initOverlay(Tooltip, tooltip, latlng, options)
.openOn(this);
return this;
},
// @method closeTooltip(tooltip: Tooltip): this
// Closes the tooltip given as parameter.
closeTooltip: function (tooltip) {
tooltip.close();
return this;
}
});
/*
* @namespace Layer
* @section Tooltip methods example
*
* All layers share a set of methods convenient for binding tooltips to it.
*
* ```js
* var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
* layer.openTooltip();
* layer.closeTooltip();
* ```
*/
// @section Tooltip methods
Layer.include({
// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
// Binds a tooltip to the layer with the passed `content` and sets up the
// necessary event listeners. If a `Function` is passed it will receive
// the layer as the first argument and should return a `String` or `HTMLElement`.
bindTooltip: function (content, options) {
if (this._tooltip && this.isTooltipOpen()) {
this.unbindTooltip();
}
this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
this._initTooltipInteractions();
if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
this.openTooltip();
}
return this;
},
// @method unbindTooltip(): this
// Removes the tooltip previously bound with `bindTooltip`.
unbindTooltip: function () {
if (this._tooltip) {
this._initTooltipInteractions(true);
this.closeTooltip();
this._tooltip = null;
}
return this;
},
_initTooltipInteractions: function (remove) {
if (!remove && this._tooltipHandlersAdded) { return; }
var onOff = remove ? 'off' : 'on',
events = {
remove: this.closeTooltip,
move: this._moveTooltip
};
if (!this._tooltip.options.permanent) {
events.mouseover = this._openTooltip;
events.mouseout = this.closeTooltip;
events.click = this._openTooltip;
if (this._map) {
this._addFocusListeners();
} else {
events.add = this._addFocusListeners;
}
} else {
events.add = this._openTooltip;
}
if (this._tooltip.options.sticky) {
events.mousemove = this._moveTooltip;
}
this[onOff](events);
this._tooltipHandlersAdded = !remove;
},
// @method openTooltip(latlng?: LatLng): this
// Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
openTooltip: function (latlng) {
if (this._tooltip) {
if (!(this instanceof FeatureGroup)) {
this._tooltip._source = this;
}
if (this._tooltip._prepareOpen(latlng)) {
// open the tooltip on the map
this._tooltip.openOn(this._map);
if (this.getElement) {
this._setAriaDescribedByOnLayer(this);
} else if (this.eachLayer) {
this.eachLayer(this._setAriaDescribedByOnLayer, this);
}
}
}
return this;
},
// @method closeTooltip(): this
// Closes the tooltip bound to this layer if it is open.
closeTooltip: function () {
if (this._tooltip) {
return this._tooltip.close();
}
},
// @method toggleTooltip(): this
// Opens or closes the tooltip bound to this layer depending on its current state.
toggleTooltip: function () {
if (this._tooltip) {
this._tooltip.toggle(this);
}
return this;
},
// @method isTooltipOpen(): boolean
// Returns `true` if the tooltip bound to this layer is currently open.
isTooltipOpen: function () {
return this._tooltip.isOpen();
},
// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
// Sets the content of the tooltip bound to this layer.
setTooltipContent: function (content) {
if (this._tooltip) {
this._tooltip.setContent(content);
}
return this;
},
// @method getTooltip(): Tooltip
// Returns the tooltip bound to this layer.
getTooltip: function () {
return this._tooltip;
},
_addFocusListeners: function () {
if (this.getElement) {
this._addFocusListenersOnLayer(this);
} else if (this.eachLayer) {
this.eachLayer(this._addFocusListenersOnLayer, this);
}
},
_addFocusListenersOnLayer: function (layer) {
var el = typeof layer.getElement === 'function' && layer.getElement();
if (el) {
DomEvent.on(el, 'focus', function () {
this._tooltip._source = layer;
this.openTooltip();
}, this);
DomEvent.on(el, 'blur', this.closeTooltip, this);
}
},
_setAriaDescribedByOnLayer: function (layer) {
var el = typeof layer.getElement === 'function' && layer.getElement();
if (el) {
el.setAttribute('aria-describedby', this._tooltip._container.id);
}
},
_openTooltip: function (e) {
if (!this._tooltip || !this._map) {
return;
}
// If the map is moving, we will show the tooltip after it's done.
if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
this._openOnceFlag = true;
var that = this;
this._map.once('moveend', function () {
that._openOnceFlag = false;
that._openTooltip(e);
});
return;
}
this._tooltip._source = e.layer || e.target;
this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
},
_moveTooltip: function (e) {
var latlng = e.latlng, containerPoint, layerPoint;
if (this._tooltip.options.sticky && e.originalEvent) {
containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
layerPoint = this._map.containerPointToLayerPoint(containerPoint);
latlng = this._map.layerPointToLatLng(layerPoint);
}
this._tooltip.setLatLng(latlng);
}
});
+106
View File
@@ -0,0 +1,106 @@
import {ImageOverlay} from './ImageOverlay';
import * as DomUtil from '../dom/DomUtil';
import * as Util from '../core/Util';
/*
* @class VideoOverlay
* @aka L.VideoOverlay
* @inherits ImageOverlay
*
* Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
*
* A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
* HTML5 element.
*
* @example
*
* ```js
* var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
* videoBounds = [[ 32, -130], [ 13, -100]];
* L.videoOverlay(videoUrl, videoBounds ).addTo(map);
* ```
*/
export var VideoOverlay = ImageOverlay.extend({
// @section
// @aka VideoOverlay options
options: {
// @option autoplay: Boolean = true
// Whether the video starts playing automatically when loaded.
// On some browsers autoplay will only work with `muted: true`
autoplay: true,
// @option loop: Boolean = true
// Whether the video will loop back to the beginning when played.
loop: true,
// @option keepAspectRatio: Boolean = true
// Whether the video will save aspect ratio after the projection.
// Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
keepAspectRatio: true,
// @option muted: Boolean = false
// Whether the video starts on mute when loaded.
muted: false,
// @option playsInline: Boolean = true
// Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
playsInline: true
},
_initImage: function () {
var wasElementSupplied = this._url.tagName === 'VIDEO';
var vid = this._image = wasElementSupplied ? this._url : DomUtil.create('video');
DomUtil.addClass(vid, 'leaflet-image-layer');
if (this._zoomAnimated) { DomUtil.addClass(vid, 'leaflet-zoom-animated'); }
if (this.options.className) { DomUtil.addClass(vid, this.options.className); }
vid.onselectstart = Util.falseFn;
vid.onmousemove = Util.falseFn;
// @event load: Event
// Fired when the video has finished loading the first frame
vid.onloadeddata = Util.bind(this.fire, this, 'load');
if (wasElementSupplied) {
var sourceElements = vid.getElementsByTagName('source');
var sources = [];
for (var j = 0; j < sourceElements.length; j++) {
sources.push(sourceElements[j].src);
}
this._url = (sourceElements.length > 0) ? sources : [vid.src];
return;
}
if (!Util.isArray(this._url)) { this._url = [this._url]; }
if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
vid.style['objectFit'] = 'fill';
}
vid.autoplay = !!this.options.autoplay;
vid.loop = !!this.options.loop;
vid.muted = !!this.options.muted;
vid.playsInline = !!this.options.playsInline;
for (var i = 0; i < this._url.length; i++) {
var source = DomUtil.create('source');
source.src = this._url[i];
vid.appendChild(source);
}
}
// @method getElement(): HTMLVideoElement
// Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
// used by this overlay.
});
// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
// geographical bounds it is tied to.
export function videoOverlay(video, bounds, options) {
return new VideoOverlay(video, bounds, options);
}
+24
View File
@@ -0,0 +1,24 @@
export {Layer} from './Layer';
export {LayerGroup, layerGroup} from './LayerGroup';
export {FeatureGroup, featureGroup} from './FeatureGroup';
import {GeoJSON, geoJSON, geoJson, geometryToLayer, coordsToLatLng, coordsToLatLngs, latLngToCoords, latLngsToCoords, getFeature, asFeature} from './GeoJSON';
GeoJSON.geometryToLayer = geometryToLayer;
GeoJSON.coordsToLatLng = coordsToLatLng;
GeoJSON.coordsToLatLngs = coordsToLatLngs;
GeoJSON.latLngToCoords = latLngToCoords;
GeoJSON.latLngsToCoords = latLngsToCoords;
GeoJSON.getFeature = getFeature;
GeoJSON.asFeature = asFeature;
export {GeoJSON, geoJSON, geoJson};
export {ImageOverlay, imageOverlay} from './ImageOverlay';
export {VideoOverlay, videoOverlay} from './VideoOverlay';
export {SVGOverlay, svgOverlay} from './SVGOverlay';
export {DivOverlay} from './DivOverlay';
export {Popup, popup} from './Popup';
export {Tooltip, tooltip} from './Tooltip';
export * from './marker/index';
export * from './tile/index';
export * from './vector/index';
+74
View File
@@ -0,0 +1,74 @@
import {Icon} from './Icon';
import {toPoint as point} from '../../geometry/Point';
import {empty} from '../../dom/DomUtil';
/*
* @class DivIcon
* @aka L.DivIcon
* @inherits Icon
*
* Represents a lightweight icon for markers that uses a simple `<div>`
* element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
*
* @example
* ```js
* 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' CSS class and is styled as a little white square with a shadow.
*/
export var DivIcon = Icon.extend({
options: {
// @section
// @aka DivIcon options
iconSize: [12, 12], // also can be set through CSS
// iconAnchor: (Point),
// popupAnchor: (Point),
// @option html: String|HTMLElement = ''
// Custom HTML code to put inside the div element, empty by default. Alternatively,
// an instance of `HTMLElement`.
html: false,
// @option bgPos: Point = [0, 0]
// Optional relative position of the background, in pixels
bgPos: null,
className: 'leaflet-div-icon'
},
createIcon: function (oldIcon) {
var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
options = this.options;
if (options.html instanceof Element) {
empty(div);
div.appendChild(options.html);
} else {
div.innerHTML = options.html !== false ? options.html : '';
}
if (options.bgPos) {
var bgPos = point(options.bgPos);
div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
}
this._setIconStyles(div, 'icon');
return div;
},
createShadow: function () {
return null;
}
});
// @factory L.divIcon(options: DivIcon options)
// Creates a `DivIcon` instance with the given options.
export function divIcon(options) {
return new DivIcon(options);
}
+66
View File
@@ -0,0 +1,66 @@
import {Icon} from './Icon';
import * as DomUtil from '../../dom/DomUtil';
/*
* @miniclass Icon.Default (Icon)
* @aka L.Icon.Default
* @section
*
* A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
* no icon is specified. Points to the blue marker image distributed with Leaflet
* releases.
*
* In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
* (which is a set of `Icon options`).
*
* If you want to _completely_ replace the default icon, override the
* `L.Marker.prototype.options.icon` with your own icon instead.
*/
export var IconDefault = Icon.extend({
options: {
iconUrl: 'marker-icon.png',
iconRetinaUrl: 'marker-icon-2x.png',
shadowUrl: 'marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
tooltipAnchor: [16, -28],
shadowSize: [41, 41]
},
_getIconUrl: function (name) {
if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
IconDefault.imagePath = this._detectIconPath();
}
// @option imagePath: String
// `Icon.Default` will try to auto-detect the location of the
// blue icon images. If you are placing these images in a non-standard
// way, set this option to point to the right path.
return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
},
_stripUrl: function (path) { // separate function to use in tests
var strip = function (str, re, idx) {
var match = re.exec(str);
return match && match[idx];
};
path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
return path && strip(path, /^(.*)marker-icon\.png$/, 1);
},
_detectIconPath: function () {
var el = DomUtil.create('div', 'leaflet-default-icon-path', document.body);
var path = DomUtil.getStyle(el, 'background-image') ||
DomUtil.getStyle(el, 'backgroundImage'); // IE8
document.body.removeChild(el);
path = this._stripUrl(path);
if (path) { return path; }
var link = document.querySelector('link[href$="leaflet.css"]');
if (!link) { return ''; }
return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
}
});
+165
View File
@@ -0,0 +1,165 @@
import {Class} from '../../core/Class';
import {setOptions} from '../../core/Util';
import {toPoint as point} from '../../geometry/Point';
import Browser from '../../core/Browser';
/*
* @class Icon
* @aka L.Icon
*
* Represents an icon to provide when creating a marker.
*
* @example
*
* ```js
* 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.
*
*/
export var Icon = Class.extend({
/* @section
* @aka Icon options
*
* @option iconUrl: String = null
* **(required)** The URL to the icon image (absolute or relative to your script path).
*
* @option iconRetinaUrl: String = null
* The URL to a retina sized version of the icon image (absolute or relative to your
* script path). Used for Retina screen devices.
*
* @option iconSize: Point = null
* Size of the icon image in pixels.
*
* @option iconAnchor: Point = null
* The 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.
*
* @option popupAnchor: Point = [0, 0]
* The coordinates of the point from which popups will "open", relative to the icon anchor.
*
* @option tooltipAnchor: Point = [0, 0]
* The coordinates of the point from which tooltips will "open", relative to the icon anchor.
*
* @option shadowUrl: String = null
* The URL to the icon shadow image. If not specified, no shadow image will be created.
*
* @option shadowRetinaUrl: String = null
*
* @option shadowSize: Point = null
* Size of the shadow image in pixels.
*
* @option shadowAnchor: Point = null
* The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
* as iconAnchor if not specified).
*
* @option className: String = ''
* A custom class name to assign to both icon and shadow images. Empty by default.
*/
options: {
popupAnchor: [0, 0],
tooltipAnchor: [0, 0],
// @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 (options) {
setOptions(this, options);
},
// @method createIcon(oldIcon?: HTMLElement): HTMLElement
// Called internally when the icon has to be shown, returns a `<img>` HTML element
// styled according to the options.
createIcon: function (oldIcon) {
return this._createIcon('icon', oldIcon);
},
// @method createShadow(oldIcon?: HTMLElement): HTMLElement
// As `createIcon`, but for the shadow beneath it.
createShadow: function (oldIcon) {
return this._createIcon('shadow', oldIcon);
},
_createIcon: function (name, oldIcon) {
var src = this._getIconUrl(name);
if (!src) {
if (name === 'icon') {
throw new Error('iconUrl not set in Icon options (see the docs).');
}
return null;
}
var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
this._setIconStyles(img, name);
if (this.options.crossOrigin || this.options.crossOrigin === '') {
img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
}
return img;
},
_setIconStyles: function (img, name) {
var options = this.options;
var sizeOption = options[name + 'Size'];
if (typeof sizeOption === 'number') {
sizeOption = [sizeOption, sizeOption];
}
var size = point(sizeOption),
anchor = point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
size && size.divideBy(2, true));
img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
if (anchor) {
img.style.marginLeft = (-anchor.x) + 'px';
img.style.marginTop = (-anchor.y) + 'px';
}
if (size) {
img.style.width = size.x + 'px';
img.style.height = size.y + 'px';
}
},
_createImg: function (src, el) {
el = el || document.createElement('img');
el.src = src;
return el;
},
_getIconUrl: function (name) {
return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
}
});
// @factory L.icon(options: Icon options)
// Creates an icon instance with the given options.
export function icon(options) {
return new Icon(options);
}
+161
View File
@@ -0,0 +1,161 @@
import {Handler} from '../../core/Handler';
import * as DomUtil from '../../dom/DomUtil';
import {Draggable} from '../../dom/Draggable';
import {toBounds} from '../../geometry/Bounds';
import {toPoint} from '../../geometry/Point';
import {requestAnimFrame, cancelAnimFrame} from '../../core/Util';
/*
* L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
*/
/* @namespace Marker
* @section 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 `Handler` methods). Example:
*
* ```js
* marker.dragging.disable();
* ```
*
* @property dragging: Handler
* Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
*/
export var MarkerDrag = Handler.extend({
initialize: function (marker) {
this._marker = marker;
},
addHooks: function () {
var icon = this._marker._icon;
if (!this._draggable) {
this._draggable = new Draggable(icon, icon, true);
}
this._draggable.on({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).enable();
DomUtil.addClass(icon, 'leaflet-marker-draggable');
},
removeHooks: function () {
this._draggable.off({
dragstart: this._onDragStart,
predrag: this._onPreDrag,
drag: this._onDrag,
dragend: this._onDragEnd
}, this).disable();
if (this._marker._icon) {
DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
}
},
moved: function () {
return this._draggable && this._draggable._moved;
},
_adjustPan: function (e) {
var marker = this._marker,
map = marker._map,
speed = this._marker.options.autoPanSpeed,
padding = this._marker.options.autoPanPadding,
iconPos = DomUtil.getPosition(marker._icon),
bounds = map.getPixelBounds(),
origin = map.getPixelOrigin();
var panBounds = toBounds(
bounds.min._subtract(origin).add(padding),
bounds.max._subtract(origin).subtract(padding)
);
if (!panBounds.contains(iconPos)) {
// Compute incremental movement
var movement = toPoint(
(Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
(Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
(Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
(Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
).multiplyBy(speed);
map.panBy(movement, {animate: false});
this._draggable._newPos._add(movement);
this._draggable._startPos._add(movement);
DomUtil.setPosition(marker._icon, this._draggable._newPos);
this._onDrag(e);
this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
}
},
_onDragStart: function () {
// @section Dragging events
// @event dragstart: Event
// Fired when the user starts dragging the marker.
// @event movestart: Event
// Fired when the marker starts moving (because of dragging).
this._oldLatLng = this._marker.getLatLng();
// When using ES6 imports it could not be set when `Popup` was not imported as well
this._marker.closePopup && this._marker.closePopup();
this._marker
.fire('movestart')
.fire('dragstart');
},
_onPreDrag: function (e) {
if (this._marker.options.autoPan) {
cancelAnimFrame(this._panRequest);
this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
}
},
_onDrag: function (e) {
var marker = this._marker,
shadow = marker._shadow,
iconPos = DomUtil.getPosition(marker._icon),
latlng = marker._map.layerPointToLatLng(iconPos);
// update shadow position
if (shadow) {
DomUtil.setPosition(shadow, iconPos);
}
marker._latlng = latlng;
e.latlng = latlng;
e.oldLatLng = this._oldLatLng;
// @event drag: Event
// Fired repeatedly while the user drags the marker.
marker
.fire('move', e)
.fire('drag', e);
},
_onDragEnd: function (e) {
// @event dragend: DragEndEvent
// Fired when the user stops dragging the marker.
cancelAnimFrame(this._panRequest);
// @event moveend: Event
// Fired when the marker stops moving (because of dragging).
delete this._oldLatLng;
this._marker
.fire('moveend')
.fire('dragend', e);
}
});
+419
View File
@@ -0,0 +1,419 @@
import {Layer} from '../Layer';
import {IconDefault} from './Icon.Default';
import * as Util from '../../core/Util';
import {toLatLng as latLng} from '../../geo/LatLng';
import {toPoint as point} from '../../geometry/Point';
import * as DomUtil from '../../dom/DomUtil';
import * as DomEvent from '../../dom/DomEvent';
import {MarkerDrag} from './Marker.Drag';
/*
* @class Marker
* @inherits Interactive 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);
* ```
*/
export var Marker = Layer.extend({
// @section
// @aka Marker options
options: {
// @option icon: Icon = *
// Icon instance to use for rendering the marker.
// See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
// If not specified, a common instance of `L.Icon.Default` is used.
icon: new IconDefault(),
// Option inherited from "Interactive layer" abstract class
interactive: true,
// @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).
// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
title: '',
// @option alt: String = 'Marker'
// Text for the `alt` attribute of the icon image.
// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
alt: 'Marker',
// @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',
// @option shadowPane: String = 'shadowPane'
// `Map pane` where the markers shadow will be added.
shadowPane: 'shadowPane',
// @option bubblingMouseEvents: Boolean = false
// When `true`, a mouse event on this marker will trigger the same event on the map
// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
bubblingMouseEvents: false,
// @option autoPanOnFocus: Boolean = true
// When `true`, the map will pan whenever the marker is focused (via
// e.g. pressing `tab` on the keyboard) to ensure the marker is
// visible within the map's bounds
autoPanOnFocus: true,
// @section Draggable marker options
// @option draggable: Boolean = false
// Whether the marker is draggable with mouse/touch or not.
draggable: false,
// @option autoPan: Boolean = false
// Whether to pan the map when dragging this marker near its edge or not.
autoPan: false,
// @option autoPanPadding: Point = Point(50, 50)
// Distance (in pixels to the left/right and to the top/bottom) of the
// map edge to start panning the map.
autoPanPadding: [50, 50],
// @option autoPanSpeed: Number = 10
// Number of pixels the map should pan by.
autoPanSpeed: 10
},
/* @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) {
Util.setOptions(this, options);
this._latlng = latLng(latlng);
},
onAdd: function (map) {
this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
if (this._zoomAnimated) {
map.on('zoomanim', this._animateZoom, this);
}
this._initIcon();
this.update();
},
onRemove: function (map) {
if (this.dragging && this.dragging.enabled()) {
this.options.draggable = true;
this.dragging.removeHooks();
}
delete this.dragging;
if (this._zoomAnimated) {
map.off('zoomanim', this._animateZoom, this);
}
this._removeIcon();
this._removeShadow();
},
getEvents: function () {
return {
zoom: this.update,
viewreset: this.update
};
},
// @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 = 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 getIcon: Icon
// Returns the current icon used by the marker
getIcon: function () {
return this.options.icon;
},
// @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 && this._map) {
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 (icon.tagName === 'IMG') {
icon.alt = options.alt || '';
}
}
DomUtil.addClass(icon, classToAdd);
if (options.keyboard) {
icon.tabIndex = '0';
icon.setAttribute('role', 'button');
}
this._icon = icon;
if (options.riseOnHover) {
this.on({
mouseover: this._bringToFront,
mouseout: this._resetZIndex
});
}
if (this.options.autoPanOnFocus) {
DomEvent.on(icon, 'focus', this._panOnFocus, this);
}
var newShadow = options.icon.createShadow(this._shadow),
addShadow = false;
if (newShadow !== this._shadow) {
this._removeShadow();
addShadow = true;
}
if (newShadow) {
DomUtil.addClass(newShadow, classToAdd);
newShadow.alt = '';
}
this._shadow = newShadow;
if (options.opacity < 1) {
this._updateOpacity();
}
if (addIcon) {
this.getPane().appendChild(this._icon);
}
this._initInteraction();
if (newShadow && addShadow) {
this.getPane(options.shadowPane).appendChild(this._shadow);
}
},
_removeIcon: function () {
if (this.options.riseOnHover) {
this.off({
mouseover: this._bringToFront,
mouseout: this._resetZIndex
});
}
if (this.options.autoPanOnFocus) {
DomEvent.off(this._icon, 'focus', this._panOnFocus, this);
}
DomUtil.remove(this._icon);
this.removeInteractiveTarget(this._icon);
this._icon = null;
},
_removeShadow: function () {
if (this._shadow) {
DomUtil.remove(this._shadow);
}
this._shadow = null;
},
_setPos: function (pos) {
if (this._icon) {
DomUtil.setPosition(this._icon, pos);
}
if (this._shadow) {
DomUtil.setPosition(this._shadow, pos);
}
this._zIndex = pos.y + this.options.zIndexOffset;
this._resetZIndex();
},
_updateZIndex: function (offset) {
if (this._icon) {
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; }
DomUtil.addClass(this._icon, 'leaflet-interactive');
this.addInteractiveTarget(this._icon);
if (MarkerDrag) {
var draggable = this.options.draggable;
if (this.dragging) {
draggable = this.dragging.enabled();
this.dragging.disable();
}
this.dragging = new 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;
if (this._icon) {
DomUtil.setOpacity(this._icon, opacity);
}
if (this._shadow) {
DomUtil.setOpacity(this._shadow, opacity);
}
},
_bringToFront: function () {
this._updateZIndex(this.options.riseOffset);
},
_resetZIndex: function () {
this._updateZIndex(0);
},
_panOnFocus: function () {
var map = this._map;
if (!map) { return; }
var iconOpts = this.options.icon.options;
var size = iconOpts.iconSize ? point(iconOpts.iconSize) : point(0, 0);
var anchor = iconOpts.iconAnchor ? point(iconOpts.iconAnchor) : point(0, 0);
map.panInside(this._latlng, {
paddingTopLeft: anchor,
paddingBottomRight: size.subtract(anchor)
});
},
_getPopupAnchor: function () {
return this.options.icon.options.popupAnchor;
},
_getTooltipAnchor: function () {
return this.options.icon.options.tooltipAnchor;
}
});
// 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.
export function marker(latlng, options) {
return new Marker(latlng, options);
}
+8
View File
@@ -0,0 +1,8 @@
import {Icon} from './Icon';
export {icon} from './Icon';
import {IconDefault} from './Icon.Default';
Icon.Default = IconDefault;
export {Icon};
export {DivIcon, divIcon} from './DivIcon';
export {Marker, marker} from './Marker';
+923
View File
@@ -0,0 +1,923 @@
import {Layer} from '../Layer';
import Browser from '../../core/Browser';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import {Point} from '../../geometry/Point';
import {Bounds} from '../../geometry/Bounds';
import {LatLngBounds, toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds';
/*
* @class GridLayer
* @inherits Layer
* @aka L.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.
*
*
* @section Synchronous usage
* @example
*
* To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
*
* ```js
* 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
* var size = this.getTileSize();
* tile.width = size.x;
* tile.height = size.y;
*
* // get a canvas context and draw something on it using coords.x, coords.y and coords.z
* var ctx = tile.getContext('2d');
*
* // return the tile so it can be rendered on screen
* return tile;
* }
* });
* ```
*
* @section Asynchronous usage
* @example
*
* Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
*
* ```js
* 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
* var size = this.getTileSize();
* tile.width = size.x;
* tile.height = size.y;
*
* // draw something asynchronously and pass the tile to the done() callback
* setTimeout(function() {
* done(error, tile);
* }, 1000);
*
* return tile;
* }
* });
* ```
*
* @section
*/
export var GridLayer = Layer.extend({
// @section
// @aka GridLayer options
options: {
// @option tileSize: Number|Point = 256
// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
tileSize: 256,
// @option opacity: Number = 1.0
// Opacity of the tiles. Can be used in the `createTile()` function.
opacity: 1,
// @option updateWhenIdle: Boolean = (depends)
// Load new tiles only when panning ends.
// `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
// `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
// [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
updateWhenIdle: Browser.mobile,
// @option updateWhenZooming: Boolean = true
// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
updateWhenZooming: true,
// @option updateInterval: Number = 200
// Tiles will not update more than once every `updateInterval` milliseconds when panning.
updateInterval: 200,
// @option zIndex: Number = 1
// The explicit zIndex of the tile layer.
zIndex: 1,
// @option bounds: LatLngBounds = undefined
// If set, tiles will only be loaded inside the set `LatLngBounds`.
bounds: null,
// @option minZoom: Number = 0
// The minimum zoom level down to which this layer will be displayed (inclusive).
minZoom: 0,
// @option maxZoom: Number = undefined
// The maximum zoom level up to which this layer will be displayed (inclusive).
maxZoom: undefined,
// @option maxNativeZoom: Number = undefined
// 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: undefined,
// @option minNativeZoom: Number = undefined
// Minimum zoom number the tile source has available. If it is specified,
// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
// from `minNativeZoom` level and auto-scaled.
minNativeZoom: undefined,
// @option noWrap: Boolean = false
// Whether the layer is wrapped around the antimeridian. If `true`, the
// GridLayer will only be displayed once at low zoom levels. Has no
// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
// tiles outside the CRS limits.
noWrap: false,
// @option pane: String = 'tilePane'
// `Map pane` where the grid layer will be added.
pane: 'tilePane',
// @option className: String = ''
// A custom class name to assign to the tile layer. Empty by default.
className: '',
// @option keepBuffer: Number = 2
// When panning the map, keep this many rows and columns of tiles before unloading them.
keepBuffer: 2
},
initialize: function (options) {
Util.setOptions(this, options);
},
onAdd: function () {
this._initContainer();
this._levels = {};
this._tiles = {};
this._resetView(); // implicit _update() call
},
beforeAdd: function (map) {
map._addZoomLimit(this);
},
onRemove: function (map) {
this._removeAllTiles();
DomUtil.remove(this._container);
map._removeZoomLimit(this);
this._container = null;
this._tileZoom = undefined;
},
// @method bringToFront: this
// Brings the tile layer to the top of all tile layers.
bringToFront: function () {
if (this._map) {
DomUtil.toFront(this._container);
this._setAutoZIndex(Math.max);
}
return this;
},
// @method bringToBack: this
// Brings the tile layer to the bottom of all tile layers.
bringToBack: function () {
if (this._map) {
DomUtil.toBack(this._container);
this._setAutoZIndex(Math.min);
}
return this;
},
// @method getContainer: HTMLElement
// Returns the HTML element that contains the tiles for this layer.
getContainer: function () {
return this._container;
},
// @method setOpacity(opacity: Number): this
// Changes the [opacity](#gridlayer-opacity) of the grid layer.
setOpacity: function (opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
// @method setZIndex(zIndex: Number): this
// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
setZIndex: function (zIndex) {
this.options.zIndex = zIndex;
this._updateZIndex();
return this;
},
// @method isLoading: Boolean
// Returns `true` if any tile in the grid layer has not finished loading.
isLoading: function () {
return this._loading;
},
// @method redraw: this
// Causes the layer to clear all the tiles and request them again.
redraw: function () {
if (this._map) {
this._removeAllTiles();
var tileZoom = this._clampZoom(this._map.getZoom());
if (tileZoom !== this._tileZoom) {
this._tileZoom = tileZoom;
this._updateLevels();
}
this._update();
}
return this;
},
getEvents: function () {
var events = {
viewprereset: this._invalidateAll,
viewreset: this._resetView,
zoom: this._resetView,
moveend: this._onMoveEnd
};
if (!this.options.updateWhenIdle) {
// update tiles on move, but not more often than once per given interval
if (!this._onMove) {
this._onMove = Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
}
events.move = this._onMove;
}
if (this._zoomAnimated) {
events.zoomanim = this._animateZoom;
}
return events;
},
// @section Extension methods
// Layers extending `GridLayer` shall reimplement the following method.
// @method createTile(coords: Object, done?: Function): HTMLElement
// Called only internally, must be overridden by classes extending `GridLayer`.
// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
// is specified, it must be called when the tile has finished loading and drawing.
createTile: function () {
return document.createElement('div');
},
// @section
// @method getTileSize: Point
// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
getTileSize: function () {
var s = this.options.tileSize;
return s instanceof Point ? s : new Point(s, s);
},
_updateZIndex: function () {
if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
this._container.style.zIndex = this.options.zIndex;
}
},
_setAutoZIndex: function (compare) {
// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
var layers = this.getPane().children,
edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
for (var i = 0, len = layers.length, zIndex; i < len; i++) {
zIndex = layers[i].style.zIndex;
if (layers[i] !== this._container && zIndex) {
edgeZIndex = compare(edgeZIndex, +zIndex);
}
}
if (isFinite(edgeZIndex)) {
this.options.zIndex = edgeZIndex + compare(-1, 1);
this._updateZIndex();
}
},
_updateOpacity: function () {
if (!this._map) { return; }
// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
if (Browser.ielt9) { return; }
DomUtil.setOpacity(this._container, this.options.opacity);
var now = +new Date(),
nextFrame = false,
willPrune = false;
for (var key in this._tiles) {
var tile = this._tiles[key];
if (!tile.current || !tile.loaded) { continue; }
var fade = Math.min(1, (now - tile.loaded) / 200);
DomUtil.setOpacity(tile.el, fade);
if (fade < 1) {
nextFrame = true;
} else {
if (tile.active) {
willPrune = true;
} else {
this._onOpaqueTile(tile);
}
tile.active = true;
}
}
if (willPrune && !this._noPrune) { this._pruneTiles(); }
if (nextFrame) {
Util.cancelAnimFrame(this._fadeFrame);
this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this);
}
},
_onOpaqueTile: Util.falseFn,
_initContainer: function () {
if (this._container) { return; }
this._container = DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
this._updateZIndex();
if (this.options.opacity < 1) {
this._updateOpacity();
}
this.getPane().appendChild(this._container);
},
_updateLevels: function () {
var zoom = this._tileZoom,
maxZoom = this.options.maxZoom;
if (zoom === undefined) { return undefined; }
for (var z in this._levels) {
z = Number(z);
if (this._levels[z].el.children.length || z === zoom) {
this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
this._onUpdateLevel(z);
} else {
DomUtil.remove(this._levels[z].el);
this._removeTilesAtZoom(z);
this._onRemoveLevel(z);
delete this._levels[z];
}
}
var level = this._levels[zoom],
map = this._map;
if (!level) {
level = this._levels[zoom] = {};
level.el = DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
level.el.style.zIndex = maxZoom;
level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
level.zoom = zoom;
this._setZoomTransform(level, map.getCenter(), map.getZoom());
// force the browser to consider the newly added element for transition
Util.falseFn(level.el.offsetWidth);
this._onCreateLevel(level);
}
this._level = level;
return level;
},
_onUpdateLevel: Util.falseFn,
_onRemoveLevel: Util.falseFn,
_onCreateLevel: Util.falseFn,
_pruneTiles: function () {
if (!this._map) {
return;
}
var key, tile;
var zoom = this._map.getZoom();
if (zoom > this.options.maxZoom ||
zoom < this.options.minZoom) {
this._removeAllTiles();
return;
}
for (key in this._tiles) {
tile = this._tiles[key];
tile.retain = tile.current;
}
for (key in this._tiles) {
tile = this._tiles[key];
if (tile.current && !tile.active) {
var coords = tile.coords;
if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
}
}
}
for (key in this._tiles) {
if (!this._tiles[key].retain) {
this._removeTile(key);
}
}
},
_removeTilesAtZoom: function (zoom) {
for (var key in this._tiles) {
if (this._tiles[key].coords.z !== zoom) {
continue;
}
this._removeTile(key);
}
},
_removeAllTiles: function () {
for (var key in this._tiles) {
this._removeTile(key);
}
},
_invalidateAll: function () {
for (var z in this._levels) {
DomUtil.remove(this._levels[z].el);
this._onRemoveLevel(Number(z));
delete this._levels[z];
}
this._removeAllTiles();
this._tileZoom = undefined;
},
_retainParent: function (x, y, z, minZoom) {
var x2 = Math.floor(x / 2),
y2 = Math.floor(y / 2),
z2 = z - 1,
coords2 = new Point(+x2, +y2);
coords2.z = +z2;
var key = this._tileCoordsToKey(coords2),
tile = this._tiles[key];
if (tile && tile.active) {
tile.retain = true;
return true;
} else if (tile && tile.loaded) {
tile.retain = true;
}
if (z2 > minZoom) {
return this._retainParent(x2, y2, z2, minZoom);
}
return false;
},
_retainChildren: function (x, y, z, maxZoom) {
for (var i = 2 * x; i < 2 * x + 2; i++) {
for (var j = 2 * y; j < 2 * y + 2; j++) {
var coords = new Point(i, j);
coords.z = z + 1;
var key = this._tileCoordsToKey(coords),
tile = this._tiles[key];
if (tile && tile.active) {
tile.retain = true;
continue;
} else if (tile && tile.loaded) {
tile.retain = true;
}
if (z + 1 < maxZoom) {
this._retainChildren(i, j, z + 1, maxZoom);
}
}
}
},
_resetView: function (e) {
var animating = e && (e.pinch || e.flyTo);
this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
},
_animateZoom: function (e) {
this._setView(e.center, e.zoom, true, e.noUpdate);
},
_clampZoom: function (zoom) {
var options = this.options;
if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
return options.minNativeZoom;
}
if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
return options.maxNativeZoom;
}
return zoom;
},
_setView: function (center, zoom, noPrune, noUpdate) {
var tileZoom = Math.round(zoom);
if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
(this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
tileZoom = undefined;
} else {
tileZoom = this._clampZoom(tileZoom);
}
var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
if (!noUpdate || tileZoomChanged) {
this._tileZoom = tileZoom;
if (this._abortLoading) {
this._abortLoading();
}
this._updateLevels();
this._resetGrid();
if (tileZoom !== undefined) {
this._update(center);
}
if (!noPrune) {
this._pruneTiles();
}
// Flag to prevent _updateOpacity from pruning tiles during
// a zoom anim or a pinch gesture
this._noPrune = !!noPrune;
}
this._setZoomTransforms(center, zoom);
},
_setZoomTransforms: function (center, zoom) {
for (var i in this._levels) {
this._setZoomTransform(this._levels[i], center, zoom);
}
},
_setZoomTransform: function (level, center, zoom) {
var scale = this._map.getZoomScale(zoom, level.zoom),
translate = level.origin.multiplyBy(scale)
.subtract(this._map._getNewPixelOrigin(center, zoom)).round();
if (Browser.any3d) {
DomUtil.setTransform(level.el, translate, scale);
} else {
DomUtil.setPosition(level.el, translate);
}
},
_resetGrid: function () {
var map = this._map,
crs = map.options.crs,
tileSize = this._tileSize = this.getTileSize(),
tileZoom = this._tileZoom;
var bounds = this._map.getPixelWorldBounds(this._tileZoom);
if (bounds) {
this._globalTileRange = this._pxBoundsToTileRange(bounds);
}
this._wrapX = crs.wrapLng && !this.options.noWrap && [
Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
];
this._wrapY = crs.wrapLat && !this.options.noWrap && [
Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
];
},
_onMoveEnd: function () {
if (!this._map || this._map._animatingZoom) { return; }
this._update();
},
_getTiledPixelBounds: function (center) {
var map = this._map,
mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
scale = map.getZoomScale(mapZoom, this._tileZoom),
pixelCenter = map.project(center, this._tileZoom).floor(),
halfSize = map.getSize().divideBy(scale * 2);
return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
},
// Private method to load tiles in the grid's active zoom level according to map bounds
_update: function (center) {
var map = this._map;
if (!map) { return; }
var zoom = this._clampZoom(map.getZoom());
if (center === undefined) { center = map.getCenter(); }
if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
var pixelBounds = this._getTiledPixelBounds(center),
tileRange = this._pxBoundsToTileRange(pixelBounds),
tileCenter = tileRange.getCenter(),
queue = [],
margin = this.options.keepBuffer,
noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
tileRange.getTopRight().add([margin, -margin]));
// Sanity check: panic if the tile range contains Infinity somewhere.
if (!(isFinite(tileRange.min.x) &&
isFinite(tileRange.min.y) &&
isFinite(tileRange.max.x) &&
isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
for (var key in this._tiles) {
var c = this._tiles[key].coords;
if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
this._tiles[key].current = false;
}
}
// _update just loads more tiles. If the tile zoom level differs too much
// from the map's, let _setView reset levels and prune old tiles.
if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
// create a queue of coordinates to load tiles from
for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
var coords = new Point(i, j);
coords.z = this._tileZoom;
if (!this._isValidTile(coords)) { continue; }
var tile = this._tiles[this._tileCoordsToKey(coords)];
if (tile) {
tile.current = true;
} else {
queue.push(coords);
}
}
}
// sort tile queue to load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
});
if (queue.length !== 0) {
// if it's the first batch of tiles to load
if (!this._loading) {
this._loading = true;
// @event loading: Event
// Fired when the grid layer starts loading tiles.
this.fire('loading');
}
// create DOM fragment to append tiles in one batch
var fragment = document.createDocumentFragment();
for (i = 0; i < queue.length; i++) {
this._addTile(queue[i], fragment);
}
this._level.el.appendChild(fragment);
}
},
_isValidTile: function (coords) {
var crs = this._map.options.crs;
if (!crs.infinite) {
// don't load tile if it's out of bounds and not wrapped
var bounds = this._globalTileRange;
if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
(!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
}
if (!this.options.bounds) { return true; }
// don't load tile if it doesn't intersect the bounds in options
var tileBounds = this._tileCoordsToBounds(coords);
return latLngBounds(this.options.bounds).overlaps(tileBounds);
},
_keyToBounds: function (key) {
return this._tileCoordsToBounds(this._keyToTileCoords(key));
},
_tileCoordsToNwSe: function (coords) {
var map = this._map,
tileSize = this.getTileSize(),
nwPoint = coords.scaleBy(tileSize),
sePoint = nwPoint.add(tileSize),
nw = map.unproject(nwPoint, coords.z),
se = map.unproject(sePoint, coords.z);
return [nw, se];
},
// converts tile coordinates to its geographical bounds
_tileCoordsToBounds: function (coords) {
var bp = this._tileCoordsToNwSe(coords),
bounds = new LatLngBounds(bp[0], bp[1]);
if (!this.options.noWrap) {
bounds = this._map.wrapLatLngBounds(bounds);
}
return bounds;
},
// converts tile coordinates to key for the tile cache
_tileCoordsToKey: function (coords) {
return coords.x + ':' + coords.y + ':' + coords.z;
},
// converts tile cache key to coordinates
_keyToTileCoords: function (key) {
var k = key.split(':'),
coords = new Point(+k[0], +k[1]);
coords.z = +k[2];
return coords;
},
_removeTile: function (key) {
var tile = this._tiles[key];
if (!tile) { return; }
DomUtil.remove(tile.el);
delete this._tiles[key];
// @event tileunload: TileEvent
// Fired when a tile is removed (e.g. when a tile goes off the screen).
this.fire('tileunload', {
tile: tile.el,
coords: this._keyToTileCoords(key)
});
},
_initTile: function (tile) {
DomUtil.addClass(tile, 'leaflet-tile');
var tileSize = this.getTileSize();
tile.style.width = tileSize.x + 'px';
tile.style.height = tileSize.y + 'px';
tile.onselectstart = Util.falseFn;
tile.onmousemove = Util.falseFn;
// update opacity on tiles in IE7-8 because of filter inheritance problems
if (Browser.ielt9 && this.options.opacity < 1) {
DomUtil.setOpacity(tile, this.options.opacity);
}
},
_addTile: function (coords, container) {
var tilePos = this._getTilePos(coords),
key = this._tileCoordsToKey(coords);
var tile = this.createTile(this._wrapCoords(coords), Util.bind(this._tileReady, this, coords));
this._initTile(tile);
// if createTile is defined with a second argument ("done" callback),
// we know that tile is async and will be ready later; otherwise
if (this.createTile.length < 2) {
// mark tile as ready, but delay one frame for opacity animation to happen
Util.requestAnimFrame(Util.bind(this._tileReady, this, coords, null, tile));
}
DomUtil.setPosition(tile, tilePos);
// save tile in cache
this._tiles[key] = {
el: tile,
coords: coords,
current: true
};
container.appendChild(tile);
// @event tileloadstart: TileEvent
// Fired when a tile is requested and starts loading.
this.fire('tileloadstart', {
tile: tile,
coords: coords
});
},
_tileReady: function (coords, err, tile) {
if (err) {
// @event tileerror: TileErrorEvent
// Fired when there is an error loading a tile.
this.fire('tileerror', {
error: err,
tile: tile,
coords: coords
});
}
var key = this._tileCoordsToKey(coords);
tile = this._tiles[key];
if (!tile) { return; }
tile.loaded = +new Date();
if (this._map._fadeAnimated) {
DomUtil.setOpacity(tile.el, 0);
Util.cancelAnimFrame(this._fadeFrame);
this._fadeFrame = Util.requestAnimFrame(this._updateOpacity, this);
} else {
tile.active = true;
this._pruneTiles();
}
if (!err) {
DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
// @event tileload: TileEvent
// Fired when a tile loads.
this.fire('tileload', {
tile: tile.el,
coords: coords
});
}
if (this._noTilesToLoad()) {
this._loading = false;
// @event load: Event
// Fired when the grid layer loaded all visible tiles.
this.fire('load');
if (Browser.ielt9 || !this._map._fadeAnimated) {
Util.requestAnimFrame(this._pruneTiles, this);
} else {
// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
// to trigger a pruning.
setTimeout(Util.bind(this._pruneTiles, this), 250);
}
}
},
_getTilePos: function (coords) {
return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
},
_wrapCoords: function (coords) {
var newCoords = new Point(
this._wrapX ? Util.wrapNum(coords.x, this._wrapX) : coords.x,
this._wrapY ? Util.wrapNum(coords.y, this._wrapY) : coords.y);
newCoords.z = coords.z;
return newCoords;
},
_pxBoundsToTileRange: function (bounds) {
var tileSize = this.getTileSize();
return new Bounds(
bounds.min.unscaleBy(tileSize).floor(),
bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
},
_noTilesToLoad: function () {
for (var key in this._tiles) {
if (!this._tiles[key].loaded) { return false; }
}
return true;
}
});
// @factory L.gridLayer(options?: GridLayer options)
// Creates a new instance of GridLayer with the supplied options.
export function gridLayer(options) {
return new GridLayer(options);
}
+137
View File
@@ -0,0 +1,137 @@
import {TileLayer} from './TileLayer';
import {extend, setOptions, getParamString} from '../../core/Util';
import Browser from '../../core/Browser';
import {EPSG4326} from '../../geo/crs/CRS.EPSG4326';
import {toBounds} from '../../geometry/Bounds';
/*
* @class TileLayer.WMS
* @inherits TileLayer
* @aka L.TileLayer.WMS
* Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
*
* @example
*
* ```js
* 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"
* });
* ```
*/
export var TileLayerWMS = TileLayer.extend({
// @section
// @aka TileLayer.WMS options
// If any custom options not documented here are used, they will be sent to the
// WMS server as extra parameters in each request URL. This can be useful for
// [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
defaultWmsParams: {
service: 'WMS',
request: 'GetMap',
// @option layers: String = ''
// **(required)** Comma-separated list of WMS layers to show.
layers: '',
// @option styles: String = ''
// Comma-separated list of WMS styles.
styles: '',
// @option format: String = 'image/jpeg'
// WMS image format (use `'image/png'` for layers with transparency).
format: 'image/jpeg',
// @option transparent: Boolean = false
// If `true`, the WMS service will return images with transparency.
transparent: false,
// @option version: String = '1.1.1'
// Version of the WMS service to use
version: '1.1.1'
},
options: {
// @option crs: CRS = null
// Coordinate Reference System to use for the WMS requests, defaults to
// map CRS. Don't change this if you're not sure what it means.
crs: null,
// @option uppercase: Boolean = false
// If `true`, WMS request parameter keys will be uppercase.
uppercase: false
},
initialize: function (url, options) {
this._url = url;
var wmsParams = extend({}, this.defaultWmsParams);
// all keys that are not TileLayer options go to WMS params
for (var i in options) {
if (!(i in this.options)) {
wmsParams[i] = options[i];
}
}
options = setOptions(this, options);
var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
var tileSize = this.getTileSize();
wmsParams.width = tileSize.x * realRetina;
wmsParams.height = tileSize.y * realRetina;
this.wmsParams = wmsParams;
},
onAdd: function (map) {
this._crs = this.options.crs || map.options.crs;
this._wmsVersion = parseFloat(this.wmsParams.version);
var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
this.wmsParams[projectionKey] = this._crs.code;
TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function (coords) {
var tileBounds = this._tileCoordsToNwSe(coords),
crs = this._crs,
bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
min = bounds.min,
max = bounds.max,
bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
[min.y, min.x, max.y, max.x] :
[min.x, min.y, max.x, max.y]).join(','),
url = TileLayer.prototype.getTileUrl.call(this, coords);
return url +
getParamString(this.wmsParams, url, this.options.uppercase) +
(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
},
// @method setParams(params: Object, noRedraw?: Boolean): this
// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
setParams: function (params, noRedraw) {
extend(this.wmsParams, params);
if (!noRedraw) {
this.redraw();
}
return this;
}
});
// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
export function tileLayerWMS(url, options) {
return new TileLayerWMS(url, options);
}
+289
View File
@@ -0,0 +1,289 @@
import {GridLayer} from './GridLayer';
import 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://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
* ```
*
* @section URL template
* @example
*
* A string of the following form:
*
* ```
* 'https://{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 "&commat;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('https://{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,
// @option referrerPolicy: Boolean|String = false
// Whether the referrerPolicy attribute will be added to the tiles.
// If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
// This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
// (e.g. to validate an API token).
// Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
referrerPolicy: 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 = Math.max(options.minZoom, options.maxZoom - 1);
} else {
options.zoomOffset--;
options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
}
options.minZoom = Math.max(0, options.minZoom);
} else if (!options.zoomReverse) {
// make sure maxZoom is gte minZoom
options.maxZoom = Math.max(options.minZoom, options.maxZoom);
} else {
// make sure minZoom is lte maxZoom
options.minZoom = Math.min(options.maxZoom, options.minZoom);
}
if (typeof options.subdomains === 'string') {
options.subdomains = options.subdomains.split('');
}
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 `<img>` 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;
}
// for this new option we follow the documented behavior
// more closely by only setting the property when string
if (typeof this.options.referrerPolicy === 'string') {
tile.referrerPolicy = this.options.referrerPolicy;
}
// The alt attribute is set to the empty string,
// allowing screen readers to ignore the decorative image tiles.
// https://www.w3.org/WAI/tutorials/images/decorative/
// https://www.w3.org/TR/html-aria/#el-img-empty-alt
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: 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;
var coords = this._tiles[i].coords;
DomUtil.remove(tile);
delete this._tiles[i];
// @event tileabort: TileEvent
// Fired when a tile was loading but is now not wanted.
this.fire('tileabort', {
tile: tile,
coords: coords
});
}
}
}
},
_removeTile: function (key) {
var tile = this._tiles[key];
if (!tile) { return; }
// Cancels any pending http requests associated with the tile
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);
}
+6
View File
@@ -0,0 +1,6 @@
export {GridLayer, gridLayer} from './GridLayer';
import {TileLayer, tileLayer} from './TileLayer';
import {TileLayerWMS, tileLayerWMS} from './TileLayer.WMS';
TileLayer.WMS = TileLayerWMS;
tileLayer.wms = tileLayerWMS;
export {TileLayer, tileLayer};
+492
View File
@@ -0,0 +1,492 @@
import {Renderer} from './Renderer';
import * as DomUtil from '../../dom/DomUtil';
import * as DomEvent from '../../dom/DomEvent';
import Browser from '../../core/Browser';
import * as Util from '../../core/Util';
import {Bounds} from '../../geometry/Bounds';
/*
* @class Canvas
* @inherits Renderer
* @aka L.Canvas
*
* Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
* Inherits `Renderer`.
*
* Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
* available in all web browsers, notably IE8, and overlapping geometries might
* not display properly in some edge cases.
*
* @example
*
* Use Canvas by default for all paths in the map:
*
* ```js
* var map = L.map('map', {
* renderer: L.canvas()
* });
* ```
*
* Use a Canvas renderer with extra padding for specific vector geometries:
*
* ```js
* var map = L.map('map');
* var myRenderer = L.canvas({ padding: 0.5 });
* var line = L.polyline( coordinates, { renderer: myRenderer } );
* var circle = L.circle( center, { renderer: myRenderer } );
* ```
*/
export var Canvas = Renderer.extend({
// @section
// @aka Canvas options
options: {
// @option tolerance: Number = 0
// How much to extend the click tolerance around a path/object on the map.
tolerance: 0
},
getEvents: function () {
var events = Renderer.prototype.getEvents.call(this);
events.viewprereset = this._onViewPreReset;
return events;
},
_onViewPreReset: function () {
// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
this._postponeUpdatePaths = true;
},
onAdd: function () {
Renderer.prototype.onAdd.call(this);
// Redraw vectors since canvas is cleared upon removal,
// in case of removing the renderer itself from the map.
this._draw();
},
_initContainer: function () {
var container = this._container = document.createElement('canvas');
DomEvent.on(container, 'mousemove', this._onMouseMove, this);
DomEvent.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
DomEvent.on(container, 'mouseout', this._handleMouseOut, this);
container['_leaflet_disable_events'] = true;
this._ctx = container.getContext('2d');
},
_destroyContainer: function () {
Util.cancelAnimFrame(this._redrawRequest);
delete this._ctx;
DomUtil.remove(this._container);
DomEvent.off(this._container);
delete this._container;
},
_updatePaths: function () {
if (this._postponeUpdatePaths) { return; }
var layer;
this._redrawBounds = null;
for (var id in this._layers) {
layer = this._layers[id];
layer._update();
}
this._redraw();
},
_update: function () {
if (this._map._animatingZoom && this._bounds) { return; }
Renderer.prototype._update.call(this);
var b = this._bounds,
container = this._container,
size = b.getSize(),
m = Browser.retina ? 2 : 1;
DomUtil.setPosition(container, b.min);
// set canvas size (also clearing it); use double size on retina
container.width = m * size.x;
container.height = m * size.y;
container.style.width = size.x + 'px';
container.style.height = size.y + 'px';
if (Browser.retina) {
this._ctx.scale(2, 2);
}
// translate so we use the same path coordinates after canvas element moves
this._ctx.translate(-b.min.x, -b.min.y);
// Tell paths to redraw themselves
this.fire('update');
},
_reset: function () {
Renderer.prototype._reset.call(this);
if (this._postponeUpdatePaths) {
this._postponeUpdatePaths = false;
this._updatePaths();
}
},
_initPath: function (layer) {
this._updateDashArray(layer);
this._layers[Util.stamp(layer)] = layer;
var order = layer._order = {
layer: layer,
prev: this._drawLast,
next: null
};
if (this._drawLast) { this._drawLast.next = order; }
this._drawLast = order;
this._drawFirst = this._drawFirst || this._drawLast;
},
_addPath: function (layer) {
this._requestRedraw(layer);
},
_removePath: function (layer) {
var order = layer._order;
var next = order.next;
var prev = order.prev;
if (next) {
next.prev = prev;
} else {
this._drawLast = prev;
}
if (prev) {
prev.next = next;
} else {
this._drawFirst = next;
}
delete layer._order;
delete this._layers[Util.stamp(layer)];
this._requestRedraw(layer);
},
_updatePath: function (layer) {
// Redraw the union of the layer's old pixel
// bounds and the new pixel bounds.
this._extendRedrawBounds(layer);
layer._project();
layer._update();
// The redraw will extend the redraw bounds
// with the new pixel bounds.
this._requestRedraw(layer);
},
_updateStyle: function (layer) {
this._updateDashArray(layer);
this._requestRedraw(layer);
},
_updateDashArray: function (layer) {
if (typeof layer.options.dashArray === 'string') {
var parts = layer.options.dashArray.split(/[, ]+/),
dashArray = [],
dashValue,
i;
for (i = 0; i < parts.length; i++) {
dashValue = Number(parts[i]);
// Ignore dash array containing invalid lengths
if (isNaN(dashValue)) { return; }
dashArray.push(dashValue);
}
layer.options._dashArray = dashArray;
} else {
layer.options._dashArray = layer.options.dashArray;
}
},
_requestRedraw: function (layer) {
if (!this._map) { return; }
this._extendRedrawBounds(layer);
this._redrawRequest = this._redrawRequest || Util.requestAnimFrame(this._redraw, this);
},
_extendRedrawBounds: function (layer) {
if (layer._pxBounds) {
var padding = (layer.options.weight || 0) + 1;
this._redrawBounds = this._redrawBounds || new Bounds();
this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
}
},
_redraw: function () {
this._redrawRequest = null;
if (this._redrawBounds) {
this._redrawBounds.min._floor();
this._redrawBounds.max._ceil();
}
this._clear(); // clear layers in redraw bounds
this._draw(); // draw layers
this._redrawBounds = null;
},
_clear: function () {
var bounds = this._redrawBounds;
if (bounds) {
var size = bounds.getSize();
this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
} else {
this._ctx.save();
this._ctx.setTransform(1, 0, 0, 1, 0, 0);
this._ctx.clearRect(0, 0, this._container.width, this._container.height);
this._ctx.restore();
}
},
_draw: function () {
var layer, bounds = this._redrawBounds;
this._ctx.save();
if (bounds) {
var size = bounds.getSize();
this._ctx.beginPath();
this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
this._ctx.clip();
}
this._drawing = true;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
layer._updatePath();
}
}
this._drawing = false;
this._ctx.restore(); // Restore state before clipping.
},
_updatePoly: function (layer, closed) {
if (!this._drawing) { return; }
var i, j, len2, p,
parts = layer._parts,
len = parts.length,
ctx = this._ctx;
if (!len) { return; }
ctx.beginPath();
for (i = 0; i < len; i++) {
for (j = 0, len2 = parts[i].length; j < len2; j++) {
p = parts[i][j];
ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
}
if (closed) {
ctx.closePath();
}
}
this._fillStroke(ctx, layer);
// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
},
_updateCircle: function (layer) {
if (!this._drawing || layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 1),
s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
if (s !== 1) {
ctx.restore();
}
this._fillStroke(ctx, layer);
},
_fillStroke: function (ctx, layer) {
var options = layer.options;
if (options.fill) {
ctx.globalAlpha = options.fillOpacity;
ctx.fillStyle = options.fillColor || options.color;
ctx.fill(options.fillRule || 'evenodd');
}
if (options.stroke && options.weight !== 0) {
if (ctx.setLineDash) {
ctx.setLineDash(layer.options && layer.options._dashArray || []);
}
ctx.globalAlpha = options.opacity;
ctx.lineWidth = options.weight;
ctx.strokeStyle = options.color;
ctx.lineCap = options.lineCap;
ctx.lineJoin = options.lineJoin;
ctx.stroke();
}
},
// Canvas obviously doesn't have mouse events for individual drawn objects,
// so we emulate that by calculating what's under the mouse on mousemove/click manually
_onClick: function (e) {
var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
clickedLayer = layer;
}
}
}
this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
},
_onMouseMove: function (e) {
if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
var point = this._map.mouseEventToLayerPoint(e);
this._handleMouseHover(e, point);
},
_handleMouseOut: function (e) {
var layer = this._hoveredLayer;
if (layer) {
// if we're leaving the layer, fire mouseout
DomUtil.removeClass(this._container, 'leaflet-interactive');
this._fireEvent([layer], e, 'mouseout');
this._hoveredLayer = null;
this._mouseHoverThrottled = false;
}
},
_handleMouseHover: function (e, point) {
if (this._mouseHoverThrottled) {
return;
}
var layer, candidateHoveredLayer;
for (var order = this._drawFirst; order; order = order.next) {
layer = order.layer;
if (layer.options.interactive && layer._containsPoint(point)) {
candidateHoveredLayer = layer;
}
}
if (candidateHoveredLayer !== this._hoveredLayer) {
this._handleMouseOut(e);
if (candidateHoveredLayer) {
DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
this._fireEvent([candidateHoveredLayer], e, 'mouseover');
this._hoveredLayer = candidateHoveredLayer;
}
}
this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
this._mouseHoverThrottled = true;
setTimeout(Util.bind(function () {
this._mouseHoverThrottled = false;
}, this), 32);
},
_fireEvent: function (layers, e, type) {
this._map._fireDOMEvent(e, type || e.type, layers);
},
_bringToFront: function (layer) {
var order = layer._order;
if (!order) { return; }
var next = order.next;
var prev = order.prev;
if (next) {
next.prev = prev;
} else {
// Already last
return;
}
if (prev) {
prev.next = next;
} else if (next) {
// Update first entry unless this is the
// single entry
this._drawFirst = next;
}
order.prev = this._drawLast;
this._drawLast.next = order;
order.next = null;
this._drawLast = order;
this._requestRedraw(layer);
},
_bringToBack: function (layer) {
var order = layer._order;
if (!order) { return; }
var next = order.next;
var prev = order.prev;
if (prev) {
prev.next = next;
} else {
// Already first
return;
}
if (next) {
next.prev = prev;
} else if (prev) {
// Update last entry unless this is the
// single entry
this._drawLast = prev;
}
order.prev = null;
order.next = this._drawFirst;
this._drawFirst.prev = order;
this._drawFirst = order;
this._requestRedraw(layer);
}
});
// @factory L.canvas(options?: Renderer options)
// Creates a Canvas renderer with the given options.
export function canvas(options) {
return Browser.canvas ? new Canvas(options) : null;
}
+113
View File
@@ -0,0 +1,113 @@
import {CircleMarker} from './CircleMarker';
import {Path} from './Path';
import * as Util from '../../core/Util';
import {toLatLng} from '../../geo/LatLng';
import {LatLngBounds} from '../../geo/LatLngBounds';
import {Earth} from '../../geo/crs/CRS.Earth';
/*
* @class Circle
* @aka L.Circle
* @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], {radius: 200}).addTo(map);
* ```
*/
export var Circle = CircleMarker.extend({
initialize: function (latlng, options, legacyOptions) {
if (typeof options === 'number') {
// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
options = Util.extend({}, legacyOptions, {radius: options});
}
Util.setOptions(this, options);
this._latlng = toLatLng(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 LatLngBounds(
this._map.layerPointToLatLng(this._point.subtract(half)),
this._map.layerPointToLatLng(this._point.add(half)));
},
setStyle: Path.prototype.setStyle,
_project: function () {
var lng = this._latlng.lng,
lat = this._latlng.lat,
map = this._map,
crs = map.options.crs;
if (crs.distance === Earth.distance) {
var d = Math.PI / 180,
latR = (this._mRadius / 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 : p.x - map.project([lat2, lng - lngR]).x;
this._radiusY = p.y - top.y;
} 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.
export function circle(latlng, options, legacyOptions) {
return new Circle(latlng, options, legacyOptions);
}
+109
View File
@@ -0,0 +1,109 @@
import {Path} from './Path';
import * as Util from '../../core/Util';
import {toLatLng} from '../../geo/LatLng';
import {Bounds} from '../../geometry/Bounds';
/*
* @class CircleMarker
* @aka L.CircleMarker
* @inherits Path
*
* A circle of a fixed size with radius specified in pixels. Extends `Path`.
*/
export var CircleMarker = Path.extend({
// @section
// @aka CircleMarker options
options: {
fill: true,
// @option radius: Number = 10
// Radius of the circle marker, in pixels
radius: 10
},
initialize: function (latlng, options) {
Util.setOptions(this, options);
this._latlng = toLatLng(latlng);
this._radius = this.options.radius;
},
// @method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function (latlng) {
var oldLatLng = this._latlng;
this._latlng = toLatLng(latlng);
this.redraw();
// @event move: Event
// Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
},
// @method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function () {
return this._latlng;
},
// @method setRadius(radius: Number): this
// Sets the radius of a circle marker. Units are in pixels.
setRadius: function (radius) {
this.options.radius = this._radius = radius;
return this.redraw();
},
// @method getRadius(): Number
// Returns the current radius of the circle
getRadius: function () {
return this._radius;
},
setStyle : function (options) {
var radius = options && options.radius || this._radius;
Path.prototype.setStyle.call(this, options);
this.setRadius(radius);
return this;
},
_project: function () {
this._point = this._map.latLngToLayerPoint(this._latlng);
this._updateBounds();
},
_updateBounds: function () {
var r = this._radius,
r2 = this._radiusY || r,
w = this._clickTolerance(),
p = [r + w, r2 + w];
this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function () {
if (this._map) {
this._updatePath();
}
},
_updatePath: function () {
this._renderer._updateCircle(this);
},
_empty: function () {
return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function (p) {
return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
}
});
// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
// Instantiates a circle marker object given a geographical point, and an optional options object.
export function circleMarker(latlng, options) {
return new CircleMarker(latlng, options);
}
+148
View File
@@ -0,0 +1,148 @@
import {Layer} from '../Layer';
import * as Util from '../../core/Util';
/*
* @class Path
* @aka L.Path
* @inherits Interactive layer
*
* An abstract class that contains options and constants shared between vector
* overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
*/
export var Path = Layer.extend({
// @section
// @aka Path options
options: {
// @option stroke: Boolean = true
// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
stroke: true,
// @option color: String = '#3388ff'
// Stroke color
color: '#3388ff',
// @option weight: Number = 3
// Stroke width in pixels
weight: 3,
// @option opacity: Number = 1.0
// Stroke opacity
opacity: 1,
// @option lineCap: String= 'round'
// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
lineCap: 'round',
// @option lineJoin: String = 'round'
// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
lineJoin: 'round',
// @option dashArray: String = null
// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
dashArray: null,
// @option dashOffset: String = null
// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
dashOffset: null,
// @option fill: Boolean = depends
// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
fill: false,
// @option fillColor: String = *
// Fill color. Defaults to the value of the [`color`](#path-color) option
fillColor: null,
// @option fillOpacity: Number = 0.2
// Fill opacity.
fillOpacity: 0.2,
// @option fillRule: String = 'evenodd'
// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
fillRule: 'evenodd',
// className: '',
// Option inherited from "Interactive layer" abstract class
interactive: true,
// @option bubblingMouseEvents: Boolean = true
// When `true`, a mouse event on this path will trigger the same event on the map
// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
bubblingMouseEvents: true
},
beforeAdd: function (map) {
// Renderer is set here because we need to call renderer.getEvents
// before this.getEvents.
this._renderer = map.getRenderer(this);
},
onAdd: function () {
this._renderer._initPath(this);
this._reset();
this._renderer._addPath(this);
},
onRemove: function () {
this._renderer._removePath(this);
},
// @method redraw(): this
// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
redraw: function () {
if (this._map) {
this._renderer._updatePath(this);
}
return this;
},
// @method setStyle(style: Path options): this
// Changes the appearance of a Path based on the options in the `Path options` object.
setStyle: function (style) {
Util.setOptions(this, style);
if (this._renderer) {
this._renderer._updateStyle(this);
if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
this._updateBounds();
}
}
return this;
},
// @method bringToFront(): this
// Brings the layer to the top of all path layers.
bringToFront: function () {
if (this._renderer) {
this._renderer._bringToFront(this);
}
return this;
},
// @method bringToBack(): this
// Brings the layer to the bottom of all path layers.
bringToBack: function () {
if (this._renderer) {
this._renderer._bringToBack(this);
}
return this;
},
getElement: function () {
return this._path;
},
_reset: function () {
// defined in child classes
this._project();
this._update();
},
_clickTolerance: function () {
// used when doing hit detection for Canvas layers
return (this.options.stroke ? this.options.weight / 2 : 0) +
(this._renderer.options.tolerance || 0);
}
});
+159
View File
@@ -0,0 +1,159 @@
import {Polyline} from './Polyline';
import {LatLng} from '../../geo/LatLng';
import * as LineUtil from '../../geometry/LineUtil';
import {Point} from '../../geometry/Point';
import {Bounds} from '../../geometry/Bounds';
import * as PolyUtil from '../../geometry/PolyUtil';
/*
* @class Polygon
* @aka L.Polygon
* @inherits Polyline
*
* A class for drawing polygon overlays on a map. Extends `Polyline`.
*
* 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.
*
*
* @example
*
* ```js
* // create a red polygon from an array of LatLng points
* var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
*
* 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:
*
* ```js
* var latlngs = [
* [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
* [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ];
* ```
*
* Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
*
* ```js
* var latlngs = [
* [ // first polygon
* [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
* [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
* ],
* [ // second polygon
* [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
* ]
* ];
* ```
*/
export var Polygon = Polyline.extend({
options: {
fill: true
},
isEmpty: function () {
return !this._latlngs.length || !this._latlngs[0].length;
},
// @method getCenter(): LatLng
// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
getCenter: function () {
// throws error when not yet added to map as this center calculation requires projected coordinates
if (!this._map) {
throw new Error('Must add layer to map before using getCenter()');
}
return PolyUtil.polygonCenter(this._defaultShape(), this._map.options.crs);
},
_convertLatLngs: function (latlngs) {
var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
len = result.length;
// remove last point if it equals first one
if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
result.pop();
}
return result;
},
_setLatLngs: function (latlngs) {
Polyline.prototype._setLatLngs.call(this, latlngs);
if (LineUtil.isFlat(this._latlngs)) {
this._latlngs = [this._latlngs];
}
},
_defaultShape: function () {
return LineUtil.isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
},
_clipPoints: function () {
// polygons need a different clipping algorithm so we redefine that
var bounds = this._renderer._bounds,
w = this.options.weight,
p = new Point(w, w);
// increase clip padding by stroke width to avoid stroke on clip edges
bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
return;
}
if (this.options.noClip) {
this._parts = this._rings;
return;
}
for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
clipped = PolyUtil.clipPolygon(this._rings[i], bounds, true);
if (clipped.length) {
this._parts.push(clipped);
}
}
},
_updatePath: function () {
this._renderer._updatePoly(this, true);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function (p) {
var inside = false,
part, p1, p2, i, j, k, len, len2;
if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
// ray casting algorithm for detecting if point is in polygon
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
p1 = part[j];
p2 = part[k];
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
inside = !inside;
}
}
}
// also check if it's on polygon stroke
return inside || Polyline.prototype._containsPoint.call(this, p, true);
}
});
// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
export function polygon(latlngs, options) {
return new Polygon(latlngs, options);
}
+307
View File
@@ -0,0 +1,307 @@
import {Path} from './Path';
import * as Util from '../../core/Util';
import * as LineUtil from '../../geometry/LineUtil';
import {LatLng, toLatLng} from '../../geo/LatLng';
import {LatLngBounds} from '../../geo/LatLngBounds';
import {Bounds} from '../../geometry/Bounds';
import {Point} from '../../geometry/Point';
/*
* @class Polyline
* @aka L.Polyline
* @inherits Path
*
* A class for drawing polyline overlays on a map. Extends `Path`.
*
* @example
*
* ```js
* // create a red polyline from an array of LatLng points
* var latlngs = [
* [45.51, -122.68],
* [37.77, -122.43],
* [34.04, -118.2]
* ];
*
* 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:
*
* ```js
* // create a red polyline from an array of arrays of LatLng points
* var latlngs = [
* [[45.51, -122.68],
* [37.77, -122.43],
* [34.04, -118.2]],
* [[40.78, -73.91],
* [41.83, -87.62],
* [32.76, -96.72]]
* ];
* ```
*/
export var Polyline = Path.extend({
// @section
// @aka Polyline options
options: {
// @option smoothFactor: Number = 1.0
// How much to simplify the polyline on each zoom level. More means
// better performance and smoother look, and less means more accurate representation.
smoothFactor: 1.0,
// @option noClip: Boolean = false
// Disable polyline clipping.
noClip: false
},
initialize: function (latlngs, options) {
Util.setOptions(this, options);
this._setLatLngs(latlngs);
},
// @method getLatLngs(): LatLng[]
// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
getLatLngs: function () {
return this._latlngs;
},
// @method setLatLngs(latlngs: LatLng[]): this
// Replaces all the points in the polyline with the given array of geographical points.
setLatLngs: function (latlngs) {
this._setLatLngs(latlngs);
return this.redraw();
},
// @method isEmpty(): Boolean
// Returns `true` if the Polyline has no LatLngs.
isEmpty: function () {
return !this._latlngs.length;
},
// @method closestLayerPoint(p: Point): Point
// Returns the point closest to `p` on the Polyline.
closestLayerPoint: function (p) {
var minDistance = Infinity,
minPoint = null,
closest = LineUtil._sqClosestPointOnSegment,
p1, p2;
for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
var points = this._parts[j];
for (var i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
var sqDist = closest(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = closest(p, p1, p2);
}
}
}
if (minPoint) {
minPoint.distance = Math.sqrt(minDistance);
}
return minPoint;
},
// @method getCenter(): LatLng
// Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
getCenter: function () {
// throws error when not yet added to map as this center calculation requires projected coordinates
if (!this._map) {
throw new Error('Must add layer to map before using getCenter()');
}
return LineUtil.polylineCenter(this._defaultShape(), this._map.options.crs);
},
// @method getBounds(): LatLngBounds
// Returns the `LatLngBounds` of the path.
getBounds: function () {
return this._bounds;
},
// @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
// Adds a given point to the polyline. By default, adds to the first ring of
// the polyline in case of a multi-polyline, but can be overridden by passing
// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
addLatLng: function (latlng, latlngs) {
latlngs = latlngs || this._defaultShape();
latlng = toLatLng(latlng);
latlngs.push(latlng);
this._bounds.extend(latlng);
return this.redraw();
},
_setLatLngs: function (latlngs) {
this._bounds = new LatLngBounds();
this._latlngs = this._convertLatLngs(latlngs);
},
_defaultShape: function () {
return LineUtil.isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
},
// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
_convertLatLngs: function (latlngs) {
var result = [],
flat = LineUtil.isFlat(latlngs);
for (var i = 0, len = latlngs.length; i < len; i++) {
if (flat) {
result[i] = toLatLng(latlngs[i]);
this._bounds.extend(result[i]);
} else {
result[i] = this._convertLatLngs(latlngs[i]);
}
}
return result;
},
_project: function () {
var pxBounds = new Bounds();
this._rings = [];
this._projectLatlngs(this._latlngs, this._rings, pxBounds);
if (this._bounds.isValid() && pxBounds.isValid()) {
this._rawPxBounds = pxBounds;
this._updateBounds();
}
},
_updateBounds: function () {
var w = this._clickTolerance(),
p = new Point(w, w);
if (!this._rawPxBounds) {
return;
}
this._pxBounds = new Bounds([
this._rawPxBounds.min.subtract(p),
this._rawPxBounds.max.add(p)
]);
},
// recursively turns latlngs into a set of rings with projected coordinates
_projectLatlngs: function (latlngs, result, projectedBounds) {
var flat = latlngs[0] instanceof LatLng,
len = latlngs.length,
i, ring;
if (flat) {
ring = [];
for (i = 0; i < len; i++) {
ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
projectedBounds.extend(ring[i]);
}
result.push(ring);
} else {
for (i = 0; i < len; i++) {
this._projectLatlngs(latlngs[i], result, projectedBounds);
}
}
},
// clip polyline by renderer bounds so that we have less to render for performance
_clipPoints: function () {
var bounds = this._renderer._bounds;
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
return;
}
if (this.options.noClip) {
this._parts = this._rings;
return;
}
var parts = this._parts,
i, j, k, len, len2, segment, points;
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
points = this._rings[i];
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
segment = LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
if (!segment) { continue; }
parts[k] = parts[k] || [];
parts[k].push(segment[0]);
// if segment goes out of screen, or it's the last one, it's the end of the line part
if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
parts[k].push(segment[1]);
k++;
}
}
}
},
// simplify each clipped part of the polyline for performance
_simplifyPoints: function () {
var parts = this._parts,
tolerance = this.options.smoothFactor;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = LineUtil.simplify(parts[i], tolerance);
}
},
_update: function () {
if (!this._map) { return; }
this._clipPoints();
this._simplifyPoints();
this._updatePath();
},
_updatePath: function () {
this._renderer._updatePoly(this);
},
// Needed by the `Canvas` renderer for interactivity
_containsPoint: function (p, closed) {
var i, j, k, len, len2, part,
w = this._clickTolerance();
if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
// hit detection for polylines
for (i = 0, len = this._parts.length; i < len; i++) {
part = this._parts[i];
for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
if (!closed && (j === 0)) { continue; }
if (LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
return true;
}
}
}
return false;
}
});
// @factory L.polyline(latlngs: LatLng[], options?: Polyline 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.
export function polyline(latlngs, options) {
return new Polyline(latlngs, options);
}
// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
Polyline._flat = LineUtil._flat;
+57
View File
@@ -0,0 +1,57 @@
import {Polygon} from './Polygon';
import {toLatLngBounds} from '../../geo/LatLngBounds';
/*
* L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
*/
/*
* @class Rectangle
* @aka L.Rectangle
* @inherits Polygon
*
* A class for drawing rectangle overlays on a map. Extends `Polygon`.
*
* @example
*
* ```js
* // 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);
* ```
*
*/
export var Rectangle = Polygon.extend({
initialize: function (latLngBounds, options) {
Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
},
// @method setBounds(latLngBounds: LatLngBounds): this
// Redraws the rectangle with the passed bounds.
setBounds: function (latLngBounds) {
return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
},
_boundsToLatLngs: function (latLngBounds) {
latLngBounds = toLatLngBounds(latLngBounds);
return [
latLngBounds.getSouthWest(),
latLngBounds.getNorthWest(),
latLngBounds.getNorthEast(),
latLngBounds.getSouthEast()
];
}
});
// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
export function rectangle(latLngBounds, options) {
return new Rectangle(latLngBounds, options);
}
@@ -0,0 +1,45 @@
import {Map} from '../../map/Map';
import {canvas} from './Canvas';
import {svg} from './SVG';
Map.include({
// @namespace Map; @method getRenderer(layer: Path): Renderer
// Returns the instance of `Renderer` that should be used to render the given
// `Path`. It will ensure that the `renderer` options of the map and paths
// are respected, and that the renderers do exist on the map.
getRenderer: function (layer) {
// @namespace Path; @option renderer: Renderer
// Use this specific instance of `Renderer` for this path. Takes
// precedence over the map's [default renderer](#map-renderer).
var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
if (!renderer) {
renderer = this._renderer = this._createRenderer();
}
if (!this.hasLayer(renderer)) {
this.addLayer(renderer);
}
return renderer;
},
_getPaneRenderer: function (name) {
if (name === 'overlayPane' || name === undefined) {
return false;
}
var renderer = this._paneRenderers[name];
if (renderer === undefined) {
renderer = this._createRenderer({pane: name});
this._paneRenderers[name] = renderer;
}
return renderer;
},
_createRenderer: function (options) {
// @namespace Map; @option preferCanvas: Boolean = false
// Whether `Path`s should be rendered on a `Canvas` renderer.
// By default, all `Path`s are rendered in a `SVG` renderer.
return (this.options.preferCanvas && canvas(options)) || svg(options);
}
});
+133
View File
@@ -0,0 +1,133 @@
import {Layer} from '../Layer';
import * as DomUtil from '../../dom/DomUtil';
import * as Util from '../../core/Util';
import Browser from '../../core/Browser';
import {Bounds} from '../../geometry/Bounds';
/*
* @class Renderer
* @inherits Layer
* @aka L.Renderer
*
* Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
* DOM container of the renderer, its bounds, and its zoom animation.
*
* A `Renderer` works as an implicit layer group for all `Path`s - the renderer
* itself can be added or removed to the map. All paths use a renderer, which can
* be implicit (the map will decide the type of renderer and use it automatically)
* or explicit (using the [`renderer`](#path-renderer) option of the path).
*
* Do not use this class directly, use `SVG` and `Canvas` instead.
*
* @event update: Event
* Fired when the renderer updates its bounds, center and zoom, for example when
* its map has moved
*/
export var Renderer = Layer.extend({
// @section
// @aka Renderer options
options: {
// @option padding: Number = 0.1
// How much to extend the clip area around the map view (relative to its size)
// e.g. 0.1 would be 10% of map view in each direction
padding: 0.1
},
initialize: function (options) {
Util.setOptions(this, options);
Util.stamp(this);
this._layers = this._layers || {};
},
onAdd: function () {
if (!this._container) {
this._initContainer(); // defined by renderer implementations
// always keep transform-origin as 0 0
DomUtil.addClass(this._container, 'leaflet-zoom-animated');
}
this.getPane().appendChild(this._container);
this._update();
this.on('update', this._updatePaths, this);
},
onRemove: function () {
this.off('update', this._updatePaths, this);
this._destroyContainer();
},
getEvents: function () {
var events = {
viewreset: this._reset,
zoom: this._onZoom,
moveend: this._update,
zoomend: this._onZoomEnd
};
if (this._zoomAnimated) {
events.zoomanim = this._onAnimZoom;
}
return events;
},
_onAnimZoom: function (ev) {
this._updateTransform(ev.center, ev.zoom);
},
_onZoom: function () {
this._updateTransform(this._map.getCenter(), this._map.getZoom());
},
_updateTransform: function (center, zoom) {
var scale = this._map.getZoomScale(zoom, this._zoom),
viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
currentCenterPoint = this._map.project(this._center, zoom),
topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
.subtract(this._map._getNewPixelOrigin(center, zoom));
if (Browser.any3d) {
DomUtil.setTransform(this._container, topLeftOffset, scale);
} else {
DomUtil.setPosition(this._container, topLeftOffset);
}
},
_reset: function () {
this._update();
this._updateTransform(this._center, this._zoom);
for (var id in this._layers) {
this._layers[id]._reset();
}
},
_onZoomEnd: function () {
for (var id in this._layers) {
this._layers[id]._project();
}
},
_updatePaths: function () {
for (var id in this._layers) {
this._layers[id]._update();
}
},
_update: function () {
// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
// Subclasses are responsible of firing the 'update' event.
var p = this.options.padding,
size = this._map.getSize(),
min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
this._center = this._map.getCenter();
this._zoom = this._map.getZoom();
}
});
+39
View File
@@ -0,0 +1,39 @@
import Browser from '../../core/Browser';
// @namespace SVG; @section
// There are several static functions which can be called without instantiating L.SVG:
// @function create(name: String): SVGElement
// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
// corresponding to the class name passed. For example, using 'line' will return
// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
export function svgCreate(name) {
return document.createElementNS('http://www.w3.org/2000/svg', name);
}
// @function pointsToPath(rings: Point[], closed: Boolean): String
// Generates a SVG path string for multiple rings, with each ring turning
// into "M..L..L.." instructions
export function pointsToPath(rings, closed) {
var str = '',
i, j, len, len2, points, p;
for (i = 0, len = rings.length; i < len; i++) {
points = rings[i];
for (j = 0, len2 = points.length; j < len2; j++) {
p = points[j];
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
// closes the ring for polygons; "x" is VML syntax
str += closed ? (Browser.svg ? 'z' : 'x') : '';
}
// SVG complains about empty path strings
return str || 'M0 0';
}
+144
View File
@@ -0,0 +1,144 @@
import * as DomUtil from '../../dom/DomUtil';
import * as Util from '../../core/Util';
import {Renderer} from './Renderer';
/*
* Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
*/
export var vmlCreate = (function () {
try {
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
return function (name) {
return document.createElement('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
// Do not return fn from catch block so `e` can be garbage collected
// See https://github.com/Leaflet/Leaflet/pull/7279
}
return function (name) {
return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
})();
/*
* @class SVG
*
*
* VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
* with old versions of Internet Explorer.
*/
// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
export var vmlMixin = {
_initContainer: function () {
this._container = DomUtil.create('div', 'leaflet-vml-container');
},
_update: function () {
if (this._map._animatingZoom) { return; }
Renderer.prototype._update.call(this);
this.fire('update');
},
_initPath: function (layer) {
var container = layer._container = vmlCreate('shape');
DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
container.coordsize = '1 1';
layer._path = vmlCreate('path');
container.appendChild(layer._path);
this._updateStyle(layer);
this._layers[Util.stamp(layer)] = layer;
},
_addPath: function (layer) {
var container = layer._container;
this._container.appendChild(container);
if (layer.options.interactive) {
layer.addInteractiveTarget(container);
}
},
_removePath: function (layer) {
var container = layer._container;
DomUtil.remove(container);
layer.removeInteractiveTarget(container);
delete this._layers[Util.stamp(layer)];
},
_updateStyle: function (layer) {
var stroke = layer._stroke,
fill = layer._fill,
options = layer.options,
container = layer._container;
container.stroked = !!options.stroke;
container.filled = !!options.fill;
if (options.stroke) {
if (!stroke) {
stroke = layer._stroke = vmlCreate('stroke');
}
container.appendChild(stroke);
stroke.weight = options.weight + 'px';
stroke.color = options.color;
stroke.opacity = options.opacity;
if (options.dashArray) {
stroke.dashStyle = Util.isArray(options.dashArray) ?
options.dashArray.join(' ') :
options.dashArray.replace(/( *, *)/g, ' ');
} else {
stroke.dashStyle = '';
}
stroke.endcap = options.lineCap.replace('butt', 'flat');
stroke.joinstyle = options.lineJoin;
} else if (stroke) {
container.removeChild(stroke);
layer._stroke = null;
}
if (options.fill) {
if (!fill) {
fill = layer._fill = vmlCreate('fill');
}
container.appendChild(fill);
fill.color = options.fillColor || options.color;
fill.opacity = options.fillOpacity;
} else if (fill) {
container.removeChild(fill);
layer._fill = null;
}
},
_updateCircle: function (layer) {
var p = layer._point.round(),
r = Math.round(layer._radius),
r2 = Math.round(layer._radiusY || r);
this._setPath(layer, layer._empty() ? 'M0 0' :
'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
},
_setPath: function (layer, path) {
layer._path.v = path;
},
_bringToFront: function (layer) {
DomUtil.toFront(layer._container);
},
_bringToBack: function (layer) {
DomUtil.toBack(layer._container);
}
};
+207
View File
@@ -0,0 +1,207 @@
import {Renderer} from './Renderer';
import * as DomUtil from '../../dom/DomUtil';
import * as DomEvent from '../../dom/DomEvent';
import Browser from '../../core/Browser';
import {stamp} from '../../core/Util';
import {svgCreate, pointsToPath} from './SVG.Util';
export {pointsToPath};
import {vmlMixin, vmlCreate} from './SVG.VML';
export var create = Browser.vml ? vmlCreate : svgCreate;
/*
* @class SVG
* @inherits Renderer
* @aka L.SVG
*
* Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
* Inherits `Renderer`.
*
* Due to [technical limitations](https://caniuse.com/svg), SVG is not
* available in all web browsers, notably Android 2.x and 3.x.
*
* Although SVG is not available on IE7 and IE8, these browsers support
* [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
* (a now deprecated technology), and the SVG renderer will fall back to VML in
* this case.
*
* @example
*
* Use SVG by default for all paths in the map:
*
* ```js
* var map = L.map('map', {
* renderer: L.svg()
* });
* ```
*
* Use a SVG renderer with extra padding for specific vector geometries:
*
* ```js
* var map = L.map('map');
* var myRenderer = L.svg({ padding: 0.5 });
* var line = L.polyline( coordinates, { renderer: myRenderer } );
* var circle = L.circle( center, { renderer: myRenderer } );
* ```
*/
export var SVG = Renderer.extend({
_initContainer: function () {
this._container = create('svg');
// makes it possible to click through svg root; we'll reset it back in individual paths
this._container.setAttribute('pointer-events', 'none');
this._rootGroup = create('g');
this._container.appendChild(this._rootGroup);
},
_destroyContainer: function () {
DomUtil.remove(this._container);
DomEvent.off(this._container);
delete this._container;
delete this._rootGroup;
delete this._svgSize;
},
_update: function () {
if (this._map._animatingZoom && this._bounds) { return; }
Renderer.prototype._update.call(this);
var b = this._bounds,
size = b.getSize(),
container = this._container;
// set size of svg-container if changed
if (!this._svgSize || !this._svgSize.equals(size)) {
this._svgSize = size;
container.setAttribute('width', size.x);
container.setAttribute('height', size.y);
}
// movement: update container viewBox so that we don't have to change coordinates of individual layers
DomUtil.setPosition(container, b.min);
container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
this.fire('update');
},
// methods below are called by vector layers implementations
_initPath: function (layer) {
var path = layer._path = create('path');
// @namespace Path
// @option className: String = null
// Custom class name set on an element. Only for SVG renderer.
if (layer.options.className) {
DomUtil.addClass(path, layer.options.className);
}
if (layer.options.interactive) {
DomUtil.addClass(path, 'leaflet-interactive');
}
this._updateStyle(layer);
this._layers[stamp(layer)] = layer;
},
_addPath: function (layer) {
if (!this._rootGroup) { this._initContainer(); }
this._rootGroup.appendChild(layer._path);
layer.addInteractiveTarget(layer._path);
},
_removePath: function (layer) {
DomUtil.remove(layer._path);
layer.removeInteractiveTarget(layer._path);
delete this._layers[stamp(layer)];
},
_updatePath: function (layer) {
layer._project();
layer._update();
},
_updateStyle: function (layer) {
var path = layer._path,
options = layer.options;
if (!path) { return; }
if (options.stroke) {
path.setAttribute('stroke', options.color);
path.setAttribute('stroke-opacity', options.opacity);
path.setAttribute('stroke-width', options.weight);
path.setAttribute('stroke-linecap', options.lineCap);
path.setAttribute('stroke-linejoin', options.lineJoin);
if (options.dashArray) {
path.setAttribute('stroke-dasharray', options.dashArray);
} else {
path.removeAttribute('stroke-dasharray');
}
if (options.dashOffset) {
path.setAttribute('stroke-dashoffset', options.dashOffset);
} else {
path.removeAttribute('stroke-dashoffset');
}
} else {
path.setAttribute('stroke', 'none');
}
if (options.fill) {
path.setAttribute('fill', options.fillColor || options.color);
path.setAttribute('fill-opacity', options.fillOpacity);
path.setAttribute('fill-rule', options.fillRule || 'evenodd');
} else {
path.setAttribute('fill', 'none');
}
},
_updatePoly: function (layer, closed) {
this._setPath(layer, pointsToPath(layer._parts, closed));
},
_updateCircle: function (layer) {
var p = layer._point,
r = Math.max(Math.round(layer._radius), 1),
r2 = Math.max(Math.round(layer._radiusY), 1) || r,
arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
// drawing a circle with two half-arcs
var d = layer._empty() ? 'M0 0' :
'M' + (p.x - r) + ',' + p.y +
arc + (r * 2) + ',0 ' +
arc + (-r * 2) + ',0 ';
this._setPath(layer, d);
},
_setPath: function (layer, path) {
layer._path.setAttribute('d', path);
},
// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
_bringToFront: function (layer) {
DomUtil.toFront(layer._path);
},
_bringToBack: function (layer) {
DomUtil.toBack(layer._path);
}
});
if (Browser.vml) {
SVG.include(vmlMixin);
}
// @namespace SVG
// @factory L.svg(options?: Renderer options)
// Creates a SVG renderer with the given options.
export function svg(options) {
return Browser.svg || Browser.vml ? new SVG(options) : null;
}
+14
View File
@@ -0,0 +1,14 @@
export {Renderer} from './Renderer';
export {Canvas, canvas} from './Canvas';
import {SVG, create, pointsToPath, svg} from './SVG';
SVG.create = create;
SVG.pointsToPath = pointsToPath;
export {SVG, svg};
import './Renderer.getRenderer'; // This is a bit of a hack, but needed because circular dependencies
export {Path} from './Path';
export {CircleMarker, circleMarker} from './CircleMarker';
export {Circle, circle} from './Circle';
export {Polyline, polyline} from './Polyline';
export {Polygon, polygon} from './Polygon';
export {Rectangle, rectangle} from './Rectangle';
+1751
View File
@@ -0,0 +1,1751 @@
import * as Util from '../core/Util';
import {Evented} from '../core/Events';
import {EPSG3857} from '../geo/crs/CRS.EPSG3857';
import {Point, toPoint} from '../geometry/Point';
import {Bounds, toBounds} from '../geometry/Bounds';
import {LatLng, toLatLng} from '../geo/LatLng';
import {LatLngBounds, toLatLngBounds} from '../geo/LatLngBounds';
import Browser from '../core/Browser';
import * as DomEvent from '../dom/DomEvent';
import * as DomUtil from '../dom/DomUtil';
import {PosAnimation} from '../dom/PosAnimation';
/*
* @class Map
* @aka L.Map
* @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
* });
* ```
*
*/
export var Map = Evented.extend({
options: {
// @section Map State Options
// @option crs: CRS = L.CRS.EPSG3857
// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
// sure what it means.
crs: 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 = *
// Minimum zoom level of the map.
// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
// the lowest of their `minZoom` options will be used instead.
minZoom: undefined,
// @option maxZoom: Number = *
// Maximum zoom level of the map.
// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
// the highest of their `maxZoom` options will be used instead.
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 if the user 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 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,
// @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
},
initialize: function (id, options) { // (HTMLElement or String, Object)
options = Util.setOptions(this, options);
// Make sure to assign internal flags at the beginning,
// to avoid inconsistent state in some edge cases.
this._handlers = [];
this._layers = {};
this._zoomBoundLayers = {};
this._sizeChanged = true;
this._initContainer(id);
this._initLayout();
// hack for https://github.com/Leaflet/Leaflet/issues/1980
this._onResize = Util.bind(this._onResize, this);
this._initEvents();
if (options.maxBounds) {
this.setMaxBounds(options.maxBounds);
}
if (options.zoom !== undefined) {
this._zoom = this._limitZoom(options.zoom);
}
if (options.center && options.zoom !== undefined) {
this.setView(toLatLng(options.center), options.zoom, {reset: true});
}
this.callInitHooks();
// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
this._zoomAnimated = DomUtil.TRANSITION && Browser.any3d && !Browser.mobileOpera &&
this.options.zoomAnimation;
// zoom transitions run with the same duration for all layers, so if one of transitionend events
// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
if (this._zoomAnimated) {
this._createAnimProxy();
DomEvent.on(this._proxy, DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
}
this._addLayers(this.options.layers);
},
// @section Methods for modifying 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.
setView: function (center, zoom, options) {
zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
options = options || {};
this._stop();
if (this._loaded && !options.reset && options !== true) {
if (options.animate !== undefined) {
options.zoom = Util.extend({animate: options.animate}, options.zoom);
options.pan = Util.extend({animate: options.animate, duration: options.duration}, options.pan);
}
// try animating pan or zoom
var moved = (this._zoom !== zoom) ?
this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
this._tryAnimatedPan(center, options.pan);
if (moved) {
// prevent resize handler call, the view will refresh after animation anyway
clearTimeout(this._sizeTimer);
return this;
}
}
// animation didn't start, just reset the map view
this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
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;
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 || (Browser.any3d ? this.options.zoomDelta : 1);
return this.setZoom(this._zoom + delta, options);
},
// @method zoomOut(delta?: Number, options?: Zoom options): this
// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
zoomOut: function (delta, options) {
delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
return this.setZoom(this._zoom - delta, 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),
containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
return this.setView(newCenter, zoom, {zoom: options});
},
_getBoundsCenterZoom: function (bounds, options) {
options = options || {};
bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
if (zoom === Infinity) {
return {
center: bounds.getCenter(),
zoom: zoom
};
}
var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
swPoint = this.project(bounds.getSouthWest(), zoom),
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 = toLatLngBounds(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);
},
// @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, options?: Pan options): this
// Pans the map by a given number of pixels (animated).
panBy: function (offset, options) {
offset = toPoint(offset).round();
options = options || {};
if (!offset.x && !offset.y) {
return this.fire('moveend');
}
// If we pan too far, Chrome gets issues with tiles
// and makes them disappear or appear in the wrong place (slightly offset) #2602
if (options.animate !== true && !this.getSize().contains(offset)) {
this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
return this;
}
if (!this._panAnim) {
this._panAnim = new PosAnimation();
this._panAnim.on({
'step': this._onPanTransitionStep,
'end': this._onPanTransitionEnd
}, this);
}
// don't fire movestart if animating inertia
if (!options.noMoveStart) {
this.fire('movestart');
}
// animate pan unless animate: false specified
if (options.animate !== false) {
DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
var newPos = this._getMapPanePos().subtract(offset).round();
this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
} else {
this._rawPanBy(offset);
this.fire('move').fire('moveend');
}
return this;
},
// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
// Sets the view of the map (geographical center and zoom) performing a smooth
// pan-zoom animation.
flyTo: function (targetCenter, targetZoom, options) {
options = options || {};
if (options.animate === false || !Browser.any3d) {
return this.setView(targetCenter, targetZoom, options);
}
this._stop();
var from = this.project(this.getCenter()),
to = this.project(targetCenter),
size = this.getSize(),
startZoom = this._zoom;
targetCenter = toLatLng(targetCenter);
targetZoom = targetZoom === undefined ? startZoom : targetZoom;
var w0 = Math.max(size.x, size.y),
w1 = w0 * this.getZoomScale(startZoom, targetZoom),
u1 = (to.distanceTo(from)) || 1,
rho = 1.42,
rho2 = rho * rho;
function r(i) {
var s1 = i ? -1 : 1,
s2 = i ? w1 : w0,
t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
b1 = 2 * s2 * rho2 * u1,
b = t1 / b1,
sq = Math.sqrt(b * b + 1) - b;
// workaround for floating point precision bug when sq = 0, log = -Infinite,
// thus triggering an infinite loop in flyTo
var log = sq < 0.000000001 ? -18 : Math.log(sq);
return log;
}
function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
function tanh(n) { return sinh(n) / cosh(n); }
var r0 = r(0);
function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
var start = Date.now(),
S = (r(1) - r0) / rho,
duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
function frame() {
var t = (Date.now() - start) / duration,
s = easeOut(t) * S;
if (t <= 1) {
this._flyToFrame = Util.requestAnimFrame(frame, this);
this._move(
this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
this.getScaleZoom(w0 / w(s), startZoom),
{flyTo: true});
} else {
this
._move(targetCenter, targetZoom)
._moveEnd(true);
}
}
this._moveStart(true, options.noMoveStart);
frame.call(this);
return this;
},
// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
flyToBounds: function (bounds, options) {
var target = this._getBoundsCenterZoom(bounds, options);
return this.flyTo(target.center, target.zoom, options);
},
// @method setMaxBounds(bounds: LatLngBounds): this
// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
setMaxBounds: function (bounds) {
bounds = toLatLngBounds(bounds);
if (this.listens('moveend', this._panInsideMaxBounds)) {
this.off('moveend', this._panInsideMaxBounds);
}
if (!bounds.isValid()) {
this.options.maxBounds = null;
return this;
}
this.options.maxBounds = bounds;
if (this._loaded) {
this._panInsideMaxBounds();
}
return this.on('moveend', this._panInsideMaxBounds);
},
// @method setMinZoom(zoom: Number): this
// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
setMinZoom: function (zoom) {
var oldZoom = this.options.minZoom;
this.options.minZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire('zoomlevelschange');
if (this.getZoom() < this.options.minZoom) {
return this.setZoom(zoom);
}
}
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) {
var oldZoom = this.options.maxZoom;
this.options.maxZoom = zoom;
if (this._loaded && oldZoom !== zoom) {
this.fire('zoomlevelschange');
if (this.getZoom() > this.options.maxZoom) {
return this.setZoom(zoom);
}
}
return this;
},
// @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, toLatLngBounds(bounds));
if (!center.equals(newCenter)) {
this.panTo(newCenter, options);
}
this._enforcingBounds = false;
return this;
},
// @method panInside(latlng: LatLng, options?: padding options): this
// Pans the map the minimum amount to make the `latlng` visible. Use
// padding options to fit the display to more restricted bounds.
// If `latlng` is already within the (optionally padded) display bounds,
// the map will not be panned.
panInside: function (latlng, options) {
options = options || {};
var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
pixelCenter = this.project(this.getCenter()),
pixelPoint = this.project(latlng),
pixelBounds = this.getPixelBounds(),
paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
paddedSize = paddedBounds.getSize();
if (!paddedBounds.contains(pixelPoint)) {
this._enforcingBounds = true;
var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
this.panTo(this.unproject(pixelCenter), options);
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.
// @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; }
options = Util.extend({
animate: false,
pan: true
}, options === true ? {animate: true} : options);
var oldSize = this.getSize();
this._sizeChanged = true;
this._lastCenter = null;
var newSize = this.getSize(),
oldCenter = oldSize.divideBy(2).round(),
newCenter = newSize.divideBy(2).round(),
offset = oldCenter.subtract(newCenter);
if (!offset.x && !offset.y) { return this; }
if (options.animate && options.pan) {
this.panBy(offset);
} else {
if (options.pan) {
this._rawPanBy(offset);
}
this.fire('move');
if (options.debounceMoveend) {
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(Util.bind(this.fire, this, 'moveend'), 200);
} else {
this.fire('moveend');
}
}
// @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();
},
// @section Geolocation methods
// @method locate(options?: Locate options): this
// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
// event with location data on success or a [`locationerror`](#map-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).
// Note that, if your page doesn't use HTTPS, this method will fail in
// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
// See `Locate options` for more details.
locate: function (options) {
options = this._locateOptions = Util.extend({
timeout: 10000,
watch: false
// setView: false
// maxZoom: <Number>
// maximumAge: 0
// enableHighAccuracy: false
}, options);
if (!('geolocation' in navigator)) {
this._handleGeolocationError({
code: 0,
message: 'Geolocation not supported.'
});
return this;
}
var onResponse = Util.bind(this._handleGeolocationResponse, this),
onError = Util.bind(this._handleGeolocationError, this);
if (options.watch) {
this._locationWatchId =
navigator.geolocation.watchPosition(onResponse, onError, options);
} else {
navigator.geolocation.getCurrentPosition(onResponse, onError, options);
}
return this;
},
// @method stopLocate(): this
// Stops watching location previously initiated by `map.locate({watch: true})`
// and aborts resetting the map view if map.locate was called with
// `{setView: true}`.
stopLocate: function () {
if (navigator.geolocation && navigator.geolocation.clearWatch) {
navigator.geolocation.clearWatch(this._locationWatchId);
}
if (this._locateOptions) {
this._locateOptions.setView = false;
}
return this;
},
_handleGeolocationError: function (error) {
if (!this._container._leaflet_id) { return; }
var c = error.code,
message = error.message ||
(c === 1 ? 'permission denied' :
(c === 2 ? 'position unavailable' : 'timeout'));
if (this._locateOptions.setView && !this._loaded) {
this.fitWorld();
}
// @section Location events
// @event locationerror: ErrorEvent
// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
this.fire('locationerror', {
code: c,
message: 'Geolocation error: ' + message + '.'
});
},
_handleGeolocationResponse: function (pos) {
if (!this._container._leaflet_id) { return; }
var lat = pos.coords.latitude,
lng = pos.coords.longitude,
latlng = new LatLng(lat, lng),
bounds = latlng.toBounds(pos.coords.accuracy * 2),
options = this._locateOptions;
if (options.setView) {
var zoom = this.getBoundsZoom(bounds);
this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
}
var data = {
latlng: latlng,
bounds: bounds,
timestamp: pos.timestamp
};
for (var i in pos.coords) {
if (typeof pos.coords[i] === 'number') {
data[i] = pos.coords[i];
}
}
// @event locationfound: LocationEvent
// Fired when geolocation (using the [`locate`](#map-locate) method)
// went successfully.
this.fire('locationfound', data);
},
// TODO Appropriate 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; }
var handler = this[name] = new HandlerClass(this);
this._handlers.push(handler);
if (this.options[name]) {
handler.enable();
}
return this;
},
// @method remove(): this
// Destroys the map and clears all related event listeners.
remove: function () {
this._initEvents(true);
if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
if (this._containerId !== this._container._leaflet_id) {
throw new Error('Map container is being reused by another instance');
}
try {
// throws error in IE6-8
delete this._container._leaflet_id;
delete this._containerId;
} catch (e) {
/*eslint-disable */
this._container._leaflet_id = undefined;
/* eslint-enable */
this._containerId = undefined;
}
if (this._locationWatchId !== undefined) {
this.stopLocate();
}
this._stop();
DomUtil.remove(this._mapPane);
if (this._clearControlPos) {
this._clearControlPos();
}
if (this._resizeRequest) {
Util.cancelAnimFrame(this._resizeRequest);
this._resizeRequest = null;
}
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');
}
var i;
for (i in this._layers) {
this._layers[i].remove();
}
for (i in this._panes) {
DomUtil.remove(this._panes[i]);
}
this._layers = [];
this._panes = [];
delete this._mapPane;
delete this._renderer;
return this;
},
// @section Other Methods
// @method createPane(name: String, container?: HTMLElement): HTMLElement
// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
// then returns it. The pane is created as a child of `container`, or
// as a child of the main map pane if not set.
createPane: function (name, container) {
var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
pane = DomUtil.create('div', className, container || this._mapPane);
if (name) {
this._panes[name] = pane;
}
return pane;
},
// @section Methods for Getting Map State
// @method getCenter(): LatLng
// Returns the geographical center of the map view
getCenter: function () {
this._checkIfLoaded();
if (this._lastCenter && !this._moved()) {
return this._lastCenter.clone();
}
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()),
ne = this.unproject(bounds.getTopRight());
return new 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;
},
// @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, padding?: Point): 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 = toLatLngBounds(bounds);
padding = toPoint(padding || [0, 0]);
var zoom = this.getZoom() || 0,
min = this.getMinZoom(),
max = this.getMaxZoom(),
nw = bounds.getNorthWest(),
se = bounds.getSouthEast(),
size = this.getSize().subtract(padding),
boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
snap = Browser.any3d ? this.options.zoomSnap : 1,
scalex = size.x / boundsSize.x,
scaley = size.y / boundsSize.y,
scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
zoom = this.getScaleZoom(scale, zoom);
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;
}
return Math.max(min, Math.min(max, zoom));
},
// @method getSize(): Point
// Returns the current size of the map container (in pixels).
getSize: function () {
if (!this._size || this._sizeChanged) {
this._size = new Point(
this._container.clientWidth || 0,
this._container.clientHeight || 0);
this._sizeChanged = false;
}
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);
return new 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](#map-pane), given its name or its HTML element (its identity).
getPane: function (pane) {
return typeof pane === 'string' ? this._panes[pane] : pane;
},
// @method getPanes(): Object
// Returns a plain object containing the names of all [panes](#map-pane) 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
// @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
var crs = this.options.crs;
fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
return crs.scale(toZoom) / crs.scale(fromZoom);
},
// @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;
var zoom = crs.zoom(scale * crs.scale(fromZoom));
return isNaN(zoom) ? Infinity : zoom;
},
// @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) {
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
},
// @method unproject(point: Point, zoom: Number): LatLng
// Inverse of [`project`](#map-project).
unproject: function (point, zoom) {
zoom = zoom === undefined ? this._zoom : zoom;
return this.options.crs.pointToLatLng(toPoint(point), zoom);
},
// @method layerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding geographical coordinate (for the current zoom level).
layerPointToLatLng: function (point) {
var projectedPoint = toPoint(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) {
var projectedPoint = this.project(toLatLng(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(toLatLng(latlng));
},
// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
// Returns a `LatLngBounds` with the same size as the given one, ensuring that
// its center is within the CRS's bounds.
// By default this means the center longitude is wrapped around the dateline so its
// value is between -180 and +180 degrees, and the majority of the bounds
// overlaps the CRS's bounds.
wrapLatLngBounds: function (latlng) {
return this.options.crs.wrapLatLngBounds(toLatLngBounds(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(toLatLng(latlng1), toLatLng(latlng2));
},
// @method containerPointToLayerPoint(point: Point): Point
// Given a pixel coordinate relative to the map container, returns the corresponding
// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
containerPointToLayerPoint: function (point) { // (Point)
return toPoint(point).subtract(this._getMapPanePos());
},
// @method layerPointToContainerPoint(point: Point): Point
// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
// returns the corresponding pixel coordinate relative to the map container.
layerPointToContainerPoint: function (point) { // (Point)
return toPoint(point).add(this._getMapPanePos());
},
// @method containerPointToLatLng(point: Point): LatLng
// Given a pixel coordinate relative to the map container, returns
// the corresponding geographical coordinate (for the current zoom level).
containerPointToLatLng: function (point) {
var layerPoint = this.containerPointToLayerPoint(toPoint(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(toLatLng(latlng)));
},
// @method mouseEventToContainerPoint(ev: MouseEvent): Point
// Given a MouseEvent object, returns the pixel coordinate relative to the
// map container where the event took place.
mouseEventToContainerPoint: function (e) {
return 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) {
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));
},
// map initialization methods
_initContainer: function (id) {
var container = this._container = DomUtil.get(id);
if (!container) {
throw new Error('Map container not found.');
} else if (container._leaflet_id) {
throw new Error('Map container is already initialized.');
}
DomEvent.on(container, 'scroll', this._onScroll, this);
this._containerId = Util.stamp(container);
},
_initLayout: function () {
var container = this._container;
this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
DomUtil.addClass(container, 'leaflet-container' +
(Browser.touch ? ' leaflet-touch' : '') +
(Browser.retina ? ' leaflet-retina' : '') +
(Browser.ielt9 ? ' leaflet-oldie' : '') +
(Browser.safari ? ' leaflet-safari' : '') +
(this._fadeAnimated ? ' leaflet-fade-anim' : ''));
var position = DomUtil.getStyle(container, 'position');
if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
container.style.position = 'relative';
}
this._initPanes();
if (this._initControlPos) {
this._initControlPos();
}
},
_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);
DomUtil.setPosition(this._mapPane, new Point(0, 0));
// @pane tilePane: HTMLElement = 200
// Pane for `GridLayer`s and `TileLayer`s
this.createPane('tilePane');
// @pane overlayPane: HTMLElement = 400
// Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
this.createPane('overlayPane');
// @pane shadowPane: HTMLElement = 500
// Pane for overlay shadows (e.g. `Marker` shadows)
this.createPane('shadowPane');
// @pane markerPane: HTMLElement = 600
// Pane for `Icon`s of `Marker`s
this.createPane('markerPane');
// @pane tooltipPane: HTMLElement = 650
// Pane for `Tooltip`s.
this.createPane('tooltipPane');
// @pane popupPane: HTMLElement = 700
// Pane for `Popup`s.
this.createPane('popupPane');
if (!this.options.markerZoomAnimation) {
DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
}
},
// private methods that modify map state
// @section Map state change events
_resetView: function (center, zoom, noMoveStart) {
DomUtil.setPosition(this._mapPane, new Point(0, 0));
var loading = !this._loaded;
this._loaded = true;
zoom = this._limitZoom(zoom);
this.fire('viewprereset');
var zoomChanged = this._zoom !== zoom;
this
._moveStart(zoomChanged, noMoveStart)
._move(center, zoom)
._moveEnd(zoomChanged);
// @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');
// @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');
}
},
_moveStart: function (zoomChanged, noMoveStart) {
// @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');
}
if (!noMoveStart) {
this.fire('movestart');
}
return this;
},
_move: function (center, zoom, data, supressEvent) {
if (zoom === undefined) {
zoom = this._zoom;
}
var zoomChanged = this._zoom !== zoom;
this._zoom = zoom;
this._lastCenter = center;
this._pixelOrigin = this._getNewPixelOrigin(center);
if (!supressEvent) {
// @event zoom: Event
// Fired repeatedly 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);
}
// @event move: Event
// Fired repeatedly during any movement of the map,
// including pan and fly animations.
this.fire('move', data);
} else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
this.fire('zoom', data);
}
return this;
},
_moveEnd: function (zoomChanged) {
// @event zoomend: Event
// Fired when the map zoom changed, after any animations.
if (zoomChanged) {
this.fire('zoomend');
}
// @event moveend: Event
// Fired when the center of the map stops changing
// (e.g. user stopped dragging the map or after non-centered zoom).
return this.fire('moveend');
},
_stop: function () {
Util.cancelAnimFrame(this._flyToFrame);
if (this._panAnim) {
this._panAnim.stop();
}
return this;
},
_rawPanBy: function (offset) {
DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
},
_getZoomSpan: function () {
return this.getMaxZoom() - this.getMinZoom();
},
_panInsideMaxBounds: function () {
if (!this._enforcingBounds) {
this.panInsideBounds(this.options.maxBounds);
}
},
_checkIfLoaded: function () {
if (!this._loaded) {
throw new Error('Set map center and zoom first.');
}
},
// DOM event handling
// @section Interaction events
_initEvents: function (remove) {
this._targets = {};
this._targets[Util.stamp(this._container)] = this;
var onOff = remove ? DomEvent.off : DomEvent.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: KeyboardEvent
// Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
// @event keydown: KeyboardEvent
// Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
// the `keydown` event is fired for keys that produce a character value and for keys
// that do not produce a character value.
// @event keyup: KeyboardEvent
// Fired when the user releases a key from the keyboard while the map is focused.
onOff(this._container, 'click dblclick mousedown mouseup ' +
'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
if (this.options.trackResize) {
onOff(window, 'resize', this._onResize, this);
}
if (Browser.any3d && this.options.transform3DLimit) {
(remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
}
},
_onResize: function () {
Util.cancelAnimFrame(this._resizeRequest);
this._resizeRequest = Util.requestAnimFrame(
function () { this.invalidateSize({debounceMoveend: true}); }, this);
},
_onScroll: function () {
this._container.scrollTop = 0;
this._container.scrollLeft = 0;
},
_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: https://jsfiddle.net/dg6r5hhb/
this._resetView(this.getCenter(), this.getZoom());
}
},
_findEventTargets: function (e, type) {
var targets = [],
target,
isHover = type === 'mouseout' || type === 'mouseover',
src = e.target || e.srcElement,
dragging = false;
while (src) {
target = this._targets[Util.stamp(src)];
if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
// Prevent firing click after you just dragged an object.
dragging = true;
break;
}
if (target && target.listens(type, true)) {
if (isHover && !DomEvent.isExternalTarget(src, e)) { break; }
targets.push(target);
if (isHover) { break; }
}
if (src === this._container) { break; }
src = src.parentNode;
}
if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
targets = [this];
}
return targets;
},
_isClickDisabled: function (el) {
while (el && el !== this._container) {
if (el['_leaflet_disable_click']) { return true; }
el = el.parentNode;
}
},
_handleDOMEvent: function (e) {
var el = (e.target || e.srcElement);
if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
return;
}
var type = e.type;
if (type === 'mousedown') {
// prevents outline when clicking on keyboard-focusable element
DomUtil.preventOutline(el);
}
this._fireDOMEvent(e, type);
},
_mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
_fireDOMEvent: function (e, type, canvasTargets) {
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 = Util.extend({}, e);
synth.type = 'preclick';
this._fireDOMEvent(synth, synth.type, canvasTargets);
}
// Find the layer the event is propagating from and its parents.
var targets = this._findEventTargets(e, type);
if (canvasTargets) {
var filtered = []; // pick only targets with listeners
for (var i = 0; i < canvasTargets.length; i++) {
if (canvasTargets[i].listens(type, true)) {
filtered.push(canvasTargets[i]);
}
}
targets = filtered.concat(targets);
}
if (!targets.length) { return; }
if (type === 'contextmenu') {
DomEvent.preventDefault(e);
}
var target = targets[0];
var data = {
originalEvent: e
};
if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
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);
}
for (i = 0; i < targets.length; i++) {
targets[i].fire(type, data, true);
if (data.originalEvent._stopped ||
(targets[i].options.bubblingMouseEvents === false && Util.indexOf(this._mouseEvents, type) !== -1)) { return; }
}
},
_draggableMoved: function (obj) {
obj = obj.dragging && obj.dragging.enabled() ? obj : this;
return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
},
_clearHandlers: function () {
for (var i = 0, len = this._handlers.length; i < len; i++) {
this._handlers[i].disable();
}
},
// @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});
} else {
this.on('load', callback, context);
}
return this;
},
// private methods for getting map state
_getMapPanePos: function () {
return DomUtil.getPosition(this._mapPane) || new Point(0, 0);
},
_moved: function () {
var pos = this._getMapPanePos();
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());
},
_getNewPixelOrigin: function (center, zoom) {
var viewHalf = this.getSize()._divideBy(2);
return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
},
_latLngToNewLayerPoint: function (latlng, zoom, center) {
var topLeft = this._getNewPixelOrigin(center, zoom);
return this.project(latlng, zoom)._subtract(topLeft);
},
_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
var topLeft = this._getNewPixelOrigin(center, zoom);
return toBounds([
this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
]);
},
// layer point of the current center
_getCenterLayerPoint: function () {
return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
},
// offset of the specified place to the current center in pixels
_getCenterOffset: function (latlng) {
return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
},
// adjust center for view to get inside bounds
_limitCenter: function (center, zoom, bounds) {
if (!bounds) { return center; }
var centerPoint = this.project(center, zoom),
viewHalf = this.getSize().divideBy(2),
viewBounds = new 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 (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
return center;
}
return this.unproject(centerPoint.add(offset), zoom);
},
// adjust offset for view to get inside bounds
_limitOffset: function (offset, bounds) {
if (!bounds) { return offset; }
var viewBounds = this.getPixelBounds(),
newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
return offset.add(this._getBoundsOffset(newBounds, bounds));
},
// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
var projectedMaxBounds = toBounds(
this.project(maxBounds.getNorthEast(), zoom),
this.project(maxBounds.getSouthWest(), zoom)
),
minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
dx = this._rebound(minOffset.x, -maxOffset.x),
dy = this._rebound(minOffset.y, -maxOffset.y);
return new Point(dx, dy);
},
_rebound: function (left, right) {
return left + right > 0 ?
Math.round(left - right) / 2 :
Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
},
_limitZoom: function (zoom) {
var min = this.getMinZoom(),
max = this.getMaxZoom(),
snap = Browser.any3d ? this.options.zoomSnap : 1;
if (snap) {
zoom = Math.round(zoom / snap) * snap;
}
return Math.max(min, Math.min(max, zoom));
},
_onPanTransitionStep: function () {
this.fire('move');
},
_onPanTransitionEnd: function () {
DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
this.fire('moveend');
},
_tryAnimatedPan: function (center, options) {
// difference between the new and current centers in pixels
var offset = this._getCenterOffset(center)._trunc();
// don't animate too far unless animate: true specified in options
if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
this.panBy(offset, options);
return true;
},
_createAnimProxy: function () {
var proxy = this._proxy = DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
this._panes.mapPane.appendChild(proxy);
this.on('zoomanim', function (e) {
var prop = DomUtil.TRANSFORM,
transform = this._proxy.style[prop];
DomUtil.setTransform(this._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 === this._proxy.style[prop] && this._animatingZoom) {
this._onZoomTransitionEnd();
}
}, this);
this.on('load moveend', this._animMoveEnd, this);
this._on('unload', this._destroyAnimProxy, this);
},
_destroyAnimProxy: function () {
DomUtil.remove(this._proxy);
this.off('load moveend', this._animMoveEnd, this);
delete this._proxy;
},
_animMoveEnd: function () {
var c = this.getCenter(),
z = this.getZoom();
DomUtil.setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
},
_catchTransitionEnd: function (e) {
if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
this._onZoomTransitionEnd();
}
},
_nothingToAnimate: function () {
return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
},
_tryAnimatedZoom: function (center, zoom, options) {
if (this._animatingZoom) { return true; }
options = options || {};
// don't animate if disabled, not supported or zoom difference is too large
if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
// 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);
// 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; }
Util.requestAnimFrame(function () {
this
._moveStart(true, options.noMoveStart || false)
._animateZoom(center, zoom, true);
}, this);
return true;
},
_animateZoom: function (center, zoom, startAnim, noUpdate) {
if (!this._mapPane) { return; }
if (startAnim) {
this._animatingZoom = true;
// remember what center/zoom to set after animation
this._animateToCenter = center;
this._animateToZoom = zoom;
DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
}
// @section Other Events
// @event zoomanim: ZoomAnimEvent
// Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
this.fire('zoomanim', {
center: center,
zoom: zoom,
noUpdate: noUpdate
});
if (!this._tempFireZoomEvent) {
this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
}
this._move(this._animateToCenter, this._animateToZoom, undefined, true);
// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
setTimeout(Util.bind(this._onZoomTransitionEnd, this), 250);
},
_onZoomTransitionEnd: function () {
if (!this._animatingZoom) { return; }
if (this._mapPane) {
DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
}
this._animatingZoom = false;
this._move(this._animateToCenter, this._animateToZoom, undefined, true);
if (this._tempFireZoomEvent) {
this.fire('zoom');
}
delete this._tempFireZoomEvent;
this.fire('move');
this._moveEnd(true);
}
});
// @section
// @factory L.map(id: String, options?: Map options)
// Instantiates a map object given the DOM ID of a `<div>` 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 `<div>` HTML element
// and optionally an object literal with `Map options`.
export function createMap(id, options) {
return new Map(id, options);
}
+112
View File
@@ -0,0 +1,112 @@
This file documents the common options passed to several map methods.
@miniclass Locate options (Map)
@aka locate options
@section
Some of the geolocation methods for `Map` take in an `options` parameter. This
is a plain javascript object with the following optional components:
@option watch: Boolean = false
If `true`, starts continuous watching of location changes (instead of detecting it
once) using W3C `watchPosition` method. You can later stop watching using
`map.stopLocate()` method.
@option setView: Boolean = false
If `true`, automatically sets the map view to the user location with respect to
detection accuracy, or to world view if geolocation failed.
@option maxZoom: Number = Infinity
The maximum zoom for automatic view setting when using `setView` option.
@option timeout: Number = 10000
Number of milliseconds to wait for a response from geolocation before firing a
`locationerror` event.
@option maximumAge: Number = 0
Maximum age of detected location. If less than this amount of milliseconds
passed since last geolocation response, `locate` will return a cached location.
@option enableHighAccuracy: Boolean = false
Enables high accuracy, see [description in the W3C spec](https://w3c.github.io/geolocation-api/#enablehighaccuracy-member).
@miniclass Zoom options (Map)
@aka zoom options
@section
Some of the `Map` methods which modify the zoom level take in an `options`
parameter. This is a plain javascript object with the following optional
components:
@option animate: Boolean
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.
@miniclass Pan options (Map)
@aka pan options
@section
Some of the `Map` methods which modify the center of the map take in an `options`
parameter. This is a plain javascript object with the following optional
components:
@option animate: Boolean
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).
@option duration: Number = 0.25
Duration of animated panning, in seconds.
@option easeLinearity: Number = 0.25
The curvature factor of panning animation easing (third parameter of the
[Cubic Bezier curve](https://cubic-bezier.com/)). 1.0 means linear animation,
and the smaller this number, the more bowed the curve.
@option noMoveStart: Boolean = false
If `true`, panning won't fire `movestart` event on start (used internally for
panning inertia).
@miniclass Zoom/pan options (Map)
@aka zoom/pan options
@inherits Zoom options
@inherits Pan options
@miniclass Padding options (Map)
@aka padding options
@option paddingTopLeft: Point = [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.
@option paddingBottomRight: Point = [0, 0]
The same for the bottom right corner of the map.
@option padding: Point = [0, 0]
Equivalent of setting both top left and bottom right padding to the same value.
@miniclass FitBounds options (Map)
@aka fitBounds options
@inherits Zoom/pan options
@inherits Padding options
@option maxZoom: Number = null
The maximum possible zoom to use.
+152
View File
@@ -0,0 +1,152 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import * as DomEvent from '../../dom/DomEvent';
import {LatLngBounds} from '../../geo/LatLngBounds';
import {Bounds} from '../../geometry/Bounds';
/*
* L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
* (zoom to a selected bounding box), enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option boxZoom: Boolean = true
// Whether the map can be zoomed to a rectangular area specified by
// dragging the mouse while pressing the shift key.
boxZoom: true
});
export var BoxZoom = Handler.extend({
initialize: function (map) {
this._map = map;
this._container = map._container;
this._pane = map._panes.overlayPane;
this._resetStateTimeout = 0;
map.on('unload', this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e) {
if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e);
DomEvent.on(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseMove: function (e) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
this._map.fire('boxzoomstart');
}
this._point = this._map.mouseEventToContainerPoint(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
DomEvent.off(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
_onMouseUp: function (e) {
if ((e.which !== 1) && (e.button !== 1)) { return; }
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
this._clearDeferredResetState();
this._resetStateTimeout = setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
this._map
.fitBounds(bounds)
.fire('boxzoomend', {boxZoomBounds: bounds});
},
_onKeyDown: function (e) {
if (e.keyCode === 27) {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
}
});
// @section Handlers
// @property boxZoom: Handler
// Box (shift-drag with mouse) zoom handler.
Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
@@ -0,0 +1,55 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
/*
* L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option doubleClickZoom: Boolean|String = true
// Whether the map can be zoomed in by double clicking on it and
// zoomed out by double clicking while holding shift. If passed
// `'center'`, double-click zoom will zoom to the center of the
// view regardless of where the mouse was.
doubleClickZoom: true
});
export var DoubleClickZoom = Handler.extend({
addHooks: function () {
this._map.on('dblclick', this._onDoubleClick, this);
},
removeHooks: function () {
this._map.off('dblclick', this._onDoubleClick, this);
},
_onDoubleClick: function (e) {
var map = this._map,
oldZoom = map.getZoom(),
delta = map.options.zoomDelta,
zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
if (map.options.doubleClickZoom === 'center') {
map.setZoom(zoom);
} else {
map.setZoomAround(e.containerPoint, zoom);
}
}
});
// @section Handlers
//
// 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 `Handler` methods). For example:
//
// ```js
// map.doubleClickZoom.disable();
// ```
//
// @property doubleClickZoom: Handler
// Double click zoom handler.
Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
+235
View File
@@ -0,0 +1,235 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import {Draggable} from '../../dom/Draggable';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import {toLatLngBounds as latLngBounds} from '../../geo/LatLngBounds';
import {toBounds} from '../../geometry/Bounds';
/*
* L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @option dragging: Boolean = true
// Whether the map is draggable with mouse/touch or not.
dragging: true,
// @section Panning Inertia Options
// @option inertia: Boolean = *
// If 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. Enabled by default.
inertia: true,
// @option inertiaDeceleration: Number = 3000
// The rate with which the inertial movement slows down, in pixels/second².
inertiaDeceleration: 3400, // px/s^2
// @option inertiaMaxSpeed: Number = Infinity
// Max speed of the inertial movement, in pixels/second.
inertiaMaxSpeed: Infinity, // px/s
// @option easeLinearity: Number = 0.2
easeLinearity: 0.2,
// TODO refactor, move to CRS
// @option worldCopyJump: Boolean = false
// With 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.
worldCopyJump: false,
// @option maxBoundsViscosity: Number = 0.0
// If `maxBounds` is set, this option will control how solid the bounds
// are when dragging the map around. The default value of `0.0` allows the
// user to drag outside the bounds at normal speed, higher values will
// slow down map dragging outside bounds, and `1.0` makes the bounds fully
// solid, preventing the user from dragging outside the bounds.
maxBoundsViscosity: 0.0
});
export var Drag = Handler.extend({
addHooks: function () {
if (!this._draggable) {
var map = this._map;
this._draggable = new Draggable(map._mapPane, map._container);
this._draggable.on({
dragstart: this._onDragStart,
drag: this._onDrag,
dragend: this._onDragEnd
}, this);
this._draggable.on('predrag', this._onPreDragLimit, this);
if (map.options.worldCopyJump) {
this._draggable.on('predrag', this._onPreDragWrap, this);
map.on('zoomend', this._onZoomEnd, this);
map.whenReady(this._onZoomEnd, this);
}
}
DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
this._draggable.enable();
this._positions = [];
this._times = [];
},
removeHooks: function () {
DomUtil.removeClass(this._map._container, 'leaflet-grab');
DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
this._draggable.disable();
},
moved: function () {
return this._draggable && this._draggable._moved;
},
moving: function () {
return this._draggable && this._draggable._moving;
},
_onDragStart: function () {
var map = this._map;
map._stop();
if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
var bounds = latLngBounds(this._map.options.maxBounds);
this._offsetLimit = toBounds(
this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
.add(this._map.getSize()));
this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
} else {
this._offsetLimit = null;
}
map
.fire('movestart')
.fire('dragstart');
if (map.options.inertia) {
this._positions = [];
this._times = [];
}
},
_onDrag: function (e) {
if (this._map.options.inertia) {
var time = this._lastTime = +new Date(),
pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
this._positions.push(pos);
this._times.push(time);
this._prunePositions(time);
}
this._map
.fire('move', e)
.fire('drag', e);
},
_prunePositions: function (time) {
while (this._positions.length > 1 && time - this._times[0] > 50) {
this._positions.shift();
this._times.shift();
}
},
_onZoomEnd: function () {
var pxCenter = this._map.getSize().divideBy(2),
pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
},
_viscousLimit: function (value, threshold) {
return value - (value - threshold) * this._viscosity;
},
_onPreDragLimit: function () {
if (!this._viscosity || !this._offsetLimit) { return; }
var offset = this._draggable._newPos.subtract(this._draggable._startPos);
var limit = this._offsetLimit;
if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
this._draggable._newPos = this._draggable._startPos.add(offset);
},
_onPreDragWrap: function () {
// TODO refactor to be able to adjust map pane position after zoom
var worldWidth = this._worldWidth,
halfWidth = Math.round(worldWidth / 2),
dx = this._initialWorldOffset,
x = this._draggable._newPos.x,
newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
this._draggable._absPos = this._draggable._newPos.clone();
this._draggable._newPos.x = newX;
},
_onDragEnd: function (e) {
var map = this._map,
options = map.options,
noInertia = !options.inertia || e.noInertia || this._times.length < 2;
map.fire('dragend', e);
if (noInertia) {
map.fire('moveend');
} else {
this._prunePositions(+new Date());
var direction = this._lastPos.subtract(this._positions[0]),
duration = (this._lastTime - this._times[0]) / 1000,
ease = options.easeLinearity,
speedVector = direction.multiplyBy(ease / duration),
speed = speedVector.distanceTo([0, 0]),
limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
if (!offset.x && !offset.y) {
map.fire('moveend');
} else {
offset = map._limitOffset(offset, map.options.maxBounds);
Util.requestAnimFrame(function () {
map.panBy(offset, {
duration: decelerationDuration,
easeLinearity: ease,
noMoveStart: true,
animate: true
});
});
}
}
}
});
// @section Handlers
// @property dragging: Handler
// Map dragging handler (by both mouse and touch).
Map.addInitHook('addHandler', 'dragging', Drag);
+183
View File
@@ -0,0 +1,183 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import {on, off, stop} from '../../dom/DomEvent';
import {toPoint} from '../../geometry/Point';
/*
* L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
*/
// @namespace Map
// @section Keyboard Navigation Options
Map.mergeOptions({
// @option keyboard: Boolean = true
// Makes the map focusable and allows users to navigate the map with keyboard
// arrows and `+`/`-` keys.
keyboard: true,
// @option keyboardPanDelta: Number = 80
// Amount of pixels to pan when pressing an arrow key.
keyboardPanDelta: 80
});
export var Keyboard = Handler.extend({
keyCodes: {
left: [37],
right: [39],
down: [40],
up: [38],
zoomIn: [187, 107, 61, 171],
zoomOut: [189, 109, 54, 173]
},
initialize: function (map) {
this._map = map;
this._setPanDelta(map.options.keyboardPanDelta);
this._setZoomDelta(map.options.zoomDelta);
},
addHooks: function () {
var container = this._map._container;
// make the container focusable by tabbing
if (container.tabIndex <= 0) {
container.tabIndex = '0';
}
on(container, {
focus: this._onFocus,
blur: this._onBlur,
mousedown: this._onMouseDown
}, this);
this._map.on({
focus: this._addHooks,
blur: this._removeHooks
}, this);
},
removeHooks: function () {
this._removeHooks();
off(this._map._container, {
focus: this._onFocus,
blur: this._onBlur,
mousedown: this._onMouseDown
}, this);
this._map.off({
focus: this._addHooks,
blur: this._removeHooks
}, this);
},
_onMouseDown: function () {
if (this._focused) { return; }
var body = document.body,
docEl = document.documentElement,
top = body.scrollTop || docEl.scrollTop,
left = body.scrollLeft || docEl.scrollLeft;
this._map._container.focus();
window.scrollTo(left, top);
},
_onFocus: function () {
this._focused = true;
this._map.fire('focus');
},
_onBlur: function () {
this._focused = false;
this._map.fire('blur');
},
_setPanDelta: function (panDelta) {
var keys = this._panKeys = {},
codes = this.keyCodes,
i, len;
for (i = 0, len = codes.left.length; i < len; i++) {
keys[codes.left[i]] = [-1 * panDelta, 0];
}
for (i = 0, len = codes.right.length; i < len; i++) {
keys[codes.right[i]] = [panDelta, 0];
}
for (i = 0, len = codes.down.length; i < len; i++) {
keys[codes.down[i]] = [0, panDelta];
}
for (i = 0, len = codes.up.length; i < len; i++) {
keys[codes.up[i]] = [0, -1 * panDelta];
}
},
_setZoomDelta: function (zoomDelta) {
var keys = this._zoomKeys = {},
codes = this.keyCodes,
i, len;
for (i = 0, len = codes.zoomIn.length; i < len; i++) {
keys[codes.zoomIn[i]] = zoomDelta;
}
for (i = 0, len = codes.zoomOut.length; i < len; i++) {
keys[codes.zoomOut[i]] = -zoomDelta;
}
},
_addHooks: function () {
on(document, 'keydown', this._onKeyDown, this);
},
_removeHooks: function () {
off(document, 'keydown', this._onKeyDown, this);
},
_onKeyDown: function (e) {
if (e.altKey || e.ctrlKey || e.metaKey) { return; }
var key = e.keyCode,
map = this._map,
offset;
if (key in this._panKeys) {
if (!map._panAnim || !map._panAnim._inProgress) {
offset = this._panKeys[key];
if (e.shiftKey) {
offset = toPoint(offset).multiplyBy(3);
}
if (map.options.maxBounds) {
offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
}
if (map.options.worldCopyJump) {
var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
map.panTo(newLatLng);
} else {
map.panBy(offset);
}
}
} else if (key in this._zoomKeys) {
map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
} else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
map.closePopup();
} else {
return;
}
stop(e);
}
});
// @section Handlers
// @section Handlers
// @property keyboard: Handler
// Keyboard navigation handler.
Map.addInitHook('addHandler', 'keyboard', Keyboard);
@@ -0,0 +1,91 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import * as DomEvent from '../../dom/DomEvent';
import * as Util from '../../core/Util';
/*
* L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
*/
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @section Mouse wheel options
// @option scrollWheelZoom: Boolean|String = true
// Whether 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.
scrollWheelZoom: true,
// @option wheelDebounceTime: Number = 40
// Limits 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.
wheelDebounceTime: 40,
// @option wheelPxPerZoomLevel: Number = 60
// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
// mean a change of one full zoom level. Smaller values will make wheel-zooming
// faster (and vice versa).
wheelPxPerZoomLevel: 60
});
export var ScrollWheelZoom = Handler.extend({
addHooks: function () {
DomEvent.on(this._map._container, 'wheel', this._onWheelScroll, this);
this._delta = 0;
},
removeHooks: function () {
DomEvent.off(this._map._container, 'wheel', this._onWheelScroll, this);
},
_onWheelScroll: function (e) {
var delta = DomEvent.getWheelDelta(e);
var debounce = this._map.options.wheelDebounceTime;
this._delta += delta;
this._lastMousePos = this._map.mouseEventToContainerPoint(e);
if (!this._startTime) {
this._startTime = +new Date();
}
var left = Math.max(debounce - (+new Date() - this._startTime), 0);
clearTimeout(this._timer);
this._timer = setTimeout(Util.bind(this._performZoom, this), left);
DomEvent.stop(e);
},
_performZoom: function () {
var map = this._map,
zoom = map.getZoom(),
snap = this._map.options.zoomSnap || 0;
map._stop(); // stop panning and fly animations if any
// map the delta with a sigmoid function to -4..4 range leaning on -1..1
var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
this._delta = 0;
this._startTime = null;
if (!delta) { return; }
if (map.options.scrollWheelZoom === 'center') {
map.setZoom(zoom + delta);
} else {
map.setZoomAround(this._lastMousePos, zoom + delta);
}
}
});
// @section Handlers
// @property scrollWheelZoom: Handler
// Scroll wheel zoom handler.
Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
+102
View File
@@ -0,0 +1,102 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import * as DomEvent from '../../dom/DomEvent';
import {Point} from '../../geometry/Point';
import * as Util from '../../core/Util';
import Browser from '../../core/Browser';
/*
* L.Map.TapHold is used to simulate `contextmenu` event on long hold,
* which otherwise is not fired by mobile Safari.
*/
var tapHoldDelay = 600;
// @namespace Map
// @section Interaction Options
Map.mergeOptions({
// @section Touch interaction options
// @option tapHold: Boolean
// Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
// @option tapTolerance: Number = 15
// The max number of pixels a user can shift his finger during touch
// for it to be considered a valid tap.
tapTolerance: 15
});
export var TapHold = Handler.extend({
addHooks: function () {
DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
},
removeHooks: function () {
DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
},
_onDown: function (e) {
clearTimeout(this._holdTimeout);
if (e.touches.length !== 1) { return; }
var first = e.touches[0];
this._startPos = this._newPos = new Point(first.clientX, first.clientY);
this._holdTimeout = setTimeout(Util.bind(function () {
this._cancel();
if (!this._isTapValid()) { return; }
// prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
DomEvent.on(document, 'touchend', DomEvent.preventDefault);
DomEvent.on(document, 'touchend touchcancel', this._cancelClickPrevent);
this._simulateEvent('contextmenu', first);
}, this), tapHoldDelay);
DomEvent.on(document, 'touchend touchcancel contextmenu', this._cancel, this);
DomEvent.on(document, 'touchmove', this._onMove, this);
},
_cancelClickPrevent: function cancelClickPrevent() {
DomEvent.off(document, 'touchend', DomEvent.preventDefault);
DomEvent.off(document, 'touchend touchcancel', cancelClickPrevent);
},
_cancel: function () {
clearTimeout(this._holdTimeout);
DomEvent.off(document, 'touchend touchcancel contextmenu', this._cancel, this);
DomEvent.off(document, 'touchmove', this._onMove, this);
},
_onMove: function (e) {
var first = e.touches[0];
this._newPos = new Point(first.clientX, first.clientY);
},
_isTapValid: function () {
return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
},
_simulateEvent: function (type, e) {
var simulatedEvent = new MouseEvent(type, {
bubbles: true,
cancelable: true,
view: window,
// detail: 1,
screenX: e.screenX,
screenY: e.screenY,
clientX: e.clientX,
clientY: e.clientY,
// button: 2,
// buttons: 2
});
simulatedEvent._simulated = true;
e.target.dispatchEvent(simulatedEvent);
}
});
// @section Handlers
// @property tapHold: Handler
// Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
Map.addInitHook('addHandler', 'tapHold', TapHold);
+130
View File
@@ -0,0 +1,130 @@
import {Map} from '../Map';
import {Handler} from '../../core/Handler';
import * as DomEvent from '../../dom/DomEvent';
import * as Util from '../../core/Util';
import * as DomUtil from '../../dom/DomUtil';
import Browser from '../../core/Browser';
/*
* L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
*/
// @namespace Map
// @section Interaction Options
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.
touchZoom: Browser.touch,
// @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
});
export var TouchZoom = Handler.extend({
addHooks: function () {
DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
},
removeHooks: function () {
DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
},
_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]);
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));
}
this._startDist = p1.distanceTo(p2);
this._startZoom = map.getZoom();
this._moved = false;
this._zooming = true;
map._stop();
DomEvent.on(document, 'touchmove', this._onTouchMove, this);
DomEvent.on(document, 'touchend touchcancel', this._onTouchEnd, this);
DomEvent.preventDefault(e);
},
_onTouchMove: function (e) {
if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
var map = this._map,
p1 = map.mouseEventToContainerPoint(e.touches[0]),
p2 = map.mouseEventToContainerPoint(e.touches[1]),
scale = p1.distanceTo(p2) / this._startDist;
this._zoom = map.getScaleZoom(scale, this._startZoom);
if (!map.options.bounceAtZoomLimits && (
(this._zoom < map.getMinZoom() && scale < 1) ||
(this._zoom > map.getMaxZoom() && scale > 1))) {
this._zoom = map._limitZoom(this._zoom);
}
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 (!this._moved) {
map._moveStart(true, false);
this._moved = true;
}
Util.cancelAnimFrame(this._animRequest);
var moveFn = Util.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
this._animRequest = Util.requestAnimFrame(moveFn, this, true);
DomEvent.preventDefault(e);
},
_onTouchEnd: function () {
if (!this._moved || !this._zooming) {
this._zooming = false;
return;
}
this._zooming = false;
Util.cancelAnimFrame(this._animRequest);
DomEvent.off(document, 'touchmove', this._onTouchMove, this);
DomEvent.off(document, 'touchend touchcancel', this._onTouchEnd, this);
// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
if (this._map.options.zoomAnimation) {
this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
} else {
this._map._resetView(this._center, this._map._limitZoom(this._zoom));
}
}
});
// @section Handlers
// @property touchZoom: Handler
// Touch zoom handler.
Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
+17
View File
@@ -0,0 +1,17 @@
import {Map} from './Map';
import {BoxZoom} from './handler/Map.BoxZoom';
Map.BoxZoom = BoxZoom;
import {DoubleClickZoom} from './handler/Map.DoubleClickZoom';
Map.DoubleClickZoom = DoubleClickZoom;
import {Drag} from './handler/Map.Drag';
Map.Drag = Drag;
import {Keyboard} from './handler/Map.Keyboard';
Map.Keyboard = Keyboard;
import {ScrollWheelZoom} from './handler/Map.ScrollWheelZoom';
Map.ScrollWheelZoom = ScrollWheelZoom;
import {TapHold} from './handler/Map.TapHold';
Map.TapHold = TapHold;
import {TouchZoom} from './handler/Map.TouchZoom';
Map.TouchZoom = TouchZoom;
export {Map, createMap as map} from './Map';