From 178b97ddc5098d188e0bd569ce55b5a0878734d6 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Sun, 17 May 2026 18:07:40 -0400 Subject: [PATCH 1/4] [19.0][MIG] web_leaflet_lib: Migration to 19.0 --- web_leaflet_lib/README.rst | 150 + web_leaflet_lib/__init__.py | 2 + web_leaflet_lib/__manifest__.py | 24 + web_leaflet_lib/data/ir_config_parameter.xml | 28 + web_leaflet_lib/hooks.py | 11 + web_leaflet_lib/i18n/fr.po | 22 + web_leaflet_lib/i18n/it.po | 22 + web_leaflet_lib/i18n/web_leaflet_lib.pot | 19 + web_leaflet_lib/models/__init__.py | 1 + web_leaflet_lib/models/ir_http.py | 20 + web_leaflet_lib/pyproject.toml | 3 + web_leaflet_lib/readme/CONFIGURE.md | 4 + web_leaflet_lib/readme/CONTRIBUTORS.md | 1 + web_leaflet_lib/readme/CREDITS.md | 3 + web_leaflet_lib/readme/DESCRIPTION.md | 46 + web_leaflet_lib/static/description/icon.png | Bin 0 -> 39188 bytes web_leaflet_lib/static/description/index.html | 486 + .../static/lib/leaflet/images/layers-2x.png | Bin 0 -> 1259 bytes .../static/lib/leaflet/images/layers.png | Bin 0 -> 696 bytes .../lib/leaflet/images/marker-icon-2x.png | Bin 0 -> 2464 bytes .../static/lib/leaflet/images/marker-icon.png | Bin 0 -> 1466 bytes .../lib/leaflet/images/marker-shadow.png | Bin 0 -> 618 bytes .../static/lib/leaflet/leaflet.css | 661 + web_leaflet_lib/static/lib/leaflet/leaflet.js | 14512 ++++++++++++++++ .../MarkerCluster.Default.css | 60 + .../leaflet_markercluster/MarkerCluster.css | 14 + .../leaflet.markercluster.js | 2690 +++ 27 files changed, 18779 insertions(+) create mode 100644 web_leaflet_lib/README.rst create mode 100644 web_leaflet_lib/__init__.py create mode 100644 web_leaflet_lib/__manifest__.py create mode 100644 web_leaflet_lib/data/ir_config_parameter.xml create mode 100644 web_leaflet_lib/hooks.py create mode 100644 web_leaflet_lib/i18n/fr.po create mode 100644 web_leaflet_lib/i18n/it.po create mode 100644 web_leaflet_lib/i18n/web_leaflet_lib.pot create mode 100644 web_leaflet_lib/models/__init__.py create mode 100644 web_leaflet_lib/models/ir_http.py create mode 100644 web_leaflet_lib/pyproject.toml create mode 100644 web_leaflet_lib/readme/CONFIGURE.md create mode 100644 web_leaflet_lib/readme/CONTRIBUTORS.md create mode 100644 web_leaflet_lib/readme/CREDITS.md create mode 100644 web_leaflet_lib/readme/DESCRIPTION.md create mode 100644 web_leaflet_lib/static/description/icon.png create mode 100644 web_leaflet_lib/static/description/index.html create mode 100644 web_leaflet_lib/static/lib/leaflet/images/layers-2x.png create mode 100644 web_leaflet_lib/static/lib/leaflet/images/layers.png create mode 100644 web_leaflet_lib/static/lib/leaflet/images/marker-icon-2x.png create mode 100644 web_leaflet_lib/static/lib/leaflet/images/marker-icon.png create mode 100644 web_leaflet_lib/static/lib/leaflet/images/marker-shadow.png create mode 100644 web_leaflet_lib/static/lib/leaflet/leaflet.css create mode 100644 web_leaflet_lib/static/lib/leaflet/leaflet.js create mode 100644 web_leaflet_lib/static/lib/leaflet_markercluster/MarkerCluster.Default.css create mode 100644 web_leaflet_lib/static/lib/leaflet_markercluster/MarkerCluster.css create mode 100644 web_leaflet_lib/static/lib/leaflet_markercluster/leaflet.markercluster.js diff --git a/web_leaflet_lib/README.rst b/web_leaflet_lib/README.rst new file mode 100644 index 000000000..3f9b1be4b --- /dev/null +++ b/web_leaflet_lib/README.rst @@ -0,0 +1,150 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========================== +Leaflet Javascript Library +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e2958825380defcea854d52ad207b7e610d5ac1dbc754b7c3dee544ac418fb8c + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fgeospatial-lightgray.png?logo=github + :target: https://github.com/OCA/geospatial/tree/18.0/web_leaflet_lib + :alt: OCA/geospatial +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/geospatial-18-0/geospatial-18-0-web_leaflet_lib + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/geospatial&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends odoo to include Leaflet Javacript library. + +This module is used by ``web_view_leaflet_map``. + +**Important Note** + +The javascript library is opensource and distributed under BSD 2 +Licence. See : https://github.com/Leaflet/Leaflet/blob/main/LICENSE. The +plugin library is opensource and distributed under MIT Licence. See : +https://github.com/Leaflet/Leaflet.markercluster/blob/master/MIT-LICENCE.txt. + +You can so use it freely. + +However, display maps requires to display layers provided by tiles +servers, that requires ressources. + +**For testing purpose** + +You can use the openStreetMap url +``https://tile.openstreetmap.org/{z}/{x}/{y}.png`` or other, listed in +that page : https://wiki.openstreetmap.org/wiki/Tile_servers + +Apart from very limited testing purposes, you should not use the tiles +supplied by OpenStreetMap.org itself. OpenStreetMap is a volunteer-run +non-profit body and cannot supply tiles for large-scale commercial use. + +**Regular / High Usage** + +- you can contact one of the following companies : + https://switch2osm.org/providers/ +- You can also install yourself your own tiles servers. See + documentation : https://switch2osm.org/serving-tiles/ + +**Library Update** + +For the time being, the module embed the lealflet.js library version +1.9.4 ( released on May 18, 2023.) + +If a new release is out: + +- please download it here https://leafletjs.com/download.html +- update the javascript, css and images, present in the folder + ``static/lib/leaflet`` +- update the plugins +- test the features +- make a Pull Request + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +- Go to Settings > Technical > Parameters > System Parameters +- Create or edit the parameter with the key ``leaflet.tile_url`` +- As a value, set the url of the tiles server you chose. (See + description) + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* GRAP + +Contributors +------------ + +- Sylvain LE GAL (https://www.twitter.com/legalsylvain) + +Other credits +------------- + +The module embed: + +- the Leaflet.js library. (https://github.com/Leaflet/Leaflet) +- the markercluster plugin. + (https://github.com/Leaflet/Leaflet.markercluster) + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px + :target: https://github.com/legalsylvain + :alt: legalsylvain + +Current `maintainer `__: + +|maintainer-legalsylvain| + +This module is part of the `OCA/geospatial `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_leaflet_lib/__init__.py b/web_leaflet_lib/__init__.py new file mode 100644 index 000000000..cc6b6354a --- /dev/null +++ b/web_leaflet_lib/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/web_leaflet_lib/__manifest__.py b/web_leaflet_lib/__manifest__.py new file mode 100644 index 000000000..d095af297 --- /dev/null +++ b/web_leaflet_lib/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright (C) 2024 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Leaflet Javascript Library", + "summary": "Bring leaflet.js librairy in odoo.", + "version": "19.0.1.0.0", + "author": "GRAP, Odoo Community Association (OCA)", + "maintainers": ["legalsylvain"], + "website": "https://github.com/OCA/geospatial", + "license": "AGPL-3", + "category": "Extra Tools", + "depends": ["base"], + "data": ["data/ir_config_parameter.xml"], + "assets": { + "web.assets_backend": [ + "/web_leaflet_lib/static/lib/leaflet/*", + "/web_leaflet_lib/static/lib/leaflet_markercluster/*", + ], + }, + "installable": True, + "post_init_hook": "post_init_hook", +} diff --git a/web_leaflet_lib/data/ir_config_parameter.xml b/web_leaflet_lib/data/ir_config_parameter.xml new file mode 100644 index 000000000..51d750053 --- /dev/null +++ b/web_leaflet_lib/data/ir_config_parameter.xml @@ -0,0 +1,28 @@ + + + + + leaflet.copyright + OpenStreetMap]]> + + + + leaflet.tile_url + False + + diff --git a/web_leaflet_lib/hooks.py b/web_leaflet_lib/hooks.py new file mode 100644 index 000000000..eabb80789 --- /dev/null +++ b/web_leaflet_lib/hooks.py @@ -0,0 +1,11 @@ +def post_init_hook(env): + """ + Update the default tile URL in demo data if needed. + """ + base = env.ref("base.module_base") + if base.demo: + tile_url_param = env["ir.config_parameter"].search( + [("key", "=", "leaflet.tile_url")] + ) + if tile_url_param.value == "False": + tile_url_param.value = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" diff --git a/web_leaflet_lib/i18n/fr.po b/web_leaflet_lib/i18n/fr.po new file mode 100644 index 000000000..40589a830 --- /dev/null +++ b/web_leaflet_lib/i18n/fr.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_leaflet_lib +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-12 20:11+0000\n" +"PO-Revision-Date: 2024-10-12 20:11+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_leaflet_lib +#: model:ir.model,name:web_leaflet_lib.model_ir_http +msgid "HTTP Routing" +msgstr "Routage HTTP" diff --git a/web_leaflet_lib/i18n/it.po b/web_leaflet_lib/i18n/it.po new file mode 100644 index 000000000..f4322c522 --- /dev/null +++ b/web_leaflet_lib/i18n/it.po @@ -0,0 +1,22 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_leaflet_lib +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-11-21 11:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: web_leaflet_lib +#: model:ir.model,name:web_leaflet_lib.model_ir_http +msgid "HTTP Routing" +msgstr "Instradamento HTTP" diff --git a/web_leaflet_lib/i18n/web_leaflet_lib.pot b/web_leaflet_lib/i18n/web_leaflet_lib.pot new file mode 100644 index 000000000..e721356d0 --- /dev/null +++ b/web_leaflet_lib/i18n/web_leaflet_lib.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_leaflet_lib +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_leaflet_lib +#: model:ir.model,name:web_leaflet_lib.model_ir_http +msgid "HTTP Routing" +msgstr "" diff --git a/web_leaflet_lib/models/__init__.py b/web_leaflet_lib/models/__init__.py new file mode 100644 index 000000000..9a5eb7187 --- /dev/null +++ b/web_leaflet_lib/models/__init__.py @@ -0,0 +1 @@ +from . import ir_http diff --git a/web_leaflet_lib/models/ir_http.py b/web_leaflet_lib/models/ir_http.py new file mode 100644 index 000000000..9fe6aad64 --- /dev/null +++ b/web_leaflet_lib/models/ir_http.py @@ -0,0 +1,20 @@ +# Copyright (C) 2022 - Today: GRAP (http://www.grap.coop) +# @author: Sylvain LE GAL (https://twitter.com/legalsylvain) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class Http(models.AbstractModel): + _inherit = "ir.http" + + def session_info(self): + result = super().session_info() + config = self.env["ir.config_parameter"].sudo() + result.update( + { + "leaflet.tile_url": config.get_param("leaflet.tile_url", default=""), + "leaflet.copyright": config.get_param("leaflet.copyright", default=""), + } + ) + return result diff --git a/web_leaflet_lib/pyproject.toml b/web_leaflet_lib/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/web_leaflet_lib/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_leaflet_lib/readme/CONFIGURE.md b/web_leaflet_lib/readme/CONFIGURE.md new file mode 100644 index 000000000..6c07774f0 --- /dev/null +++ b/web_leaflet_lib/readme/CONFIGURE.md @@ -0,0 +1,4 @@ +- Go to Settings \> Technical \> Parameters \> System Parameters +- Create or edit the parameter with the key `leaflet.tile_url` +- As a value, set the url of the tiles server you chose. (See + description) diff --git a/web_leaflet_lib/readme/CONTRIBUTORS.md b/web_leaflet_lib/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..4a6b63400 --- /dev/null +++ b/web_leaflet_lib/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Sylvain LE GAL () diff --git a/web_leaflet_lib/readme/CREDITS.md b/web_leaflet_lib/readme/CREDITS.md new file mode 100644 index 000000000..f993caca9 --- /dev/null +++ b/web_leaflet_lib/readme/CREDITS.md @@ -0,0 +1,3 @@ +The module embed: +- the Leaflet.js library. (https://github.com/Leaflet/Leaflet) +- the markercluster plugin. (https://github.com/Leaflet/Leaflet.markercluster) diff --git a/web_leaflet_lib/readme/DESCRIPTION.md b/web_leaflet_lib/readme/DESCRIPTION.md new file mode 100644 index 000000000..7540a64ea --- /dev/null +++ b/web_leaflet_lib/readme/DESCRIPTION.md @@ -0,0 +1,46 @@ +This module extends odoo to include Leaflet Javacript library. + +This module is used by `web_view_leaflet_map`. + +**Important Note** + +The javascript library is opensource and distributed under BSD 2 +Licence. See : . +The plugin library is opensource and distributed under MIT +Licence. See : . + +You can so use it freely. + +However, display maps requires to display layers provided by tiles +servers, that requires ressources. + +**For testing purpose** + +You can use the openStreetMap url +`https://tile.openstreetmap.org/{z}/{x}/{y}.png` or other, listed in +that page : + +Apart from very limited testing purposes, you should not use the tiles +supplied by OpenStreetMap.org itself. OpenStreetMap is a volunteer-run +non-profit body and cannot supply tiles for large-scale commercial use. + +**Regular / High Usage** + +- you can contact one of the following companies : + +- You can also install yourself your own tiles servers. See + documentation : + +**Library Update** + +For the time being, the module embed the lealflet.js library version +1.9.4 ( released on May 18, 2023.) + +If a new release is out: + +- please download it here +- update the javascript, css and images, present in the folder + `static/lib/leaflet` +- update the plugins +- test the features +- make a Pull Request diff --git a/web_leaflet_lib/static/description/icon.png b/web_leaflet_lib/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9a516f2f8a1871318c6531a23430eb43cb4ac1e9 GIT binary patch literal 39188 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 literal 0 HcmV?d00001 diff --git a/web_leaflet_lib/static/description/index.html b/web_leaflet_lib/static/description/index.html new file mode 100644 index 000000000..76651fc02 --- /dev/null +++ b/web_leaflet_lib/static/description/index.html @@ -0,0 +1,486 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Leaflet Javascript Library

+ +

Beta License: AGPL-3 OCA/geospatial Translate me on Weblate Try me on Runboat

+

This module extends odoo to include Leaflet Javacript library.

+

This module is used by web_view_leaflet_map.

+

Important Note

+

The javascript library is opensource and distributed under BSD 2 +Licence. See : https://github.com/Leaflet/Leaflet/blob/main/LICENSE. The +plugin library is opensource and distributed under MIT Licence. See : +https://github.com/Leaflet/Leaflet.markercluster/blob/master/MIT-LICENCE.txt.

+

You can so use it freely.

+

However, display maps requires to display layers provided by tiles +servers, that requires ressources.

+

For testing purpose

+

You can use the openStreetMap url +https://tile.openstreetmap.org/{z}/{x}/{y}.png or other, listed in +that page : https://wiki.openstreetmap.org/wiki/Tile_servers

+

Apart from very limited testing purposes, you should not use the tiles +supplied by OpenStreetMap.org itself. OpenStreetMap is a volunteer-run +non-profit body and cannot supply tiles for large-scale commercial use.

+

Regular / High Usage

+ +

Library Update

+

For the time being, the module embed the lealflet.js library version +1.9.4 ( released on May 18, 2023.)

+

If a new release is out:

+
    +
  • please download it here https://leafletjs.com/download.html
  • +
  • update the javascript, css and images, present in the folder +static/lib/leaflet
  • +
  • update the plugins
  • +
  • test the features
  • +
  • make a Pull Request
  • +
+

Table of contents

+ +
+

Configuration

+
    +
  • Go to Settings > Technical > Parameters > System Parameters
  • +
  • Create or edit the parameter with the key leaflet.tile_url
  • +
  • As a value, set the url of the tiles server you chose. (See +description)
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • GRAP
  • +
+
+ +
+

Other credits

+

The module embed:

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

legalsylvain

+

This module is part of the OCA/geospatial project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/web_leaflet_lib/static/lib/leaflet/images/layers-2x.png b/web_leaflet_lib/static/lib/leaflet/images/layers-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..200c333dca9652ac4cba004d609e5af4eee168c1 GIT binary patch literal 1259 zcmVFhCYNy;#0irRPomHqW|G1C*;4?@4#E?jH>?v@U%cy?3dQAc-DchXVErpOh~ z-jbon+tNbnl6hoEb;)TVk+%hTDDi_G%i3*RZ&15!$Fjr^f;Ke&A@|?=`2&+{zr+3a z{D*=t(`AXyS%X7N z%a#RZw6vD^t_rnM`L4E>m=U&R!A-&}nZIi$BOPvkhrCuUe@BN~-lRD)f44;J%TwgE zcze8u!PQ_NR7?o(NylLXVTfDO zxs5=@|GsYEsNo4M#nT%N!UE(?dnS)t2+{ELYAFp*3=iF=|EQnTp`#vlSXuGVraYo? z+RCzXo6h3qA8{KG?S4nE(lM+;Eb4nT3XV;7gcAxUi5m)`k5tv}cPy()8ZR3TLW3I- zAS^}cq-IJvL7a4RgR!yk@~RT%$lA7{L5ES*hyx)M4(yxI$Ub(4f)K|^v1>zvwQY!_ zIrWw8q9GS^!Dp~}+?mbnB6jDF8mVlbQ!jFKDY;w=7;XO{9bq7>LXGK24WA`;rL)_Z z)&j}pbV(;6gY;VMhbxgvn`X;6x}VUEE-7 z%)7j-%t8S=ZL3yc)HbXDAqJZvBTPoiW_A-+a8m3_Z?v{DN7Tnr#O_VUMT0UBt$;p` zDh6JbGHN8JJ*JN%y2%msb97@_S>9!%Egwk;?PEkU9ntz&3uR}%Fj5d$JHQbQb3}a{ zSzFT^#n=VInPpcAS}CNxj?_ zVscANk5Cfz(51EI1pz};AWWb|kgbYNb4wCEGUn3+eMUMV?1-{=I4TlmLJMot@rd07 zZuo2hk1ccu{YmGkcYdWAVdk{Z4Nm?^cTD&}jGm+Q1SYIXMwmG*oO*83&#>l%nbR`G zhh=lZ%xIb7kU3#;TBbfECrnC9P=-XpL|TG2BoZdj61*XiFbW8?1Z_wp%#;>${SUIy V$8qr;L*)Pf002ovPDHLkV1hYLS~36t literal 0 HcmV?d00001 diff --git a/web_leaflet_lib/static/lib/leaflet/images/layers.png b/web_leaflet_lib/static/lib/leaflet/images/layers.png new file mode 100644 index 0000000000000000000000000000000000000000..1a72e5784b2b456eac5d7670738db80697af3377 GIT binary patch literal 696 zcmV;p0!RIcP)*@&l2<6p=!C&s@#ZL+%BQvF&b?w6S%wp=I>1QHj7AP5C)IWy#b znXXB;g;j=$a-tW89K%FbDceHVq&unY*Wx3L#=EGWH=rjqnp|4c_Ulec!ql3#G-5ZF zVlbBA@XP=)C8U&+Lrc)S4O5%1$&{(;7R^K(CSnvSr$v;+B$8q&7Bf|h$#PARo1^%M zf1H^nG-EiXVXr07OH(*8R)xa|FD;lXUlg_-%)~ZGsL2cX0NXaAzN2q%jqLRR6ruVk8`Jb7n#{`T;o@`F= z#3YcynIR^s83UNF3D!f5m#Mg)NJ24&Qfrqb&_z=yF;=B)#9Iq7u-@^O!(mW{D;qvr zPc)gVb%aowtS8m@ElL4A9G>w#ffQ~q{i&_i)*6f^)Sz|C?C>zb4Uo?H<-&Hz@a?J; z$ml@zGygWofb9$ZBj6aLjpLhsT2AzjOu=-*u_gSCUYnU^5s62$4H-fe}gSR(=wKRaTHh!@*b)YV6mo|a4Fn6Rgc&Rpk zvn_X|3VY?v=>nJ{slE^V1GaGWk}m@aIWGIpghbfPh8m@aIWEo_%AZI>==moIFVE^L=C zZJ91?mo03UEp3-BY?wBGur6$uD{Yr9Y?m%SHF8Fk1pc(Nva%QJ+{FLkalfypz3&M|||Fn`7|g3c~4(nXHKFmRnwn$J#_$xE8i z|Ns9!kC;(oC1qQk>LMp3_a2(odYyMT@>voX=UI)k>1cJdn;gjmJ-|6v4nb1Oryh)eQMwHP(i@!36%vGJyFK(JTj?Vb{{C=jx&)@1l zlFmnw%0`&bqruifkkHKC=vbiAM3&E`#Mv>2%tw;VK8?_|&E89cs{a1}$J*!f_xd-C z&F%B|oxRgPlh0F!txkxrQjNA`m9~?&&|jw4W0<`_iNHsX$VQXVK!B}Xkh4>av|f_8 zLY2?t?ejE=%(TnfV5iqOjm?d;&qI~ZGl|SzU77a)002XDQchC<95+*MjE@82?VLm= z3xf6%Vd@99z|q|-ua5l3kJxvZwan-8K1cPiwQAtlcNX~ZqLeoMB+a;7)WA|O#HOB% zg6SX;754xD1{Fy}K~#8Ntklac&zTpadXZ& zC*_=T&g7hfbI$R?v%9?sknIb97gJOJ=`-8YyS3ndqN+Jm+x33!p&Hc@@L$w))s2@N ztv~i}Emc?DykgwFWwma($8+~b>l?tqj$dh13R^nMZnva9 zn0Vflzv2Dvp`oVQw{Guby~i`JGbyBGTEC{y>yzCkg>K&CIeQ$u;lyQ+M{O~gEJ^)Z zrF3p)^>|uT;57}WY&IRwyOQ=dq%Az}_t=_hKowP!Z79q0;@Zu(SWEJJcHY+5T6I({ zw)wj*SNi4wrd+POUfZe4gF77vW?j zoFS}|r2n&$U9Y!S4VEOyN}OpZZi|?cr1VcE_tHsDQgp-ga(SwkBrkCm{|*-yb=}ZW zvcYvLvfA90TPn|!-TuYJV<6`}+RJeRgP3EA=qQcF9k0*#*{f&I_pjam%I6Dd#YE|G zqB!R}tW-K!wV1w+4JcFA_s6~=@9F&j8`u$-ifLN3vK;`lvaA-`jRn_}(8|)!3?-}I zvFi{H;@A$gEZYh?%|Qr_y#*UkOPjwiRCsJQ>mb6h5yGIk6C5_XA=8T?IBfm_?+P0; zhhUs)-(0R*H<&Kku(1>#cGtOpk&Z&kQcw&SJv-4VY<+;=8hYnoX zfNJMCa9)^5Z0;2dCUk;x-%#yS!I~Jr3pNuI!g_tHz!$hKwt1GL~sFvx)3u4TA zv>CLGdQtoZ7Du7ctJRfTqY;FPxs1G{ZJ?73D5J@OO{6BHcPbk{_mjg&p2QFeke%QI zlAJ-kvjuwy1<5D-6>su68A+i998aSZNnQX)+Q}6(GK-C%8G-!1bOJBONU{gT%IOOE z;Yk24YC@^lFW77>r6x7eS1Omc;8=GUp#&zLQ&L{ zv8$hGC`wp~$9pR>f%-_Ps3>YhzP(+vC(E*zr1CVO8ChN^MI-VGMX7+|(r!SGZ9gd5 zzO9sQd>sm|f1|X&oh=8lOzd6+ITvo zCXInR?>RZ#>Hb*PO=7dI!dZ(wY4O}ZGv zdfQFio7+0~PN*RFCZGM6@9-o~y*@?;k00NvOsw54t1^tt{*ATMs^2j}4Wp=4t3RH* z_+8b`F-{E=0sOgM<;VHTo!Ij3u zmmI`2?K7g(GOcGA)@h?$SW&pwHdtj1n57PLI8&6RHhx4R%Q7b z^JEqR)@06V!pbS*@D_ZyRMo_LlT}r{#sXOx4kM-V<_V{!5SSuM^SIVCA37|nY7LWQ zZA#B1h4l`6asz=Lvax_#GMRX|NF>=$=p{Qn0i@ExX1jGhy@B8a*_uR+ODEbVi8ObL zezG?azy>E~S~dl43&8<$(2H}P&*tuBdESUP83KQ?8B z?K(!uS>H1wlWQz;qOfB`T#TZ=EoSp~vZ5XtCvwm1h*Ex6mzTsn_y@_=xREIslV-%- zpdWkEzMjeNOGWrSM32gpBt27*O29NdhGzuDgYxcf`Jjjqw@B;Vmdb@fxdhCRi`Kg> zmUTr$=&@#i!%F4Q6mb&4QKfR^95KJ!<6~fqx-f^66AV!|ywG{6D^Vay-3b99>XOe# e-I|>x8~*?ZhF3snGbtJX0000cOl4 literal 0 HcmV?d00001 diff --git a/web_leaflet_lib/static/lib/leaflet/images/marker-icon.png b/web_leaflet_lib/static/lib/leaflet/images/marker-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..950edf24677ded147df13b26f91baa2b0fa70513 GIT binary patch literal 1466 zcmV;r1x5OaP)P001cn1^@s6z>|W`000GnNklGNuHDcIX17Zdjl&3`L?0sTjIws<{((Dh&g-s0<@jYQyl?D*X^?%13;ml^gy> ziMrY_^1WI=(g@LMizu=zCoA>C`6|QEq1eV92k*7m>G65*&@&6)aC&e}G zI)pf-Za|N`DT&Cn1J|o`19mumxW~hiKiKyc-P`S@q)rdTo84@QI@;0yXrG%9uhI>A zG5QHb6s4=<6xy{1 z@NMxEkryp{LS44%z$3lP^cX!9+2-;CTt3wM4(k*#C{aiIiLuB>jJj;KPhPzIC00bL zU3a#;aJld94lCW=`4&aAy8M7PY=HQ>O%$YEP4c4UY#CRxfgbE~(|uiI=YS8q;O9y6 zmIkXzR`}p7ti|PrM3a}WMnR=3NVnWdAAR>b9X@)DKL6=YsvmH%?I24wdq?Gh54_;# z$?_LvgjEdspdQlft#4CQ z`2Zyvy?*)N1Ftw|{_hakhG9WjS?Az@I@+IZ8JbWewR!XUK4&6346+d#~gsE0SY(LX8&JfY>Aj)RxGy96nwhs2rv zzW6pTnMpFkDSkT*a*6Dx|u@ds6ISVn0@^RmIsKZ5Y;bazbc;tTSq(kg(=481ODrPyNB6n z-$+U}(w$m6U6H$w17Bw+wDaFIe~GvNMYvnw31MpY0eQKT9l>SU``8k7w4)z!GZKMI z#_cEKq7k~i%nlK@6c-K?+R;B#5$?T#YpKD`t_4bAs^#E+@5QW$@OX3*`;(#{U^d-vY)&xEE>n5lYl&T?Amke9$Lam@{1K@O ze*LXqlKQHiv=gx+V^Cbb2?z@ISBQ*3amF;9UJ3SBg(N|710TLamQmYZ&Qjn2LuO<* zCZlB4n%@pc&7NNnY1}x+NWpHlq`OJEo|`aYN9<`RBUB+79g;>dgb6YlfN#kGL?lO_ z!6~M^7sOnbsUkKk<@Ysie&`G>ruxH&Mgy&8;i=A zB9OO!xR{AyODw>DS-q5YM{0ExFEAzt zm>RdS+ssW(-8|?xr0(?$vBVB*%(xDLtq3Hf0I5yFm<_g=W2`QWAax{1rWVH=I!VrP zs(rTFX@W#t$hXNvbgX`gK&^w_YD;CQ!B@e0QbLIWaKAXQe2-kkloo;{iF#6}z!4=W zi$giRj1{ zt;2w`VSCF#WE&*ev7jpsC=6175@(~nTE2;7M-L((0bH@yG}-TB$R~WXd?tA$s3|%y zA`9$sA(>F%J3ioz<-LJl*^o1|w84l>HBR`>3l9c8$5Xr@xCiIQ7{x$fMCzOk_-M=% z+{a_Q#;42`#KfUte@$NT77uaTz?b-fBe)1s5XE$yA79fm?KqM^VgLXD07*qoM6N<$ Ef<_J(9smFU literal 0 HcmV?d00001 diff --git a/web_leaflet_lib/static/lib/leaflet/leaflet.css b/web_leaflet_lib/static/lib/leaflet/leaflet.css new file mode 100644 index 000000000..de25a71ed --- /dev/null +++ b/web_leaflet_lib/static/lib/leaflet/leaflet.css @@ -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; + } + } diff --git a/web_leaflet_lib/static/lib/leaflet/leaflet.js b/web_leaflet_lib/static/lib/leaflet/leaflet.js new file mode 100644 index 000000000..eeb30dfdd --- /dev/null +++ b/web_leaflet_lib/static/lib/leaflet/leaflet.js @@ -0,0 +1,14512 @@ +/* @preserve + * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com + * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {})); +})(this, (function (exports) { 'use strict'; + + var version = "1.9.4"; + + /* + * @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. + 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) + var create$2 = 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. + 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) + var lastId = 0; + + // @function stamp(obj: Object): Number + // Returns the unique ID of an object, assigning it one if it doesn't have it. + 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. + 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`. + 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`. + 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). + 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) + 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. + 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. + function setOptions(obj, options) { + if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { + obj.options = obj.options ? create$2(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'`) + 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. + 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) + 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) + 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). + 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); + } + + var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; + 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. + 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). + function cancelAnimFrame(id) { + if (id) { + cancelFn.call(window, id); + } + } + + var Util = { + __proto__: null, + extend: extend, + create: create$2, + bind: bind, + get lastId () { return lastId; }, + stamp: stamp, + throttle: throttle, + wrapNum: wrapNum, + falseFn: falseFn, + formatNum: formatNum, + trim: trim, + splitWords: splitWords, + setOptions: setOptions, + getParamString: getParamString, + template: template, + isArray: isArray, + indexOf: indexOf, + emptyImageUrl: emptyImageUrl, + requestFn: requestFn, + cancelFn: cancelFn, + requestAnimFrame: requestAnimFrame, + cancelAnimFrame: cancelAnimFrame + }; + + // @class Class + // @aka L.Class + + // @section + // @uninheritable + + // Thanks to John Resig and Dean Edwards for inspiration! + + 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 () { + + 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 = create$2(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) { + extend(NewClass, props.statics); + } + + // mix includes into the prototype + if (props.includes) { + checkDeprecatedMixinEvents(props.includes); + extend.apply(null, [proto].concat(props.includes)); + } + + // mix given properties into the prototype + extend(proto, props); + delete proto.statics; + delete proto.includes; + + // merge options + if (proto.options) { + proto.options = parentProto.options ? create$2(parentProto.options) : {}; + 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; + 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) { + 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 = 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); + } + } + } + + /* + * @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); + * ``` + */ + + 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 = 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 = 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 = 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 = 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 = 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 = 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[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[stamp(obj)]; + } + return this; + }, + + _propagateEvent: function (e) { + for (var id in this._eventParents) { + this._eventParents[id].fire(e.type, 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; + + var Evented = Class.extend(Events); + + /* + * @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. + */ + + 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. + 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); + } + + /* + * @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. + */ + + 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. + function toBounds(a, b) { + if (!a || a instanceof Bounds) { + return a; + } + return new Bounds(a, b); + } + + /* + * @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. + */ + + 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). + function toLatLngBounds(a, b) { + if (a instanceof LatLngBounds) { + return a; + } + return new LatLngBounds(a, b); + } + + /* @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. + */ + + 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(' + + formatNum(this.lat, precision) + ', ' + + 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. + + function toLatLng(a, b, c) { + if (a instanceof LatLng) { + return a; + } + if (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); + } + + /* + * @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. + */ + + 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 ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, + lat = this.wrapLat ? 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); + } + }; + + /* + * @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. + */ + + var Earth = 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; + } + }); + + /* + * @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; + + 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]); + })() + }; + + /* + * @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. + function Transformation(a, b, c, d) { + if (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]`. + + function toTransformation(a, b, c, d) { + return new Transformation(a, b, c, d); + } + + /* + * @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. + */ + + var EPSG3857 = 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); + }()) + }); + + var EPSG900913 = extend({}, EPSG3857, { + code: 'EPSG:900913' + }); + + // @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). + 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 + 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'; + } + + /* + * @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', falseFn, opts); + window.removeEventListener('testPassiveEventSupport', 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 [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). + var canvas$1 = (function () { + return !!document.createElement('canvas').getContext; + }()); + + // @property svg: Boolean + // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). + var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); + + var inlineSvg = !!svg$1 && (function () { + var div = document.createElement('div'); + div.innerHTML = ''; + 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$1 && (function () { + try { + var div = document.createElement('div'); + div.innerHTML = ''; + + var shape = div.firstChild; + shape.style.behavior = 'url(#default#VML)'; + + return shape && (typeof shape.adj === 'object'); + + } catch (e) { + return false; + } + }()); + + + // @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; + } + + + var Browser = { + 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$1, + svg: svg$1, + vml: vml, + inlineSvg: inlineSvg, + mac: mac, + linux: linux + }; + + /* + * 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 + + 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; + } + + 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) { + preventDefault(e); + } + _handlePointer(handler, e); + } + + /* + * 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; + 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 , the browser generates a click on its + //