From 60b7f27c02aff88f8d2abd8510361cdf6825c589 Mon Sep 17 00:00:00 2001 From: Ronald Portier Date: Wed, 12 Nov 2025 12:46:25 +0100 Subject: [PATCH] [ADD] new module web_leaflet_draw_lib --- web_leaflet_draw_lib/README.rst | 92 + web_leaflet_draw_lib/__init__.py | 1 + web_leaflet_draw_lib/__manifest__.py | 23 + web_leaflet_draw_lib/pyproject.toml | 3 + web_leaflet_draw_lib/readme/CONTRIBUTORS.md | 1 + web_leaflet_draw_lib/readme/CREDITS.md | 1 + web_leaflet_draw_lib/readme/DESCRIPTION.md | 5 + .../static/description/icon.png | Bin 0 -> 39188 bytes .../static/description/index.html | 432 ++ .../leaflet.draw/images/spritesheet-2x.png | Bin 0 -> 3581 bytes .../lib/leaflet.draw/images/spritesheet.svg | 155 + .../static/lib/leaflet.draw/leaflet.draw.css | 305 ++ .../static/lib/leaflet.draw/leaflet.draw.js | 4201 +++++++++++++++++ 13 files changed, 5219 insertions(+) create mode 100644 web_leaflet_draw_lib/README.rst create mode 100644 web_leaflet_draw_lib/__init__.py create mode 100644 web_leaflet_draw_lib/__manifest__.py create mode 100644 web_leaflet_draw_lib/pyproject.toml create mode 100644 web_leaflet_draw_lib/readme/CONTRIBUTORS.md create mode 100644 web_leaflet_draw_lib/readme/CREDITS.md create mode 100644 web_leaflet_draw_lib/readme/DESCRIPTION.md create mode 100644 web_leaflet_draw_lib/static/description/icon.png create mode 100644 web_leaflet_draw_lib/static/description/index.html create mode 100644 web_leaflet_draw_lib/static/lib/leaflet.draw/images/spritesheet-2x.png create mode 100644 web_leaflet_draw_lib/static/lib/leaflet.draw/images/spritesheet.svg create mode 100644 web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.css create mode 100644 web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.js diff --git a/web_leaflet_draw_lib/README.rst b/web_leaflet_draw_lib/README.rst new file mode 100644 index 000000000..b0f23f17a --- /dev/null +++ b/web_leaflet_draw_lib/README.rst @@ -0,0 +1,92 @@ +=============================== +Leaflet Draw Javascript Library +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0993e5588086eda7c732199a95bd0f6f5394ec0dc7e01b6812dd872e91aae274 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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_draw_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_draw_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 Draw Javascript library. + +The functions on this module can be used to add information to maps +shown with the Leaflet main library. + +**Table of contents** + +.. contents:: + :local: + +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 +------- + +* Advance Insight + +Contributors +------------ + +- Ronald Portier (ronald@therp.nl) + +Other credits +------------- + +The module embed the leaflet.draw.js library. + +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-NL66278'| image:: https://github.com/NL66278'.png?size=40px + :target: https://github.com/NL66278' + :alt: NL66278' + +Current `maintainer `__: + +|maintainer-NL66278'| + +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_draw_lib/__init__.py b/web_leaflet_draw_lib/__init__.py new file mode 100644 index 000000000..857629df0 --- /dev/null +++ b/web_leaflet_draw_lib/__init__.py @@ -0,0 +1 @@ +# Copyright 2025 Advance Insight diff --git a/web_leaflet_draw_lib/__manifest__.py b/web_leaflet_draw_lib/__manifest__.py new file mode 100644 index 000000000..1d3e39980 --- /dev/null +++ b/web_leaflet_draw_lib/__manifest__.py @@ -0,0 +1,23 @@ +# Copyright 2025 Advance Insight +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Leaflet Draw Javascript Library", + "summary": "Bring leaflet.draw.js library in odoo.", + "version": "18.0.1.0.0", + "author": "Advance Insight, Odoo Community Association (OCA)", + "maintainers": ["NL66278'"], + "website": "https://github.com/OCA/geospatial", + "license": "AGPL-3", + "category": "Extra Tools", + "depends": ["web_leaflet_lib"], + "data": [], + "demo": [], + "assets": { + "web.assets_backend": [ + "/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.css", + "/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.js", + ], + }, + "installable": True, +} diff --git a/web_leaflet_draw_lib/pyproject.toml b/web_leaflet_draw_lib/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/web_leaflet_draw_lib/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/web_leaflet_draw_lib/readme/CONTRIBUTORS.md b/web_leaflet_draw_lib/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..76cc1212f --- /dev/null +++ b/web_leaflet_draw_lib/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Ronald Portier (ronald@therp.nl) diff --git a/web_leaflet_draw_lib/readme/CREDITS.md b/web_leaflet_draw_lib/readme/CREDITS.md new file mode 100644 index 000000000..6162efd78 --- /dev/null +++ b/web_leaflet_draw_lib/readme/CREDITS.md @@ -0,0 +1 @@ +The module embed the leaflet.draw.js library. diff --git a/web_leaflet_draw_lib/readme/DESCRIPTION.md b/web_leaflet_draw_lib/readme/DESCRIPTION.md new file mode 100644 index 000000000..cf0b5616a --- /dev/null +++ b/web_leaflet_draw_lib/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +This module extends odoo to include Leaflet Draw Javascript library. + +The functions on this module can be used to add information to maps +shown with the Leaflet main library. + diff --git a/web_leaflet_draw_lib/static/description/icon.png b/web_leaflet_draw_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_draw_lib/static/description/index.html b/web_leaflet_draw_lib/static/description/index.html new file mode 100644 index 000000000..862b69b0f --- /dev/null +++ b/web_leaflet_draw_lib/static/description/index.html @@ -0,0 +1,432 @@ + + + + + +Leaflet Draw Javascript Library + + + +
+

Leaflet Draw Javascript Library

+ + +

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

+

This module extends odoo to include Leaflet Draw Javascript library.

+

The functions on this module can be used to add information to maps +shown with the Leaflet main library.

+

Table of contents

+ +
+

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

+
    +
  • Advance Insight
  • +
+
+
+

Contributors

+ +
+
+

Other credits

+

The module embed the leaflet.draw.js library.

+
+
+

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:

+

NL66278'

+

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_draw_lib/static/lib/leaflet.draw/images/spritesheet-2x.png b/web_leaflet_draw_lib/static/lib/leaflet.draw/images/spritesheet-2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c45231aff86b1344333414cdfa7a2a74d38d5304 GIT binary patch literal 3581 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 + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.css b/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.css new file mode 100644 index 000000000..01c06e6e1 --- /dev/null +++ b/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.css @@ -0,0 +1,305 @@ +/* ================================================================== */ +/* Toolbars +/* ================================================================== */ + +.leaflet-draw-section { + position: relative; +} + +.leaflet-draw-toolbar { + margin-top: 12px; +} + +.leaflet-draw-toolbar-top { + margin-top: 0; +} + +.leaflet-draw-toolbar-notop a:first-child { + border-top-right-radius: 0; +} + +.leaflet-draw-toolbar-nobottom a:last-child { + border-bottom-right-radius: 0; +} + +.leaflet-draw-toolbar a { + background-image: url('images/spritesheet.png'); + background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); + background-repeat: no-repeat; + background-size: 270px 30px; +} + +.leaflet-retina .leaflet-draw-toolbar a { + background-image: url('images/spritesheet-2x.png'); + background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg'); +} + +.leaflet-draw a { + display: block; + text-align: center; + text-decoration: none; +} + +/* ================================================================== */ +/* Toolbar actions menu +/* ================================================================== */ + +.leaflet-draw-actions { + display: none; + list-style: none; + margin: 0; + padding: 0; + position: absolute; + left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */ + top: 0; + white-space: nowrap; +} + +.leaflet-touch .leaflet-draw-actions { + left: 32px; +} + +.leaflet-right .leaflet-draw-actions { + right:26px; + left:auto; +} + +.leaflet-touch .leaflet-right .leaflet-draw-actions { + right:32px; + left:auto; +} + +.leaflet-draw-actions li { + display: inline-block; +} + +.leaflet-draw-actions li:first-child a { + border-left: none; +} + +.leaflet-draw-actions li:last-child a { + -webkit-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.leaflet-right .leaflet-draw-actions li:last-child a { + -webkit-border-radius: 0; + border-radius: 0; +} + +.leaflet-right .leaflet-draw-actions li:first-child a { + -webkit-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.leaflet-draw-actions a { + background-color: #919187; + border-left: 1px solid #AAA; + color: #FFF; + font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif; + line-height: 28px; + text-decoration: none; + padding-left: 10px; + padding-right: 10px; + height: 28px; +} + +.leaflet-touch .leaflet-draw-actions a { + font-size: 12px; + line-height: 30px; + height: 30px; +} + +.leaflet-draw-actions-bottom { + margin-top: 0; +} + +.leaflet-draw-actions-top { + margin-top: 1px; +} + +.leaflet-draw-actions-top a, +.leaflet-draw-actions-bottom a { + height: 27px; + line-height: 27px; +} + +.leaflet-draw-actions a:hover { + background-color: #A0A098; +} + +.leaflet-draw-actions-top.leaflet-draw-actions-bottom a { + height: 26px; + line-height: 26px; +} + +/* ================================================================== */ +/* Draw toolbar +/* ================================================================== */ + +.leaflet-draw-toolbar .leaflet-draw-draw-polyline { + background-position: -2px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline { + background-position: 0 -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-draw-polygon { + background-position: -31px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon { + background-position: -25px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-draw-rectangle { + background-position: -62px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle { + background-position: -53px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-draw-circle { + background-position: -92px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle { + background-position: -80px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-draw-marker { + background-position: -122px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker { + background-position: -107px -1px; +} + +/* ================================================================== */ +/* Edit toolbar +/* ================================================================== */ + +.leaflet-draw-toolbar .leaflet-draw-edit-edit { + background-position: -152px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit { + background-position: -133px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-edit-remove { + background-position: -182px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove { + background-position: -160px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { + background-position: -212px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled { + background-position: -187px -1px; +} + +.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { + background-position: -242px -2px; +} + +.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled { + background-position: -214px -2px; +} + +/* ================================================================== */ +/* Drawing styles +/* ================================================================== */ + +.leaflet-mouse-marker { + background-color: #fff; + cursor: crosshair; +} + +.leaflet-draw-tooltip { + background: rgb(54, 54, 54); + background: rgba(0, 0, 0, 0.5); + border: 1px solid transparent; + -webkit-border-radius: 4px; + border-radius: 4px; + color: #fff; + font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif; + margin-left: 20px; + margin-top: -21px; + padding: 4px 8px; + position: absolute; + visibility: hidden; + white-space: nowrap; + z-index: 6; +} + +.leaflet-draw-tooltip:before { + border-right: 6px solid black; + border-right-color: rgba(0, 0, 0, 0.5); + border-top: 6px solid transparent; + border-bottom: 6px solid transparent; + content: ""; + position: absolute; + top: 7px; + left: -7px; +} + +.leaflet-error-draw-tooltip { + background-color: #F2DEDE; + border: 1px solid #E6B6BD; + color: #B94A48; +} + +.leaflet-error-draw-tooltip:before { + border-right-color: #E6B6BD; +} + +.leaflet-draw-tooltip-single { + margin-top: -12px +} + +.leaflet-draw-tooltip-subtext { + color: #f8d5e4; +} + +.leaflet-draw-guide-dash { + font-size: 1%; + opacity: 0.6; + position: absolute; + width: 5px; + height: 5px; +} + +/* ================================================================== */ +/* Edit styles +/* ================================================================== */ + +.leaflet-edit-marker-selected { + background-color: rgba(254, 87, 161, 0.1); + border: 4px dashed rgba(254, 87, 161, 0.6); + -webkit-border-radius: 4px; + border-radius: 4px; + box-sizing: content-box; +} + +.leaflet-edit-move { + cursor: move; +} + +.leaflet-edit-resize { + cursor: pointer; +} + +/* ================================================================== */ +/* Old IE styles +/* ================================================================== */ + +.leaflet-oldie .leaflet-draw-toolbar { + border: 1px solid #999; +} diff --git a/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.js b/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.js new file mode 100644 index 000000000..580dac201 --- /dev/null +++ b/web_leaflet_draw_lib/static/lib/leaflet.draw/leaflet.draw.js @@ -0,0 +1,4201 @@ +/* + Leaflet.draw 0.4.2, a plugin that adds drawing and editing tools to Leaflet powered maps. + (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet + + https://github.com/Leaflet/Leaflet.draw + http://leafletjs.com + */ +(function (window, document, undefined) {/** + * Leaflet.draw assumes that you have already included the Leaflet library. + */ +L.drawVersion = "0.4.2"; +/** + * @class L.Draw + * @aka Draw + * + * + * To add the draw toolbar set the option drawControl: true in the map options. + * + * @example + * ```js + * var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13); + * + * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + * attribution: '© OpenStreetMap contributors' + * }).addTo(map); + * ``` + * + * ### Adding the edit toolbar + * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map. + * + * ```js + * var map = L.map('map').setView([51.505, -0.09], 13); + * + * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { + * attribution: '© OpenStreetMap contributors' + * }).addTo(map); + * + * // FeatureGroup is to store editable layers + * var drawnItems = new L.FeatureGroup(); + * map.addLayer(drawnItems); + * + * var drawControl = new L.Control.Draw({ + * edit: { + * featureGroup: drawnItems + * } + * }); + * map.addControl(drawControl); + * ``` + * + * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that + * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon. + * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon, + * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a + * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons). + */ +L.Draw = {}; + +/** + * @class L.drawLocal + * @aka L.drawLocal + * + * The core toolbar class of the API — it is used to create the toolbar ui + * + * @example + * ```js + * var modifiedDraw = L.drawLocal.extend({ + * draw: { + * toolbar: { + * buttons: { + * polygon: 'Draw an awesome polygon' + * } + * } + * } + * }); + * ``` + * + * The default state for the control is the draw toolbar just below the zoom control. + * This will allow map users to draw vectors and markers. + * **Please note the edit toolbar is not enabled by default.** + */ +L.drawLocal = { + draw: { + toolbar: { + // #TODO: this should be reorganized where actions are nested in actions + // ex: actions.undo or actions.cancel + actions: { + title: 'Cancel drawing', + text: 'Cancel' + }, + finish: { + title: 'Finish drawing', + text: 'Finish' + }, + undo: { + title: 'Delete last point drawn', + text: 'Delete last point' + }, + buttons: { + polyline: 'Draw a polyline', + polygon: 'Draw a polygon', + rectangle: 'Draw a rectangle', + circle: 'Draw a circle', + marker: 'Draw a marker' + } + }, + handlers: { + circle: { + tooltip: { + start: 'Click and drag to draw circle.' + }, + radius: 'Radius' + }, + marker: { + tooltip: { + start: 'Click map to place marker.' + } + }, + polygon: { + tooltip: { + start: 'Click to start drawing shape.', + cont: 'Click to continue drawing shape.', + end: 'Click first point to close this shape.' + } + }, + polyline: { + error: 'Error: shape edges cannot cross!', + tooltip: { + start: 'Click to start drawing line.', + cont: 'Click to continue drawing line.', + end: 'Click last point to finish line.' + } + }, + rectangle: { + tooltip: { + start: 'Click and drag to draw rectangle.' + } + }, + simpleshape: { + tooltip: { + end: 'Release mouse to finish drawing.' + } + } + } + }, + edit: { + toolbar: { + actions: { + save: { + title: 'Save changes.', + text: 'Save' + }, + cancel: { + title: 'Cancel editing, discards all changes.', + text: 'Cancel' + } + }, + buttons: { + edit: 'Edit layers.', + editDisabled: 'No layers to edit.', + remove: 'Delete layers.', + removeDisabled: 'No layers to delete.' + } + }, + handlers: { + edit: { + tooltip: { + text: 'Drag handles, or marker to edit feature.', + subtext: 'Click cancel to undo changes.' + } + }, + remove: { + tooltip: { + text: 'Click on a feature to remove' + } + } + } + } +}; + + + +/** + * ### Events + * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different + * actions users can initiate. The following events will be triggered on the map: + * + * @class L.Draw.Event + * @aka Draw.Event + * + * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct. + * + * @example + * ```js + * map.on(L.Draw.Event.CREATED; function (e) { + * var type = e.layerType; + * layer = e.layer; + * + * if (type === 'marker') { + * // Do marker specific actions + * } + * + * // Do whatever else you need to. (save to db; add to map etc) + * map.addLayer(layer); + *}); + * ``` + */ +L.Draw.Event = {}; +/** + * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String + * + * Layer that was just created. + * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker` + * Triggered when a new vector or marker has been created. + * + */ +L.Draw.Event.CREATED = 'draw:created'; + +/** + * @event draw:edited: LayerGroup + * + * List of all layers just edited on the map. + * + * + * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved. + * + * @example + * ```js + * map.on('draw:edited'; function (e) { + * var layers = e.layers; + * layers.eachLayer(function (layer) { + * //do whatever you want; most likely save back to db + * }); + * }); + * ``` + */ +L.Draw.Event.EDITED = 'draw:edited'; + +/** + * @event draw:deleted: LayerGroup + * + * List of all layers just removed from the map. + * + * Triggered when layers have been removed (and saved) from the FeatureGroup. + */ +L.Draw.Event.DELETED = 'draw:deleted'; + +/** + * @event draw:drawstart: String + * + * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker` + * + * Triggered when the user has chosen to draw a particular vector or marker. + */ +L.Draw.Event.DRAWSTART = 'draw:drawstart'; + +/** + * @event draw:drawstop: String + * + * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker` + * + * Triggered when the user has finished a particular vector or marker. + */ + +L.Draw.Event.DRAWSTOP = 'draw:drawstop'; + +/** + * @event draw:drawvertex: LayerGroup + * + * List of all layers just being added from the map. + * + * Triggered when a vertex is created on a polyline or polygon. + */ +L.Draw.Event.DRAWVERTEX = 'draw:drawvertex'; + +/** + * @event draw:editstart: String + * + * The type of edit this is. One of: `edit` + * + * Triggered when the user starts edit mode by clicking the edit tool button. + */ + +L.Draw.Event.EDITSTART = 'draw:editstart'; + +/** + * @event draw:editmove: ILayer + * + * Layer that was just moved. + * + * Triggered as the user moves a rectangle; circle or marker. + */ +L.Draw.Event.EDITMOVE = 'draw:editmove'; + +/** + * @event draw:editresize: ILayer + * + * Layer that was just moved. + * + * Triggered as the user resizes a rectangle or circle. + */ +L.Draw.Event.EDITRESIZE = 'draw:editresize'; + +/** + * @event draw:editvertex: LayerGroup + * + * List of all layers just being edited from the map. + * + * Triggered when a vertex is edited on a polyline or polygon. + */ +L.Draw.Event.EDITVERTEX = 'draw:editvertex'; + +/** + * @event draw:editstop: String + * + * The type of edit this is. One of: `edit` + * + * Triggered when the user has finshed editing (edit mode) and saves edits. + */ +L.Draw.Event.EDITSTOP = 'draw:editstop'; + +/** + * @event draw:deletestart: String + * + * The type of edit this is. One of: `remove` + * + * Triggered when the user starts remove mode by clicking the remove tool button. + */ +L.Draw.Event.DELETESTART = 'draw:deletestart'; + +/** + * @event draw:deletestop: String + * + * The type of edit this is. One of: `remove` + * + * Triggered when the user has finished removing shapes (remove mode) and saves. + */ +L.Draw.Event.DELETESTOP = 'draw:deletestop'; + + + +L.Draw = L.Draw || {}; + +/** + * @class L.Draw.Feature + * @aka Draw.Feature + */ +L.Draw.Feature = L.Handler.extend({ + includes: L.Mixin.Events, + + // @method initialize(): void + initialize: function (map, options) { + this._map = map; + this._container = map._container; + this._overlayPane = map._panes.overlayPane; + this._popupPane = map._panes.popupPane; + + // Merge default shapeOptions options with custom shapeOptions + if (options && options.shapeOptions) { + options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions); + } + L.setOptions(this, options); + }, + + // @method enable(): void + enable: function () { + if (this._enabled) { return; } + + L.Handler.prototype.enable.call(this); + + this.fire('enabled', { handler: this.type }); + + this._map.fire(L.Draw.Event.DRAWSTART, { layerType: this.type }); + }, + + // @method initialize(): void + disable: function () { + if (!this._enabled) { return; } + + L.Handler.prototype.disable.call(this); + + this._map.fire(L.Draw.Event.DRAWSTOP, { layerType: this.type }); + + this.fire('disabled', { handler: this.type }); + }, + + // @method addHooks(): void + addHooks: function () { + var map = this._map; + + if (map) { + L.DomUtil.disableTextSelection(); + + map.getContainer().focus(); + + this._tooltip = new L.Draw.Tooltip(this._map); + + L.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + if (this._map) { + L.DomUtil.enableTextSelection(); + + this._tooltip.dispose(); + this._tooltip = null; + + L.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this); + } + }, + + // @method setOptions(): void + setOptions: function (options) { + L.setOptions(this, options); + }, + + _fireCreatedEvent: function (layer) { + this._map.fire(L.Draw.Event.CREATED, { layer: layer, layerType: this.type }); + }, + + // Cancel drawing when the escape key is pressed + _cancelDrawing: function (e) { + this._map.fire('draw:canceled', { layerType: this.type }); + if (e.keyCode === 27) { + this.disable(); + } + } +}); + + + +/** + * @class L.Draw.Polyline + * @aka Draw.Polyline + * @inherits L.Draw.Feature + */ +L.Draw.Polyline = L.Draw.Feature.extend({ + statics: { + TYPE: 'polyline' + }, + + Poly: L.Polyline, + + options: { + allowIntersection: true, + repeatMode: false, + drawError: { + color: '#b00b00', + timeout: 2500 + }, + icon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: 'leaflet-div-icon leaflet-editing-icon' + }), + touchIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon' + }), + guidelineDistance: 20, + maxGuideLineLength: 4000, + shapeOptions: { + stroke: true, + color: '#f06eaa', + weight: 4, + opacity: 0.5, + fill: false, + clickable: true + }, + metric: true, // Whether to use the metric measurement system or imperial + feet: true, // When not metric, to use feet instead of yards for display. + showLength: true, // Whether to display distance in the tooltip + zIndexOffset: 2000 // This should be > than the highest z-index any map layers + }, + + // @method initialize(): void + initialize: function (map, options) { + // if touch, switch to touch icon + if (L.Browser.touch) { + this.options.icon = this.options.touchIcon; + } + + // Need to set this here to ensure the correct message is used. + this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error; + + // Merge default drawError options with custom options + if (options && options.drawError) { + options.drawError = L.Util.extend({}, this.options.drawError, options.drawError); + } + + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.Draw.Polyline.TYPE; + + L.Draw.Feature.prototype.initialize.call(this, map, options); + }, + + // @method addHooks(): void + addHooks: function () { + L.Draw.Feature.prototype.addHooks.call(this); + if (this._map) { + this._markers = []; + + this._markerGroup = new L.LayerGroup(); + this._map.addLayer(this._markerGroup); + + this._poly = new L.Polyline([], this.options.shapeOptions); + + this._tooltip.updateContent(this._getTooltipText()); + + // Make a transparent marker that will used to catch click events. These click + // events will create the vertices. We need to do this so we can ensure that + // we can create vertices over other map layers (markers, vector layers). We + // also do not want to trigger any click handlers of objects we are clicking on + // while drawing. + if (!this._mouseMarker) { + this._mouseMarker = L.marker(this._map.getCenter(), { + icon: L.divIcon({ + className: 'leaflet-mouse-marker', + iconAnchor: [20, 20], + iconSize: [40, 40] + }), + opacity: 0, + zIndexOffset: this.options.zIndexOffset + }); + } + + if (!L.Browser.touch) { + this._map.on('mouseup', this._onMouseUp, this); // Necessary for 0.7 compatibility + } + + this._mouseMarker + .on('mousedown', this._onMouseDown, this) + .on('mouseout', this._onMouseOut, this) + .on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility + .on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter + .addTo(this._map); + + this._map + .on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility + .on('mousemove', this._onMouseMove, this) + .on('zoomlevelschange', this._onZoomEnd, this) + .on('click', this._onTouch, this) + .on('zoomend', this._onZoomEnd, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + L.Draw.Feature.prototype.removeHooks.call(this); + + this._clearHideErrorTimeout(); + + this._cleanUpShape(); + + // remove markers from map + this._map.removeLayer(this._markerGroup); + delete this._markerGroup; + delete this._markers; + + this._map.removeLayer(this._poly); + delete this._poly; + + this._mouseMarker + .off('mousedown', this._onMouseDown, this) + .off('mouseout', this._onMouseOut, this) + .off('mouseup', this._onMouseUp, this) + .off('mousemove', this._onMouseMove, this); + this._map.removeLayer(this._mouseMarker); + delete this._mouseMarker; + + // clean up DOM + this._clearGuides(); + + this._map + .off('mouseup', this._onMouseUp, this) + .off('mousemove', this._onMouseMove, this) + .off('zoomlevelschange', this._onZoomEnd, this) + .off('zoomend', this._onZoomEnd, this) + .off('click', this._onTouch, this); + }, + + // @method deleteLastVertex(): void + deleteLastVertex: function () { + if (this._markers.length <= 1) { + return; + } + + var lastMarker = this._markers.pop(), + poly = this._poly, + // Replaces .spliceLatLngs() + latlngs = poly.getLatLngs(), + latlng = latlngs.splice(-1, 1)[0]; + this._poly.setLatLngs(latlngs); + + this._markerGroup.removeLayer(lastMarker); + + if (poly.getLatLngs().length < 2) { + this._map.removeLayer(poly); + } + + this._vertexChanged(latlng, false); + }, + + // @method addVertex(): void + addVertex: function (latlng) { + var markersLength = this._markers.length; + + if (markersLength > 0 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) { + this._showErrorTooltip(); + return; + } + else if (this._errorShown) { + this._hideErrorTooltip(); + } + + this._markers.push(this._createMarker(latlng)); + + this._poly.addLatLng(latlng); + + if (this._poly.getLatLngs().length === 2) { + this._map.addLayer(this._poly); + } + + this._vertexChanged(latlng, true); + }, + + // @method completeShape(): void + completeShape: function () { + if (this._markers.length <= 1) { + return; + } + + this._fireCreatedEvent(); + this.disable(); + + if (this.options.repeatMode) { + this.enable(); + } + }, + + _finishShape: function () { + var latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs(); + var intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]); + + if ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) { + this._showErrorTooltip(); + return; + } + + this._fireCreatedEvent(); + this.disable(); + if (this.options.repeatMode) { + this.enable(); + } + }, + + //Called to verify the shape is valid when the user tries to finish it + //Return false if the shape is not valid + _shapeIsValid: function () { + return true; + }, + + _onZoomEnd: function () { + if (this._markers !== null) { + this._updateGuide(); + } + }, + + _onMouseMove: function (e) { + var newPos = this._map.mouseEventToLayerPoint(e.originalEvent); + var latlng = this._map.layerPointToLatLng(newPos); + + // Save latlng + // should this be moved to _updateGuide() ? + this._currentLatLng = latlng; + + this._updateTooltip(latlng); + + // Update the guide line + this._updateGuide(newPos); + + // Update the mouse marker position + this._mouseMarker.setLatLng(latlng); + + L.DomEvent.preventDefault(e.originalEvent); + }, + + _vertexChanged: function (latlng, added) { + this._map.fire(L.Draw.Event.DRAWVERTEX, { layers: this._markerGroup }); + this._updateFinishHandler(); + + this._updateRunningMeasure(latlng, added); + + this._clearGuides(); + + this._updateTooltip(); + }, + + _onMouseDown: function (e) { + var originalEvent = e.originalEvent; + this._mouseDownOrigin = L.point(originalEvent.clientX, originalEvent.clientY); + }, + + _onMouseUp: function (e) { + if (this._mouseDownOrigin) { + // We detect clicks within a certain tolerance, otherwise let it + // be interpreted as a drag by the map + var distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY) + .distanceTo(this._mouseDownOrigin); + if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) { + this.addVertex(e.latlng); + } + } + this._mouseDownOrigin = null; + }, + + _onTouch: function (e) { + // #TODO: use touchstart and touchend vs using click(touch start & end). + if (L.Browser.touch) { // #TODO: get rid of this once leaflet fixes their click/touch. + this._onMouseDown(e); + this._onMouseUp(e); + } + }, + + _onMouseOut: function () { + if (this._tooltip) { + this._tooltip._onMouseOut.call(this._tooltip); + } + }, + + _updateFinishHandler: function () { + var markerCount = this._markers.length; + // The last marker should have a click handler to close the polyline + if (markerCount > 1) { + this._markers[markerCount - 1].on('click', this._finishShape, this); + } + + // Remove the old marker click handler (as only the last point should close the polyline) + if (markerCount > 2) { + this._markers[markerCount - 2].off('click', this._finishShape, this); + } + }, + + _createMarker: function (latlng) { + var marker = new L.Marker(latlng, { + icon: this.options.icon, + zIndexOffset: this.options.zIndexOffset * 2 + }); + + this._markerGroup.addLayer(marker); + + return marker; + }, + + _updateGuide: function (newPos) { + var markerCount = this._markers ? this._markers.length : 0; + + if (markerCount > 0) { + newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng); + + // draw the guide line + this._clearGuides(); + this._drawGuide( + this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()), + newPos + ); + } + }, + + _updateTooltip: function (latLng) { + var text = this._getTooltipText(); + + if (latLng) { + this._tooltip.updatePosition(latLng); + } + + if (!this._errorShown) { + this._tooltip.updateContent(text); + } + }, + + _drawGuide: function (pointA, pointB) { + var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))), + guidelineDistance = this.options.guidelineDistance, + maxGuideLineLength = this.options.maxGuideLineLength, + // Only draw a guideline with a max length + i = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance, + fraction, + dashPoint, + dash; + + //create the guides container if we haven't yet + if (!this._guidesContainer) { + this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane); + } + + //draw a dash every GuildeLineDistance + for (; i < length; i += this.options.guidelineDistance) { + //work out fraction along line we are + fraction = i / length; + + //calculate new x,y point + dashPoint = { + x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)), + y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y)) + }; + + //add guide dash to guide container + dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer); + dash.style.backgroundColor = + !this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color; + + L.DomUtil.setPosition(dash, dashPoint); + } + }, + + _updateGuideColor: function (color) { + if (this._guidesContainer) { + for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) { + this._guidesContainer.childNodes[i].style.backgroundColor = color; + } + } + }, + + // removes all child elements (guide dashes) from the guides container + _clearGuides: function () { + if (this._guidesContainer) { + while (this._guidesContainer.firstChild) { + this._guidesContainer.removeChild(this._guidesContainer.firstChild); + } + } + }, + + _getTooltipText: function () { + var showLength = this.options.showLength, + labelText, distanceStr; + + if (this._markers.length === 0) { + labelText = { + text: L.drawLocal.draw.handlers.polyline.tooltip.start + }; + } else { + distanceStr = showLength ? this._getMeasurementString() : ''; + + if (this._markers.length === 1) { + labelText = { + text: L.drawLocal.draw.handlers.polyline.tooltip.cont, + subtext: distanceStr + }; + } else { + labelText = { + text: L.drawLocal.draw.handlers.polyline.tooltip.end, + subtext: distanceStr + }; + } + } + return labelText; + }, + + _updateRunningMeasure: function (latlng, added) { + var markersLength = this._markers.length, + previousMarkerIndex, distance; + + if (this._markers.length === 1) { + this._measurementRunningTotal = 0; + } else { + previousMarkerIndex = markersLength - (added ? 2 : 1); + distance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng()); + + this._measurementRunningTotal += distance * (added ? 1 : -1); + } + }, + + _getMeasurementString: function () { + var currentLatLng = this._currentLatLng, + previousLatLng = this._markers[this._markers.length - 1].getLatLng(), + distance; + + // calculate the distance from the last fixed point to the mouse position + distance = this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng); + + return L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet); + }, + + _showErrorTooltip: function () { + this._errorShown = true; + + // Update tooltip + this._tooltip + .showAsError() + .updateContent({ text: this.options.drawError.message }); + + // Update shape + this._updateGuideColor(this.options.drawError.color); + this._poly.setStyle({ color: this.options.drawError.color }); + + // Hide the error after 2 seconds + this._clearHideErrorTimeout(); + this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout); + }, + + _hideErrorTooltip: function () { + this._errorShown = false; + + this._clearHideErrorTimeout(); + + // Revert tooltip + this._tooltip + .removeError() + .updateContent(this._getTooltipText()); + + // Revert shape + this._updateGuideColor(this.options.shapeOptions.color); + this._poly.setStyle({ color: this.options.shapeOptions.color }); + }, + + _clearHideErrorTimeout: function () { + if (this._hideErrorTimeout) { + clearTimeout(this._hideErrorTimeout); + this._hideErrorTimeout = null; + } + }, + + _cleanUpShape: function () { + if (this._markers.length > 1) { + this._markers[this._markers.length - 1].off('click', this._finishShape, this); + } + }, + + _fireCreatedEvent: function () { + var poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions); + L.Draw.Feature.prototype._fireCreatedEvent.call(this, poly); + } +}); + + + +/** + * @class L.Draw.Polygon + * @aka Draw.Polygon + * @inherits L.Draw.Polyline + */ +L.Draw.Polygon = L.Draw.Polyline.extend({ + statics: { + TYPE: 'polygon' + }, + + Poly: L.Polygon, + + options: { + showArea: false, + shapeOptions: { + stroke: true, + color: '#f06eaa', + weight: 4, + opacity: 0.5, + fill: true, + fillColor: null, //same as color by default + fillOpacity: 0.2, + clickable: true + }, + metric: true // Whether to use the metric measurement system or imperial + }, + + // @method initialize(): void + initialize: function (map, options) { + L.Draw.Polyline.prototype.initialize.call(this, map, options); + + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.Draw.Polygon.TYPE; + }, + + _updateFinishHandler: function () { + var markerCount = this._markers.length; + + // The first marker should have a click handler to close the polygon + if (markerCount === 1) { + this._markers[0].on('click', this._finishShape, this); + } + + // Add and update the double click handler + if (markerCount > 2) { + this._markers[markerCount - 1].on('dblclick', this._finishShape, this); + // Only need to remove handler if has been added before + if (markerCount > 3) { + this._markers[markerCount - 2].off('dblclick', this._finishShape, this); + } + } + }, + + _getTooltipText: function () { + var text, subtext; + + if (this._markers.length === 0) { + text = L.drawLocal.draw.handlers.polygon.tooltip.start; + } else if (this._markers.length < 3) { + text = L.drawLocal.draw.handlers.polygon.tooltip.cont; + } else { + text = L.drawLocal.draw.handlers.polygon.tooltip.end; + subtext = this._getMeasurementString(); + } + + return { + text: text, + subtext: subtext + }; + }, + + _getMeasurementString: function () { + var area = this._area; + + if (!area) { + return null; + } + + return L.GeometryUtil.readableArea(area, this.options.metric); + }, + + _shapeIsValid: function () { + return this._markers.length >= 3; + }, + + _vertexChanged: function (latlng, added) { + var latLngs; + + // Check to see if we should show the area + if (!this.options.allowIntersection && this.options.showArea) { + latLngs = this._poly.getLatLngs(); + + this._area = L.GeometryUtil.geodesicArea(latLngs); + } + + L.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added); + }, + + _cleanUpShape: function () { + var markerCount = this._markers.length; + + if (markerCount > 0) { + this._markers[0].off('click', this._finishShape, this); + + if (markerCount > 2) { + this._markers[markerCount - 1].off('dblclick', this._finishShape, this); + } + } + } +}); + + + +L.SimpleShape = {}; +/** + * @class L.Draw.SimpleShape + * @aka Draw.SimpleShape + * @inherits L.Draw.Feature + */ +L.Draw.SimpleShape = L.Draw.Feature.extend({ + options: { + repeatMode: false + }, + + // @method initialize(): void + initialize: function (map, options) { + this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end; + + L.Draw.Feature.prototype.initialize.call(this, map, options); + }, + + // @method addHooks(): void + addHooks: function () { + L.Draw.Feature.prototype.addHooks.call(this); + if (this._map) { + this._mapDraggable = this._map.dragging.enabled(); + + if (this._mapDraggable) { + this._map.dragging.disable(); + } + + //TODO refactor: move cursor to styles + this._container.style.cursor = 'crosshair'; + + this._tooltip.updateContent({ text: this._initialLabelText }); + + this._map + .on('mousedown', this._onMouseDown, this) + .on('mousemove', this._onMouseMove, this) + .on('touchstart', this._onMouseDown, this) + .on('touchmove', this._onMouseMove, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + L.Draw.Feature.prototype.removeHooks.call(this); + if (this._map) { + if (this._mapDraggable) { + this._map.dragging.enable(); + } + + //TODO refactor: move cursor to styles + this._container.style.cursor = ''; + + this._map + .off('mousedown', this._onMouseDown, this) + .off('mousemove', this._onMouseMove, this) + .off('touchstart', this._onMouseDown, this) + .off('touchmove', this._onMouseMove, this); + + L.DomEvent.off(document, 'mouseup', this._onMouseUp, this); + L.DomEvent.off(document, 'touchend', this._onMouseUp, this); + + // If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return + if (this._shape) { + this._map.removeLayer(this._shape); + delete this._shape; + } + } + this._isDrawing = false; + }, + + _getTooltipText: function () { + return { + text: this._endLabelText + }; + }, + + _onMouseDown: function (e) { + this._isDrawing = true; + this._startLatLng = e.latlng; + + L.DomEvent + .on(document, 'mouseup', this._onMouseUp, this) + .on(document, 'touchend', this._onMouseUp, this) + .preventDefault(e.originalEvent); + }, + + _onMouseMove: function (e) { + var latlng = e.latlng; + + this._tooltip.updatePosition(latlng); + if (this._isDrawing) { + this._tooltip.updateContent(this._getTooltipText()); + this._drawShape(latlng); + } + }, + + _onMouseUp: function () { + if (this._shape) { + this._fireCreatedEvent(); + } + + this.disable(); + if (this.options.repeatMode) { + this.enable(); + } + } +}); + + +/** + * @class L.Draw.Rectangle + * @aka Draw.Rectangle + * @inherits L.Draw.SimpleShape + */ +L.Draw.Rectangle = L.Draw.SimpleShape.extend({ + statics: { + TYPE: 'rectangle' + }, + + options: { + shapeOptions: { + stroke: true, + color: '#f06eaa', + weight: 4, + opacity: 0.5, + fill: true, + fillColor: null, //same as color by default + fillOpacity: 0.2, + clickable: true + }, + metric: true // Whether to use the metric measurement system or imperial + }, + + // @method initialize(): void + initialize: function (map, options) { + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.Draw.Rectangle.TYPE; + + this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start; + + L.Draw.SimpleShape.prototype.initialize.call(this, map, options); + }, + + _drawShape: function (latlng) { + if (!this._shape) { + this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions); + this._map.addLayer(this._shape); + } else { + this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng)); + } + }, + + _fireCreatedEvent: function () { + var rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions); + L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle); + }, + + _getTooltipText: function () { + var tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this), + shape = this._shape, + latLngs, area, subtext; + + if (shape) { + latLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(); + area = L.GeometryUtil.geodesicArea(latLngs); + subtext = L.GeometryUtil.readableArea(area, this.options.metric); + } + + return { + text: tooltipText.text, + subtext: subtext + }; + } +}); + + + +/** + * @class L.Draw.Circle + * @aka Draw.Circle + * @inherits L.Draw.SimpleShape + */ +L.Draw.Circle = L.Draw.SimpleShape.extend({ + statics: { + TYPE: 'circle' + }, + + options: { + shapeOptions: { + stroke: true, + color: '#f06eaa', + weight: 4, + opacity: 0.5, + fill: true, + fillColor: null, //same as color by default + fillOpacity: 0.2, + clickable: true + }, + showRadius: true, + metric: true, // Whether to use the metric measurement system or imperial + feet: true // When not metric, use feet instead of yards for display + }, + + // @method initialize(): void + initialize: function (map, options) { + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.Draw.Circle.TYPE; + + this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start; + + L.Draw.SimpleShape.prototype.initialize.call(this, map, options); + }, + + _drawShape: function (latlng) { + if (!this._shape) { + this._shape = new L.Circle(this._startLatLng, this._startLatLng.distanceTo(latlng), this.options.shapeOptions); + this._map.addLayer(this._shape); + } else { + this._shape.setRadius(this._startLatLng.distanceTo(latlng)); + } + }, + + _fireCreatedEvent: function () { + var circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions); + L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle); + }, + + _onMouseMove: function (e) { + var latlng = e.latlng, + showRadius = this.options.showRadius, + useMetric = this.options.metric, + radius; + + this._tooltip.updatePosition(latlng); + if (this._isDrawing) { + this._drawShape(latlng); + + // Get the new radius (rounded to 1 dp) + radius = this._shape.getRadius().toFixed(1); + + this._tooltip.updateContent({ + text: this._endLabelText, + subtext: showRadius ? L.drawLocal.draw.handlers.circle.radius + ': ' + + L.GeometryUtil.readableDistance(radius, useMetric, this.options.feet) : '' + }); + } + } +}); + + + +/** + * @class L.Draw.Marker + * @aka Draw.Marker + * @inherits L.Draw.Feature + */ +L.Draw.Marker = L.Draw.Feature.extend({ + statics: { + TYPE: 'marker' + }, + + options: { + icon: new L.Icon.Default(), + repeatMode: false, + zIndexOffset: 2000 // This should be > than the highest z-index any markers + }, + + // @method initialize(): void + initialize: function (map, options) { + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.Draw.Marker.TYPE; + + L.Draw.Feature.prototype.initialize.call(this, map, options); + }, + + // @method addHooks(): void + addHooks: function () { + L.Draw.Feature.prototype.addHooks.call(this); + + if (this._map) { + this._tooltip.updateContent({ text: L.drawLocal.draw.handlers.marker.tooltip.start }); + + // Same mouseMarker as in Draw.Polyline + if (!this._mouseMarker) { + this._mouseMarker = L.marker(this._map.getCenter(), { + icon: L.divIcon({ + className: 'leaflet-mouse-marker', + iconAnchor: [20, 20], + iconSize: [40, 40] + }), + opacity: 0, + zIndexOffset: this.options.zIndexOffset + }); + } + + this._mouseMarker + .on('click', this._onClick, this) + .addTo(this._map); + + this._map.on('mousemove', this._onMouseMove, this); + this._map.on('click', this._onTouch, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + L.Draw.Feature.prototype.removeHooks.call(this); + + if (this._map) { + if (this._marker) { + this._marker.off('click', this._onClick, this); + this._map + .off('click', this._onClick, this) + .off('click', this._onTouch, this) + .removeLayer(this._marker); + delete this._marker; + } + + this._mouseMarker.off('click', this._onClick, this); + this._map.removeLayer(this._mouseMarker); + delete this._mouseMarker; + + this._map.off('mousemove', this._onMouseMove, this); + } + }, + + _onMouseMove: function (e) { + var latlng = e.latlng; + + this._tooltip.updatePosition(latlng); + this._mouseMarker.setLatLng(latlng); + + if (!this._marker) { + this._marker = new L.Marker(latlng, { + icon: this.options.icon, + zIndexOffset: this.options.zIndexOffset + }); + // Bind to both marker and map to make sure we get the click event. + this._marker.on('click', this._onClick, this); + this._map + .on('click', this._onClick, this) + .addLayer(this._marker); + } + else { + latlng = this._mouseMarker.getLatLng(); + this._marker.setLatLng(latlng); + } + }, + + _onClick: function () { + this._fireCreatedEvent(); + + this.disable(); + if (this.options.repeatMode) { + this.enable(); + } + }, + + _onTouch: function (e) { + // called on click & tap, only really does any thing on tap + this._onMouseMove(e); // creates & places marker + this._onClick(); // permanently places marker & ends interaction + }, + + _fireCreatedEvent: function () { + var marker = new L.Marker.Touch(this._marker.getLatLng(), { icon: this.options.icon }); + L.Draw.Feature.prototype._fireCreatedEvent.call(this, marker); + } +}); + + + +L.Edit = L.Edit || {}; + +/** + * @class L.Edit.Marker + * @aka Edit.Marker + */ +L.Edit.Marker = L.Handler.extend({ + // @method initialize(): void + initialize: function (marker, options) { + this._marker = marker; + L.setOptions(this, options); + }, + + // @method addHooks(): void + addHooks: function () { + var marker = this._marker; + + marker.dragging.enable(); + marker.on('dragend', this._onDragEnd, marker); + this._toggleMarkerHighlight(); + }, + + // @method removeHooks(): void + removeHooks: function () { + var marker = this._marker; + + marker.dragging.disable(); + marker.off('dragend', this._onDragEnd, marker); + this._toggleMarkerHighlight(); + }, + + _onDragEnd: function (e) { + var layer = e.target; + layer.edited = true; + this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer}); + }, + + _toggleMarkerHighlight: function () { + var icon = this._marker._icon; + + + // Don't do anything if this layer is a marker but doesn't have an icon. Markers + // should usually have icons. If using Leaflet.draw with Leaflet.markercluster there + // is a chance that a marker doesn't. + if (!icon) { + return; + } + + // This is quite naughty, but I don't see another way of doing it. (short of setting a new icon) + icon.style.display = 'none'; + + if (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) { + L.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected'); + // Offset as the border will make the icon move. + this._offsetMarker(icon, -4); + + } else { + L.DomUtil.addClass(icon, 'leaflet-edit-marker-selected'); + // Offset as the border will make the icon move. + this._offsetMarker(icon, 4); + } + + icon.style.display = ''; + }, + + _offsetMarker: function (icon, offset) { + var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset, + iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset; + + icon.style.marginTop = iconMarginTop + 'px'; + icon.style.marginLeft = iconMarginLeft + 'px'; + } +}); + +L.Marker.addInitHook(function () { + if (L.Edit.Marker) { + this.editing = new L.Edit.Marker(this); + + if (this.options.editable) { + this.editing.enable(); + } + } +}); + + + +L.Edit = L.Edit || {}; + +/** + * @class L.Edit.Polyline + * @aka L.Edit.Poly + * @aka Edit.Poly + */ +L.Edit.Poly = L.Handler.extend({ + options: {}, + + // @method initialize(): void + initialize: function (poly, options) { + + this.latlngs = [poly._latlngs]; + if (poly._holes) { + this.latlngs = this.latlngs.concat(poly._holes); + } + + this._poly = poly; + L.setOptions(this, options); + + this._poly.on('revert-edited', this._updateLatLngs, this); + }, + + // Compatibility method to normalize Poly* objects + // between 0.7.x and 1.0+ + _defaultShape: function () { + if (!L.Polyline._flat) { return this._poly._latlngs; } + return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0]; + }, + + _eachVertexHandler: function (callback) { + for (var i = 0; i < this._verticesHandlers.length; i++) { + callback(this._verticesHandlers[i]); + } + }, + + // @method addHooks(): void + addHooks: function () { + this._initHandlers(); + this._eachVertexHandler(function (handler) { + handler.addHooks(); + }); + }, + + // @method removeHooks(): void + removeHooks: function () { + this._eachVertexHandler(function (handler) { + handler.removeHooks(); + }); + }, + + // @method updateMarkers(): void + updateMarkers: function () { + this._eachVertexHandler(function (handler) { + handler.updateMarkers(); + }); + }, + + _initHandlers: function () { + this._verticesHandlers = []; + for (var i = 0; i < this.latlngs.length; i++) { + this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this.options)); + } + }, + + _updateLatLngs: function (e) { + this.latlngs = [e.layer._latlngs]; + if (e.layer._holes) { + this.latlngs = this.latlngs.concat(e.layer._holes); + } + } + +}); + +/** + * @class L.Edit.PolyVerticesEdit + * @aka Edit.PolyVerticesEdit + */ +L.Edit.PolyVerticesEdit = L.Handler.extend({ + options: { + icon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: 'leaflet-div-icon leaflet-editing-icon' + }), + touchIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon' + }), + drawError: { + color: '#b00b00', + timeout: 1000 + } + + + }, + + // @method intialize(): void + initialize: function (poly, latlngs, options) { + // if touch, switch to touch icon + if (L.Browser.touch) { + this.options.icon = this.options.touchIcon; + } + this._poly = poly; + + if (options && options.drawError) { + options.drawError = L.Util.extend({}, this.options.drawError, options.drawError); + } + + this._latlngs = latlngs; + + L.setOptions(this, options); + }, + + // Compatibility method to normalize Poly* objects + // between 0.7.x and 1.0+ + _defaultShape: function () { + if (!L.Polyline._flat) { return this._latlngs; } + return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0]; + }, + + // @method addHooks(): void + addHooks: function () { + var poly = this._poly; + + if (!(poly instanceof L.Polygon)) { + poly.options.fill = false; + if (poly.options.editing) { + poly.options.editing.fill = false; + } + } + + poly.setStyle(poly.options.editing); + + if (this._poly._map) { + + this._map = this._poly._map; // Set map + + if (!this._markerGroup) { + this._initMarkers(); + } + this._poly._map.addLayer(this._markerGroup); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + var poly = this._poly; + + poly.setStyle(poly.options.original); + + if (poly._map) { + poly._map.removeLayer(this._markerGroup); + delete this._markerGroup; + delete this._markers; + } + }, + + // @method updateMarkers(): void + updateMarkers: function () { + this._markerGroup.clearLayers(); + this._initMarkers(); + }, + + _initMarkers: function () { + if (!this._markerGroup) { + this._markerGroup = new L.LayerGroup(); + } + this._markers = []; + + var latlngs = this._defaultShape(), + i, j, len, marker; + + for (i = 0, len = latlngs.length; i < len; i++) { + + marker = this._createMarker(latlngs[i], i); + marker.on('click', this._onMarkerClick, this); + this._markers.push(marker); + } + + var markerLeft, markerRight; + + for (i = 0, j = len - 1; i < len; j = i++) { + if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) { + continue; + } + + markerLeft = this._markers[j]; + markerRight = this._markers[i]; + + this._createMiddleMarker(markerLeft, markerRight); + this._updatePrevNext(markerLeft, markerRight); + } + }, + + _createMarker: function (latlng, index) { + // Extending L.Marker in TouchEvents.js to include touch. + var marker = new L.Marker.Touch(latlng, { + draggable: true, + icon: this.options.icon, + }); + + marker._origLatLng = latlng; + marker._index = index; + + marker + .on('dragstart', this._onMarkerDragStart, this) + .on('drag', this._onMarkerDrag, this) + .on('dragend', this._fireEdit, this) + .on('touchmove', this._onTouchMove, this) + .on('touchend', this._fireEdit, this) + .on('MSPointerMove', this._onTouchMove, this) + .on('MSPointerUp', this._fireEdit, this); + + this._markerGroup.addLayer(marker); + + return marker; + }, + + _onMarkerDragStart: function () { + this._poly.fire('editstart'); + }, + + _spliceLatLngs: function () { + var latlngs = this._defaultShape(); + var removed = [].splice.apply(latlngs, arguments); + this._poly._convertLatLngs(latlngs, true); + this._poly.redraw(); + return removed; + }, + + _removeMarker: function (marker) { + var i = marker._index; + + this._markerGroup.removeLayer(marker); + this._markers.splice(i, 1); + this._spliceLatLngs(i, 1); + this._updateIndexes(i, -1); + + marker + .off('dragstart', this._onMarkerDragStart, this) + .off('drag', this._onMarkerDrag, this) + .off('dragend', this._fireEdit, this) + .off('touchmove', this._onMarkerDrag, this) + .off('touchend', this._fireEdit, this) + .off('click', this._onMarkerClick, this) + .off('MSPointerMove', this._onTouchMove, this) + .off('MSPointerUp', this._fireEdit, this); + }, + + _fireEdit: function () { + this._poly.edited = true; + this._poly.fire('edit'); + this._poly._map.fire(L.Draw.Event.EDITVERTEX, { layers: this._markerGroup }); + }, + + _onMarkerDrag: function (e) { + var marker = e.target; + var poly = this._poly; + + L.extend(marker._origLatLng, marker._latlng); + + if (marker._middleLeft) { + marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); + } + if (marker._middleRight) { + marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); + } + + if (poly.options.poly) { + var tooltip = poly._map._editTooltip; // Access the tooltip + + // If we don't allow intersections and the polygon intersects + if (!poly.options.poly.allowIntersection && poly.intersects()) { + + var originalColor = poly.options.color; + poly.setStyle({ color: this.options.drawError.color }); + + // Manually trigger 'dragend' behavior on marker we are about to remove + // WORKAROUND: introduced in 1.0.0-rc2, may be related to #4484 + if (L.version.indexOf('0.7') !== 0) { + marker.dragging._draggable._onUp(e); + } + this._onMarkerClick(e); // Remove violating marker + // FIXME: Reset the marker to it's original position (instead of remove) + + if (tooltip) { + tooltip.updateContent({ + text: L.drawLocal.draw.handlers.polyline.error + }); + } + + // Reset everything back to normal after a second + setTimeout(function () { + poly.setStyle({ color: originalColor }); + if (tooltip) { + tooltip.updateContent({ + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + }); + } + }, 1000); + } + } + + this._poly.redraw(); + this._poly.fire('editdrag'); + }, + + _onMarkerClick: function (e) { + + var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3, + marker = e.target; + + // If removing this point would create an invalid polyline/polygon don't remove + if (this._defaultShape().length < minPoints) { + return; + } + + // remove the marker + this._removeMarker(marker); + + // update prev/next links of adjacent markers + this._updatePrevNext(marker._prev, marker._next); + + // remove ghost markers near the removed marker + if (marker._middleLeft) { + this._markerGroup.removeLayer(marker._middleLeft); + } + if (marker._middleRight) { + this._markerGroup.removeLayer(marker._middleRight); + } + + // create a ghost marker in place of the removed one + if (marker._prev && marker._next) { + this._createMiddleMarker(marker._prev, marker._next); + + } else if (!marker._prev) { + marker._next._middleLeft = null; + + } else if (!marker._next) { + marker._prev._middleRight = null; + } + + this._fireEdit(); + }, + + _onTouchMove: function (e) { + + var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]), + latlng = this._map.layerPointToLatLng(layerPoint), + marker = e.target; + + L.extend(marker._origLatLng, latlng); + + if (marker._middleLeft) { + marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); + } + if (marker._middleRight) { + marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); + } + + this._poly.redraw(); + this.updateMarkers(); + }, + + _updateIndexes: function (index, delta) { + this._markerGroup.eachLayer(function (marker) { + if (marker._index > index) { + marker._index += delta; + } + }); + }, + + _createMiddleMarker: function (marker1, marker2) { + var latlng = this._getMiddleLatLng(marker1, marker2), + marker = this._createMarker(latlng), + onClick, + onDragStart, + onDragEnd; + + marker.setOpacity(0.6); + + marker1._middleRight = marker2._middleLeft = marker; + + onDragStart = function () { + marker.off('touchmove', onDragStart, this); + var i = marker2._index; + + marker._index = i; + + marker + .off('click', onClick, this) + .on('click', this._onMarkerClick, this); + + latlng.lat = marker.getLatLng().lat; + latlng.lng = marker.getLatLng().lng; + this._spliceLatLngs(i, 0, latlng); + this._markers.splice(i, 0, marker); + + marker.setOpacity(1); + + this._updateIndexes(i, 1); + marker2._index++; + this._updatePrevNext(marker1, marker); + this._updatePrevNext(marker, marker2); + + this._poly.fire('editstart'); + }; + + onDragEnd = function () { + marker.off('dragstart', onDragStart, this); + marker.off('dragend', onDragEnd, this); + marker.off('touchmove', onDragStart, this); + + this._createMiddleMarker(marker1, marker); + this._createMiddleMarker(marker, marker2); + }; + + onClick = function () { + onDragStart.call(this); + onDragEnd.call(this); + this._fireEdit(); + }; + + marker + .on('click', onClick, this) + .on('dragstart', onDragStart, this) + .on('dragend', onDragEnd, this) + .on('touchmove', onDragStart, this); + + this._markerGroup.addLayer(marker); + }, + + _updatePrevNext: function (marker1, marker2) { + if (marker1) { + marker1._next = marker2; + } + if (marker2) { + marker2._prev = marker1; + } + }, + + _getMiddleLatLng: function (marker1, marker2) { + var map = this._poly._map, + p1 = map.project(marker1.getLatLng()), + p2 = map.project(marker2.getLatLng()); + + return map.unproject(p1._add(p2)._divideBy(2)); + } +}); + +L.Polyline.addInitHook(function () { + + // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit + if (this.editing) { + return; + } + + if (L.Edit.Poly) { + + this.editing = new L.Edit.Poly(this, this.options.poly); + + if (this.options.editable) { + this.editing.enable(); + } + } + + this.on('add', function () { + if (this.editing && this.editing.enabled()) { + this.editing.addHooks(); + } + }); + + this.on('remove', function () { + if (this.editing && this.editing.enabled()) { + this.editing.removeHooks(); + } + }); +}); + + + +L.Edit = L.Edit || {}; +/** + * @class L.Edit.SimpleShape + * @aka Edit.SimpleShape + */ +L.Edit.SimpleShape = L.Handler.extend({ + options: { + moveIcon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move' + }), + resizeIcon: new L.DivIcon({ + iconSize: new L.Point(8, 8), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize' + }), + touchMoveIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon' + }), + touchResizeIcon: new L.DivIcon({ + iconSize: new L.Point(20, 20), + className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon' + }), + }, + + // @method intialize(): void + initialize: function (shape, options) { + // if touch, switch to touch icon + if (L.Browser.touch) { + this.options.moveIcon = this.options.touchMoveIcon; + this.options.resizeIcon = this.options.touchResizeIcon; + } + + this._shape = shape; + L.Util.setOptions(this, options); + }, + + // @method addHooks(): void + addHooks: function () { + var shape = this._shape; + if (this._shape._map) { + this._map = this._shape._map; + shape.setStyle(shape.options.editing); + + if (shape._map) { + this._map = shape._map; + if (!this._markerGroup) { + this._initMarkers(); + } + this._map.addLayer(this._markerGroup); + } + } + }, + + // @method removeHooks(): void + removeHooks: function () { + var shape = this._shape; + + shape.setStyle(shape.options.original); + + if (shape._map) { + this._unbindMarker(this._moveMarker); + + for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { + this._unbindMarker(this._resizeMarkers[i]); + } + this._resizeMarkers = null; + + this._map.removeLayer(this._markerGroup); + delete this._markerGroup; + } + + this._map = null; + }, + + // @method updateMarkers(): void + updateMarkers: function () { + this._markerGroup.clearLayers(); + this._initMarkers(); + }, + + _initMarkers: function () { + if (!this._markerGroup) { + this._markerGroup = new L.LayerGroup(); + } + + // Create center marker + this._createMoveMarker(); + + // Create edge marker + this._createResizeMarker(); + }, + + _createMoveMarker: function () { + // Children override + }, + + _createResizeMarker: function () { + // Children override + }, + + _createMarker: function (latlng, icon) { + // Extending L.Marker in TouchEvents.js to include touch. + var marker = new L.Marker.Touch(latlng, { + draggable: true, + icon: icon, + zIndexOffset: 10 + }); + + this._bindMarker(marker); + + this._markerGroup.addLayer(marker); + + return marker; + }, + + _bindMarker: function (marker) { + marker + .on('dragstart', this._onMarkerDragStart, this) + .on('drag', this._onMarkerDrag, this) + .on('dragend', this._onMarkerDragEnd, this) + .on('touchstart', this._onTouchStart, this) + .on('touchmove', this._onTouchMove, this) + .on('MSPointerMove', this._onTouchMove, this) + .on('touchend', this._onTouchEnd, this) + .on('MSPointerUp', this._onTouchEnd, this); + }, + + _unbindMarker: function (marker) { + marker + .off('dragstart', this._onMarkerDragStart, this) + .off('drag', this._onMarkerDrag, this) + .off('dragend', this._onMarkerDragEnd, this) + .off('touchstart', this._onTouchStart, this) + .off('touchmove', this._onTouchMove, this) + .off('MSPointerMove', this._onTouchMove, this) + .off('touchend', this._onTouchEnd, this) + .off('MSPointerUp', this._onTouchEnd, this); + }, + + _onMarkerDragStart: function (e) { + var marker = e.target; + marker.setOpacity(0); + + this._shape.fire('editstart'); + }, + + _fireEdit: function () { + this._shape.edited = true; + this._shape.fire('edit'); + }, + + _onMarkerDrag: function (e) { + var marker = e.target, + latlng = marker.getLatLng(); + + if (marker === this._moveMarker) { + this._move(latlng); + } else { + this._resize(latlng); + } + + this._shape.redraw(); + this._shape.fire('editdrag'); + }, + + _onMarkerDragEnd: function (e) { + var marker = e.target; + marker.setOpacity(1); + + this._fireEdit(); + }, + + _onTouchStart: function (e) { + L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e); + + if (typeof(this._getCorners) === 'function') { + // Save a reference to the opposite point + var corners = this._getCorners(), + marker = e.target, + currentCornerIndex = marker._cornerIndex; + + marker.setOpacity(0); + + // Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart() + // Latlng is null otherwise. + this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]; + this._toggleCornerMarkers(0, currentCornerIndex); + } + + this._shape.fire('editstart'); + }, + + _onTouchMove: function (e) { + var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]), + latlng = this._map.layerPointToLatLng(layerPoint), + marker = e.target; + + if (marker === this._moveMarker) { + this._move(latlng); + } else { + this._resize(latlng); + } + + this._shape.redraw(); + + // prevent touchcancel in IOS + // e.preventDefault(); + return false; + }, + + _onTouchEnd: function (e) { + var marker = e.target; + marker.setOpacity(1); + this.updateMarkers(); + this._fireEdit(); + }, + + _move: function () { + // Children override + }, + + _resize: function () { + // Children override + } +}); + + + +L.Edit = L.Edit || {}; +/** + * @class L.Edit.Rectangle + * @aka Edit.Rectangle + * @inherits L.Edit.SimpleShape + */ +L.Edit.Rectangle = L.Edit.SimpleShape.extend({ + _createMoveMarker: function () { + var bounds = this._shape.getBounds(), + center = bounds.getCenter(); + + this._moveMarker = this._createMarker(center, this.options.moveIcon); + }, + + _createResizeMarker: function () { + var corners = this._getCorners(); + + this._resizeMarkers = []; + + for (var i = 0, l = corners.length; i < l; i++) { + this._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon)); + // Monkey in the corner index as we will need to know this for dragging + this._resizeMarkers[i]._cornerIndex = i; + } + }, + + _onMarkerDragStart: function (e) { + L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e); + + // Save a reference to the opposite point + var corners = this._getCorners(), + marker = e.target, + currentCornerIndex = marker._cornerIndex; + + this._oppositeCorner = corners[(currentCornerIndex + 2) % 4]; + + this._toggleCornerMarkers(0, currentCornerIndex); + }, + + _onMarkerDragEnd: function (e) { + var marker = e.target, + bounds, center; + + // Reset move marker position to the center + if (marker === this._moveMarker) { + bounds = this._shape.getBounds(); + center = bounds.getCenter(); + + marker.setLatLng(center); + } + + this._toggleCornerMarkers(1); + + this._repositionCornerMarkers(); + + L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e); + }, + + _move: function (newCenter) { + var latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(), + bounds = this._shape.getBounds(), + center = bounds.getCenter(), + offset, newLatLngs = []; + + // Offset the latlngs to the new center + for (var i = 0, l = latlngs.length; i < l; i++) { + offset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng]; + newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]); + } + + this._shape.setLatLngs(newLatLngs); + + // Reposition the resize markers + this._repositionCornerMarkers(); + + this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape}); + }, + + _resize: function (latlng) { + var bounds; + + // Update the shape based on the current position of this corner and the opposite point + this._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner)); + + // Reposition the move marker + bounds = this._shape.getBounds(); + this._moveMarker.setLatLng(bounds.getCenter()); + + this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape}); + }, + + _getCorners: function () { + var bounds = this._shape.getBounds(), + nw = bounds.getNorthWest(), + ne = bounds.getNorthEast(), + se = bounds.getSouthEast(), + sw = bounds.getSouthWest(); + + return [nw, ne, se, sw]; + }, + + _toggleCornerMarkers: function (opacity) { + for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { + this._resizeMarkers[i].setOpacity(opacity); + } + }, + + _repositionCornerMarkers: function () { + var corners = this._getCorners(); + + for (var i = 0, l = this._resizeMarkers.length; i < l; i++) { + this._resizeMarkers[i].setLatLng(corners[i]); + } + } +}); + +L.Rectangle.addInitHook(function () { + if (L.Edit.Rectangle) { + this.editing = new L.Edit.Rectangle(this); + + if (this.options.editable) { + this.editing.enable(); + } + } +}); + + + +L.Edit = L.Edit || {}; +/** + * @class L.Edit.Circle + * @aka Edit.Circle + * @inherits L.Edit.SimpleShape + */ +L.Edit.Circle = L.Edit.SimpleShape.extend({ + _createMoveMarker: function () { + var center = this._shape.getLatLng(); + + this._moveMarker = this._createMarker(center, this.options.moveIcon); + }, + + _createResizeMarker: function () { + var center = this._shape.getLatLng(), + resizemarkerPoint = this._getResizeMarkerPoint(center); + + this._resizeMarkers = []; + this._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon)); + }, + + _getResizeMarkerPoint: function (latlng) { + // From L.shape.getBounds() + var delta = this._shape._radius * Math.cos(Math.PI / 4), + point = this._map.project(latlng); + return this._map.unproject([point.x + delta, point.y - delta]); + }, + + _move: function (latlng) { + var resizemarkerPoint = this._getResizeMarkerPoint(latlng); + + // Move the resize marker + this._resizeMarkers[0].setLatLng(resizemarkerPoint); + + // Move the circle + this._shape.setLatLng(latlng); + + this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape}); + }, + + _resize: function (latlng) { + var moveLatLng = this._moveMarker.getLatLng(), + radius = moveLatLng.distanceTo(latlng); + + this._shape.setRadius(radius); + + this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape}); + } +}); + +L.Circle.addInitHook(function () { + if (L.Edit.Circle) { + this.editing = new L.Edit.Circle(this); + + if (this.options.editable) { + this.editing.enable(); + } + } + + this.on('add', function () { + if (this.editing && this.editing.enabled()) { + this.editing.addHooks(); + } + }); + + this.on('remove', function () { + if (this.editing && this.editing.enabled()) { + this.editing.removeHooks(); + } + }); +}); + + +L.Map.mergeOptions({ + touchExtend: true +}); + +/** + * @class L.Map.TouchExtend + * @aka TouchExtend + */ +L.Map.TouchExtend = L.Handler.extend({ + + // @method initialize(): void + // Sets TouchExtend private accessor variables + initialize: function (map) { + this._map = map; + this._container = map._container; + this._pane = map._panes.overlayPane; + }, + + // @method addHooks(): void + // Adds dom listener events to the map container + addHooks: function () { + L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this); + L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this); + L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this); + if (this._detectIE()) { + L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this); + L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this); + L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this); + L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this); + + } else { + L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this); + L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this); + } + }, + + // @method removeHooks(): void + // Removes dom listener events from the map container + removeHooks: function () { + L.DomEvent.off(this._container, 'touchstart', this._onTouchStart); + L.DomEvent.off(this._container, 'touchend', this._onTouchEnd); + L.DomEvent.off(this._container, 'touchmove', this._onTouchMove); + if (this._detectIE()) { + L.DomEvent.off(this._container, 'MSPointerDowm', this._onTouchStart); + L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd); + L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove); + L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel); + } else { + L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel); + L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave); + } + }, + + _touchEvent: function (e, type) { + // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events + // _filterClick is what leaflet uses as a workaround. + // This is a problem with more things than just android. Another problem is touchEnd has no touches in + // its touch list. + var touchEvent = {}; + if (typeof e.touches !== 'undefined') { + if (!e.touches.length) { + return; + } + touchEvent = e.touches[0]; + } else if (e.pointerType === 'touch') { + touchEvent = e; + if (!this._filterClick(e)) { + return; + } + } else { + return; + } + + var containerPoint = this._map.mouseEventToContainerPoint(touchEvent), + layerPoint = this._map.mouseEventToLayerPoint(touchEvent), + latlng = this._map.layerPointToLatLng(layerPoint); + + this._map.fire(type, { + latlng: latlng, + layerPoint: layerPoint, + containerPoint: containerPoint, + pageX: touchEvent.pageX, + pageY: touchEvent.pageY, + originalEvent: e + }); + }, + + /** Borrowed from Leaflet and modified for bool ops **/ + _filterClick: function (e) { + var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), + elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); + + // are they closer together than 500ms yet more than 100ms? + // Android typically triggers them ~300ms apart while multiple listeners + // on the same event should be triggered far faster; + // or check if click is simulated on the element, and if it is, reject any non-simulated events + if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { + L.DomEvent.stop(e); + return false; + } + L.DomEvent._lastClick = timeStamp; + return true; + }, + + _onTouchStart: function (e) { + if (!this._map._loaded) { + return; + } + + var type = 'touchstart'; + this._touchEvent(e, type); + + }, + + _onTouchEnd: function (e) { + if (!this._map._loaded) { + return; + } + + var type = 'touchend'; + this._touchEvent(e, type); + }, + + _onTouchCancel: function (e) { + if (!this._map._loaded) { + return; + } + + var type = 'touchcancel'; + if (this._detectIE()) { + type = 'pointercancel'; + } + this._touchEvent(e, type); + }, + + _onTouchLeave: function (e) { + if (!this._map._loaded) { + return; + } + + var type = 'touchleave'; + this._touchEvent(e, type); + }, + + _onTouchMove: function (e) { + if (!this._map._loaded) { + return; + } + + var type = 'touchmove'; + this._touchEvent(e, type); + }, + + _detectIE: function () { + var ua = window.navigator.userAgent; + + var msie = ua.indexOf('MSIE '); + if (msie > 0) { + // IE 10 or older => return version number + return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); + } + + var trident = ua.indexOf('Trident/'); + if (trident > 0) { + // IE 11 => return version number + var rv = ua.indexOf('rv:'); + return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); + } + + var edge = ua.indexOf('Edge/'); + if (edge > 0) { + // IE 12 => return version number + return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); + } + + // other browser + return false; + } +}); + +L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend); + + +/** + * @class L.Marker.Touch + * @aka Marker.Touch + * + * This isn't full Touch support. This is just to get makers to also support dom touch events after creation + * #TODO: find a better way of getting markers to support touch. + */ +L.Marker.Touch = L.Marker.extend({ + + _initInteraction: function () { + if (!this.addInteractiveTarget) { + // 0.7.x support + return this._initInteractionLegacy(); + } + // TODO this may need be updated to re-add touch events for 1.0+ + return L.Marker.prototype._initInteraction.apply(this); + }, + + // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js + // with the addition of the touch events + _initInteractionLegacy: function () { + + if (!this.options.clickable) { + return; + } + + // TODO refactor into something shared with Map/Path/etc. to DRY it up + + var icon = this._icon, + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu', 'touchstart', 'touchend', 'touchmove']; + if (this._detectIE) { + events.concat(['MSPointerDown', 'MSPointerUp', 'MSPointerMove', 'MSPointerCancel']); + } else { + events.concat(['touchcancel']); + } + + L.DomUtil.addClass(icon, 'leaflet-clickable'); + L.DomEvent.on(icon, 'click', this._onMouseClick, this); + L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); + + for (var i = 0; i < events.length; i++) { + L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); + } + + if (L.Handler.MarkerDrag) { + this.dragging = new L.Handler.MarkerDrag(this); + + if (this.options.draggable) { + this.dragging.enable(); + } + } + }, + + _detectIE: function () { + var ua = window.navigator.userAgent; + + var msie = ua.indexOf('MSIE '); + if (msie > 0) { + // IE 10 or older => return version number + return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); + } + + var trident = ua.indexOf('Trident/'); + if (trident > 0) { + // IE 11 => return version number + var rv = ua.indexOf('rv:'); + return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); + } + + var edge = ua.indexOf('Edge/'); + if (edge > 0) { + // IE 12 => return version number + return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); + } + + // other browser + return false; + } +}); + + + +/** + * @class L.LatLngUtil + * @aka LatLngUtil + */ +L.LatLngUtil = { + // Clones a LatLngs[], returns [][] + + // @method cloneLatLngs(): void + cloneLatLngs: function (latlngs) { + var clone = []; + for (var i = 0, l = latlngs.length; i < l; i++) { + // Check for nested array (Polyline/Polygon) + if (Array.isArray(latlngs[i])) { + clone.push(L.LatLngUtil.cloneLatLngs(latlngs[i])); + } else { + clone.push(this.cloneLatLng(latlngs[i])); + } + } + return clone; + }, + + // @method cloneLatLng(): void + cloneLatLng: function (latlng) { + return L.latLng(latlng.lat, latlng.lng); + } +}; + + + +/** + * @class L.GeometryUtil + * @aka GeometryUtil + */ +L.GeometryUtil = L.extend(L.GeometryUtil || {}, { + // Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270 + + // @method geodesicArea(): void + geodesicArea: function (latLngs) { + var pointsCount = latLngs.length, + area = 0.0, + d2r = Math.PI / 180, + p1, p2; + + if (pointsCount > 2) { + for (var i = 0; i < pointsCount; i++) { + p1 = latLngs[i]; + p2 = latLngs[(i + 1) % pointsCount]; + area += ((p2.lng - p1.lng) * d2r) * + (2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r)); + } + area = area * 6378137.0 * 6378137.0 / 2.0; + } + + return Math.abs(area); + }, + + // @method readableArea(): void + readableArea: function (area, isMetric) { + var areaStr; + + if (isMetric) { + if (area >= 10000) { + areaStr = (area * 0.0001).toFixed(2) + ' ha'; + } else { + areaStr = area.toFixed(2) + ' m²'; + } + } else { + area /= 0.836127; // Square yards in 1 meter + + if (area >= 3097600) { //3097600 square yards in 1 square mile + areaStr = (area / 3097600).toFixed(2) + ' mi²'; + } else if (area >= 4840) {//48040 square yards in 1 acre + areaStr = (area / 4840).toFixed(2) + ' acres'; + } else { + areaStr = Math.ceil(area) + ' yd²'; + } + } + + return areaStr; + }, + + // @method readableDistance(): void + readableDistance: function (distance, isMetric, useFeet) { + var distanceStr; + + if (isMetric) { + // show metres when distance is < 1km, then show km + if (distance > 1000) { + distanceStr = (distance / 1000).toFixed(2) + ' km'; + } else { + distanceStr = Math.ceil(distance) + ' m'; + } + } else { + distance *= 1.09361; + + if (distance > 1760) { + distanceStr = (distance / 1760).toFixed(2) + ' miles'; + } else { + var suffix = ' yd'; + if (useFeet) { + distance = distance * 3; + suffix = ' ft'; + } + distanceStr = Math.ceil(distance) + suffix; + } + } + + return distanceStr; + } +}); + + + +/** + * @class L.LineUtil + * @aka Util + * @aka L.Utils + */ +L.Util.extend(L.LineUtil, { + + // @method segmentsIntersect(): void + // Checks to see if two line segments intersect. Does not handle degenerate cases. + // http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf + segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) { + return this._checkCounterclockwise(p, p2, p3) !== + this._checkCounterclockwise(p1, p2, p3) && + this._checkCounterclockwise(p, p1, p2) !== + this._checkCounterclockwise(p, p1, p3); + }, + + // check to see if points are in counterclockwise order + _checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { + return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x); + } +}); + + +/** + * @class L.Polyline + * @aka Polyline + */ +L.Polyline.include({ + + // @method intersects(): void + // Check to see if this polyline has any linesegments that intersect. + // NOTE: does not support detecting intersection for degenerate cases. + intersects: function () { + var points = this._getProjectedPoints(), + len = points ? points.length : 0, + i, p, p1; + + if (this._tooFewPointsForIntersection()) { + return false; + } + + for (i = len - 1; i >= 3; i--) { + p = points[i - 1]; + p1 = points[i]; + + + if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) { + return true; + } + } + + return false; + }, + + // @method newLatLngIntersects(): void + // Check for intersection if new latlng was added to this polyline. + // NOTE: does not support detecting intersection for degenerate cases. + newLatLngIntersects: function (latlng, skipFirst) { + // Cannot check a polyline for intersecting lats/lngs when not added to the map + if (!this._map) { + return false; + } + + return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst); + }, + + // @method newPointIntersects(): void + // Check for intersection if new point was added to this polyline. + // newPoint must be a layer point. + // NOTE: does not support detecting intersection for degenerate cases. + newPointIntersects: function (newPoint, skipFirst) { + var points = this._getProjectedPoints(), + len = points ? points.length : 0, + lastPoint = points ? points[len - 1] : null, + // The previous previous line segment. Previous line segment doesn't need testing. + maxIndex = len - 2; + + if (this._tooFewPointsForIntersection(1)) { + return false; + } + + return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0); + }, + + // Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these). + // Cannot have intersection when < 3 line segments (< 4 points) + _tooFewPointsForIntersection: function (extraPoints) { + var points = this._getProjectedPoints(), + len = points ? points.length : 0; + // Increment length by extraPoints if present + len += extraPoints || 0; + + return !points || len <= 3; + }, + + // Checks a line segment intersections with any line segments before its predecessor. + // Don't need to check the predecessor as will never intersect. + _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) { + var points = this._getProjectedPoints(), + p2, p3; + + minIndex = minIndex || 0; + + // Check all previous line segments (beside the immediately previous) for intersections + for (var j = maxIndex; j > minIndex; j--) { + p2 = points[j - 1]; + p3 = points[j]; + + if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) { + return true; + } + } + + return false; + }, + + _getProjectedPoints: function () { + if (!this._defaultShape) { + return this._originalPoints; + } + var points = [], + _shape = this._defaultShape(); + + for (var i = 0; i < _shape.length; i++) { + points.push(this._map.latLngToLayerPoint(_shape[i])); + } + return points; + } +}); + + + +/** + * @class L.Polygon + * @aka Polygon + */ +L.Polygon.include({ + + // @method intersects(): void + // Checks a polygon for any intersecting line segments. Ignores holes. + intersects: function () { + var polylineIntersects, + points = this._getProjectedPoints(), + len, firstPoint, lastPoint, maxIndex; + + if (this._tooFewPointsForIntersection()) { + return false; + } + + polylineIntersects = L.Polyline.prototype.intersects.call(this); + + // If already found an intersection don't need to check for any more. + if (polylineIntersects) { + return true; + } + + len = points.length; + firstPoint = points[0]; + lastPoint = points[len - 1]; + maxIndex = len - 2; + + // Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1) + return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1); + } +}); + + + +/** + * @class L.Control.Draw + * @aka L.Draw + */ +L.Control.Draw = L.Control.extend({ + + // Options + options: { + position: 'topleft', + draw: {}, + edit: false + }, + + // @method initialize(): void + // Initializes draw control, toolbars from the options + initialize: function (options) { + if (L.version < '0.7') { + throw new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/'); + } + + L.Control.prototype.initialize.call(this, options); + + var toolbar; + + this._toolbars = {}; + + // Initialize toolbars + if (L.DrawToolbar && this.options.draw) { + toolbar = new L.DrawToolbar(this.options.draw); + + this._toolbars[L.DrawToolbar.TYPE] = toolbar; + + // Listen for when toolbar is enabled + this._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this); + } + + if (L.EditToolbar && this.options.edit) { + toolbar = new L.EditToolbar(this.options.edit); + + this._toolbars[L.EditToolbar.TYPE] = toolbar; + + // Listen for when toolbar is enabled + this._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this); + } + L.toolbar = this; //set global var for editing the toolbar + }, + + // @method onAdd(): container + // Adds the toolbar container to the map + onAdd: function (map) { + var container = L.DomUtil.create('div', 'leaflet-draw'), + addedTopClass = false, + topClassName = 'leaflet-draw-toolbar-top', + toolbarContainer; + + for (var toolbarId in this._toolbars) { + if (this._toolbars.hasOwnProperty(toolbarId)) { + toolbarContainer = this._toolbars[toolbarId].addToolbar(map); + + if (toolbarContainer) { + // Add class to the first toolbar to remove the margin + if (!addedTopClass) { + if (!L.DomUtil.hasClass(toolbarContainer, topClassName)) { + L.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName); + } + addedTopClass = true; + } + + container.appendChild(toolbarContainer); + } + } + } + + return container; + }, + + // @method onRemove(): void + // Removes the toolbars from the map toolbar container + onRemove: function () { + for (var toolbarId in this._toolbars) { + if (this._toolbars.hasOwnProperty(toolbarId)) { + this._toolbars[toolbarId].removeToolbar(); + } + } + }, + + // @method setDrawingOptions(options): void + // Sets options to all toolbar instances + setDrawingOptions: function (options) { + for (var toolbarId in this._toolbars) { + if (this._toolbars[toolbarId] instanceof L.DrawToolbar) { + this._toolbars[toolbarId].setOptions(options); + } + } + }, + + _toolbarEnabled: function (e) { + var enabledToolbar = e.target; + + for (var toolbarId in this._toolbars) { + if (this._toolbars[toolbarId] !== enabledToolbar) { + this._toolbars[toolbarId].disable(); + } + } + } +}); + +L.Map.mergeOptions({ + drawControlTooltips: true, + drawControl: false +}); + +L.Map.addInitHook(function () { + if (this.options.drawControl) { + this.drawControl = new L.Control.Draw(); + this.addControl(this.drawControl); + } +}); + + + +/** + * @class L.Draw.Toolbar + * @aka Toolbar + * + * The toolbar class of the API — it is used to create the ui + * This will be depreciated + * + * @example + * + * ```js + * var toolbar = L.Toolbar(); + * toolbar.addToolbar(map); + * ``` + * + * ### Disabling a toolbar + * + * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false. + * + * ```js + * var drawControl = new L.Control.Draw({ + * draw: false, + * edit: { + * featureGroup: editableLayers + * } + * }); + * ``` + * + * ### Disabling a toolbar item + * + * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and + * markers. It also turns off the ability to edit layers. + * + * ```js + * var drawControl = new L.Control.Draw({ + * draw: { + * polygon: false, + * marker: false + * }, + * edit: { + * featureGroup: editableLayers, + * edit: false + * } + * }); + * ``` + */ +L.Toolbar = L.Class.extend({ + includes: [L.Mixin.Events], + + // @section Methods for modifying the toolbar + + // @method initialize(options): void + // Toolbar constructor + initialize: function (options) { + L.setOptions(this, options); + + this._modes = {}; + this._actionButtons = []; + this._activeMode = null; + }, + + // @method enabled(): boolean + // Gets a true/false of whether the toolbar is enabled + enabled: function () { + return this._activeMode !== null; + }, + + // @method disable(): void + // Disables the toolbar + disable: function () { + if (!this.enabled()) { + return; + } + + this._activeMode.handler.disable(); + }, + + // @method addToolbar(map): L.DomUtil + // Adds the toolbar to the map and returns the toolbar dom element + addToolbar: function (map) { + var container = L.DomUtil.create('div', 'leaflet-draw-section'), + buttonIndex = 0, + buttonClassPrefix = this._toolbarClass || '', + modeHandlers = this.getModeHandlers(map), + i; + + this._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar'); + this._map = map; + + for (i = 0; i < modeHandlers.length; i++) { + if (modeHandlers[i].enabled) { + this._initModeHandler( + modeHandlers[i].handler, + this._toolbarContainer, + buttonIndex++, + buttonClassPrefix, + modeHandlers[i].title + ); + } + } + + // if no buttons were added, do not add the toolbar + if (!buttonIndex) { + return; + } + + // Save button index of the last button, -1 as we would have ++ after the last button + this._lastButtonIndex = --buttonIndex; + + // Create empty actions part of the toolbar + this._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions'); + + // Add draw and cancel containers to the control container + container.appendChild(this._toolbarContainer); + container.appendChild(this._actionsContainer); + + return container; + }, + + // @method removeToolbar(): void + // Removes the toolbar and drops the handler event listeners + removeToolbar: function () { + // Dispose each handler + for (var handlerId in this._modes) { + if (this._modes.hasOwnProperty(handlerId)) { + // Unbind handler button + this._disposeButton( + this._modes[handlerId].button, + this._modes[handlerId].handler.enable, + this._modes[handlerId].handler + ); + + // Make sure is disabled + this._modes[handlerId].handler.disable(); + + // Unbind handler + this._modes[handlerId].handler + .off('enabled', this._handlerActivated, this) + .off('disabled', this._handlerDeactivated, this); + } + } + this._modes = {}; + + // Dispose the actions toolbar + for (var i = 0, l = this._actionButtons.length; i < l; i++) { + this._disposeButton( + this._actionButtons[i].button, + this._actionButtons[i].callback, + this + ); + } + this._actionButtons = []; + this._actionsContainer = null; + }, + + _initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) { + var type = handler.type; + + this._modes[type] = {}; + + this._modes[type].handler = handler; + + this._modes[type].button = this._createButton({ + type: type, + title: buttonTitle, + className: classNamePredix + '-' + type, + container: container, + callback: this._modes[type].handler.enable, + context: this._modes[type].handler + }); + + this._modes[type].buttonIndex = buttonIndex; + + this._modes[type].handler + .on('enabled', this._handlerActivated, this) + .on('disabled', this._handlerDeactivated, this); + }, + + _createButton: function (options) { + + var link = L.DomUtil.create('a', options.className || '', options.container); + link.href = '#'; + + if (options.text) { + link.innerHTML = options.text; + } + + if (options.title) { + link.title = options.title; + } + + L.DomEvent + .on(link, 'click', L.DomEvent.stopPropagation) + .on(link, 'mousedown', L.DomEvent.stopPropagation) + .on(link, 'dblclick', L.DomEvent.stopPropagation) + .on(link, 'click', L.DomEvent.preventDefault) + .on(link, 'click', options.callback, options.context); + + return link; + }, + + _disposeButton: function (button, callback) { + L.DomEvent + .off(button, 'click', L.DomEvent.stopPropagation) + .off(button, 'mousedown', L.DomEvent.stopPropagation) + .off(button, 'dblclick', L.DomEvent.stopPropagation) + .off(button, 'click', L.DomEvent.preventDefault) + .off(button, 'click', callback); + }, + + _handlerActivated: function (e) { + // Disable active mode (if present) + this.disable(); + + // Cache new active feature + this._activeMode = this._modes[e.handler]; + + L.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled'); + + this._showActionsToolbar(); + + this.fire('enable'); + }, + + _handlerDeactivated: function () { + this._hideActionsToolbar(); + + L.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled'); + + this._activeMode = null; + + this.fire('disable'); + }, + + _createActions: function (handler) { + var container = this._actionsContainer, + buttons = this.getActions(handler), + l = buttons.length, + li, di, dl, button; + + // Dispose the actions toolbar (todo: dispose only not used buttons) + for (di = 0, dl = this._actionButtons.length; di < dl; di++) { + this._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback); + } + this._actionButtons = []; + + // Remove all old buttons + while (container.firstChild) { + container.removeChild(container.firstChild); + } + + for (var i = 0; i < l; i++) { + if ('enabled' in buttons[i] && !buttons[i].enabled) { + continue; + } + + li = L.DomUtil.create('li', '', container); + + button = this._createButton({ + title: buttons[i].title, + text: buttons[i].text, + container: li, + callback: buttons[i].callback, + context: buttons[i].context + }); + + this._actionButtons.push({ + button: button, + callback: buttons[i].callback + }); + } + }, + + _showActionsToolbar: function () { + var buttonIndex = this._activeMode.buttonIndex, + lastButtonIndex = this._lastButtonIndex, + toolbarPosition = this._activeMode.button.offsetTop - 1; + + // Recreate action buttons on every click + this._createActions(this._activeMode.handler); + + // Correctly position the cancel button + this._actionsContainer.style.top = toolbarPosition + 'px'; + + if (buttonIndex === 0) { + L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop'); + L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top'); + } + + if (buttonIndex === lastButtonIndex) { + L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom'); + L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom'); + } + + this._actionsContainer.style.display = 'block'; + }, + + _hideActionsToolbar: function () { + this._actionsContainer.style.display = 'none'; + + L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop'); + L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom'); + L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top'); + L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom'); + } +}); + + + +L.Draw = L.Draw || {}; +/** + * @class L.Draw.Tooltip + * @aka Tooltip + * + * The tooltip class — it is used to display the tooltip while drawing + * This will be depreciated + * + * @example + * + * ```js + * var tooltip = L.Draw.Tooltip(); + * ``` + * + */ +L.Draw.Tooltip = L.Class.extend({ + + // @section Methods for modifying draw state + + // @method initialize(map): void + // Tooltip constructor + initialize: function (map) { + this._map = map; + this._popupPane = map._panes.popupPane; + + this._container = map.options.drawControlTooltips ? L.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null; + this._singleLineLabel = false; + + this._map.on('mouseout', this._onMouseOut, this); + }, + + // @method dispose(): void + // Remove Tooltip DOM and unbind events + dispose: function () { + this._map.off('mouseout', this._onMouseOut, this); + + if (this._container) { + this._popupPane.removeChild(this._container); + this._container = null; + } + }, + + // @method updateContent(labelText): this + // Changes the tooltip text to string in function call + updateContent: function (labelText) { + if (!this._container) { + return this; + } + labelText.subtext = labelText.subtext || ''; + + // update the vertical position (only if changed) + if (labelText.subtext.length === 0 && !this._singleLineLabel) { + L.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single'); + this._singleLineLabel = true; + } + else if (labelText.subtext.length > 0 && this._singleLineLabel) { + L.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single'); + this._singleLineLabel = false; + } + + this._container.innerHTML = + (labelText.subtext.length > 0 ? '' + labelText.subtext + '' + '
' : '') + + '' + labelText.text + ''; + + return this; + }, + + // @method updatePosition(latlng): this + // Changes the location of the tooltip + updatePosition: function (latlng) { + var pos = this._map.latLngToLayerPoint(latlng), + tooltipContainer = this._container; + + if (this._container) { + tooltipContainer.style.visibility = 'inherit'; + L.DomUtil.setPosition(tooltipContainer, pos); + } + + return this; + }, + + // @method showAsError(): this + // Applies error class to tooltip + showAsError: function () { + if (this._container) { + L.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip'); + } + return this; + }, + + // @method removeError(): this + // Removes the error class from the tooltip + removeError: function () { + if (this._container) { + L.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip'); + } + return this; + }, + + _onMouseOut: function () { + if (this._container) { + this._container.style.visibility = 'hidden'; + } + } +}); + + + +/** + * @class L.DrawToolbar + * @aka Toolbar + */ +L.DrawToolbar = L.Toolbar.extend({ + + statics: { + TYPE: 'draw' + }, + + options: { + polyline: {}, + polygon: {}, + rectangle: {}, + circle: {}, + marker: {} + }, + + // @method initialize(): void + initialize: function (options) { + // Ensure that the options are merged correctly since L.extend is only shallow + for (var type in this.options) { + if (this.options.hasOwnProperty(type)) { + if (options[type]) { + options[type] = L.extend({}, this.options[type], options[type]); + } + } + } + + this._toolbarClass = 'leaflet-draw-draw'; + L.Toolbar.prototype.initialize.call(this, options); + }, + + // @method getModeHandlers(): void + getModeHandlers: function (map) { + return [ + { + enabled: this.options.polyline, + handler: new L.Draw.Polyline(map, this.options.polyline), + title: L.drawLocal.draw.toolbar.buttons.polyline + }, + { + enabled: this.options.polygon, + handler: new L.Draw.Polygon(map, this.options.polygon), + title: L.drawLocal.draw.toolbar.buttons.polygon + }, + { + enabled: this.options.rectangle, + handler: new L.Draw.Rectangle(map, this.options.rectangle), + title: L.drawLocal.draw.toolbar.buttons.rectangle + }, + { + enabled: this.options.circle, + handler: new L.Draw.Circle(map, this.options.circle), + title: L.drawLocal.draw.toolbar.buttons.circle + }, + { + enabled: this.options.marker, + handler: new L.Draw.Marker(map, this.options.marker), + title: L.drawLocal.draw.toolbar.buttons.marker + } + ]; + }, + + // @method getActions(): void + getActions: function (handler) { + return [ + { + enabled: handler.completeShape, + title: L.drawLocal.draw.toolbar.finish.title, + text: L.drawLocal.draw.toolbar.finish.text, + callback: handler.completeShape, + context: handler + }, + { + enabled: handler.deleteLastVertex, + title: L.drawLocal.draw.toolbar.undo.title, + text: L.drawLocal.draw.toolbar.undo.text, + callback: handler.deleteLastVertex, + context: handler + }, + { + title: L.drawLocal.draw.toolbar.actions.title, + text: L.drawLocal.draw.toolbar.actions.text, + callback: this.disable, + context: this + } + ]; + }, + + // @method setOptions(): void + setOptions: function (options) { + L.setOptions(this, options); + + for (var type in this._modes) { + if (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) { + this._modes[type].handler.setOptions(options[type]); + } + } + } +}); + + + +/*L.Map.mergeOptions({ + editControl: true +});*/ +/** + * @class L.EditToolbar + * @aka EditToolbar + */ +L.EditToolbar = L.Toolbar.extend({ + statics: { + TYPE: 'edit' + }, + + options: { + edit: { + selectedPathOptions: { + dashArray: '10, 10', + + fill: true, + fillColor: '#fe57a1', + fillOpacity: 0.1, + + // Whether to user the existing layers color + maintainColor: false + } + }, + remove: {}, + poly: null, + featureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */ + }, + + // @method intialize(): void + initialize: function (options) { + // Need to set this manually since null is an acceptable value here + if (options.edit) { + if (typeof options.edit.selectedPathOptions === 'undefined') { + options.edit.selectedPathOptions = this.options.edit.selectedPathOptions; + } + options.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions); + } + + if (options.remove) { + options.remove = L.extend({}, this.options.remove, options.remove); + } + + if (options.poly) { + options.poly = L.extend({}, this.options.poly, options.poly); + } + + this._toolbarClass = 'leaflet-draw-edit'; + L.Toolbar.prototype.initialize.call(this, options); + + this._selectedFeatureCount = 0; + }, + + // @method getModeHandlers(): void + getModeHandlers: function (map) { + var featureGroup = this.options.featureGroup; + return [ + { + enabled: this.options.edit, + handler: new L.EditToolbar.Edit(map, { + featureGroup: featureGroup, + selectedPathOptions: this.options.edit.selectedPathOptions, + poly : this.options.poly + }), + title: L.drawLocal.edit.toolbar.buttons.edit + }, + { + enabled: this.options.remove, + handler: new L.EditToolbar.Delete(map, { + featureGroup: featureGroup + }), + title: L.drawLocal.edit.toolbar.buttons.remove + } + ]; + }, + + // @method getActions(): void + getActions: function () { + return [ + { + title: L.drawLocal.edit.toolbar.actions.save.title, + text: L.drawLocal.edit.toolbar.actions.save.text, + callback: this._save, + context: this + }, + { + title: L.drawLocal.edit.toolbar.actions.cancel.title, + text: L.drawLocal.edit.toolbar.actions.cancel.text, + callback: this.disable, + context: this + } + ]; + }, + + // @method addToolbar(): void + addToolbar: function (map) { + var container = L.Toolbar.prototype.addToolbar.call(this, map); + + this._checkDisabled(); + + this.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this); + + return container; + }, + + // @method removeToolbar(): void + removeToolbar: function () { + this.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this); + + L.Toolbar.prototype.removeToolbar.call(this); + }, + + // @method disable(): void + disable: function () { + if (!this.enabled()) { return; } + + this._activeMode.handler.revertLayers(); + + L.Toolbar.prototype.disable.call(this); + }, + + _save: function () { + this._activeMode.handler.save(); + if (this._activeMode) { + this._activeMode.handler.disable(); + } + }, + + _checkDisabled: function () { + var featureGroup = this.options.featureGroup, + hasLayers = featureGroup.getLayers().length !== 0, + button; + + if (this.options.edit) { + button = this._modes[L.EditToolbar.Edit.TYPE].button; + + if (hasLayers) { + L.DomUtil.removeClass(button, 'leaflet-disabled'); + } else { + L.DomUtil.addClass(button, 'leaflet-disabled'); + } + + button.setAttribute( + 'title', + hasLayers ? + L.drawLocal.edit.toolbar.buttons.edit + : L.drawLocal.edit.toolbar.buttons.editDisabled + ); + } + + if (this.options.remove) { + button = this._modes[L.EditToolbar.Delete.TYPE].button; + + if (hasLayers) { + L.DomUtil.removeClass(button, 'leaflet-disabled'); + } else { + L.DomUtil.addClass(button, 'leaflet-disabled'); + } + + button.setAttribute( + 'title', + hasLayers ? + L.drawLocal.edit.toolbar.buttons.remove + : L.drawLocal.edit.toolbar.buttons.removeDisabled + ); + } + } +}); + + + +/** + * @class L.EditToolbar.Edit + * @aka EditToolbar.Edit + */ +L.EditToolbar.Edit = L.Handler.extend({ + statics: { + TYPE: 'edit' + }, + + includes: L.Mixin.Events, + + // @method intialize(): void + initialize: function (map, options) { + L.Handler.prototype.initialize.call(this, map); + + L.setOptions(this, options); + + // Store the selectable layer group for ease of access + this._featureGroup = options.featureGroup; + + if (!(this._featureGroup instanceof L.FeatureGroup)) { + throw new Error('options.featureGroup must be a L.FeatureGroup'); + } + + this._uneditedLayerProps = {}; + + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.EditToolbar.Edit.TYPE; + }, + + // @method enable(): void + enable: function () { + if (this._enabled || !this._hasAvailableLayers()) { + return; + } + this.fire('enabled', {handler: this.type}); + //this disable other handlers + + this._map.fire(L.Draw.Event.EDITSTART, { handler: this.type }); + //allow drawLayer to be updated before beginning edition. + + L.Handler.prototype.enable.call(this); + this._featureGroup + .on('layeradd', this._enableLayerEdit, this) + .on('layerremove', this._disableLayerEdit, this); + }, + + // @method disable(): void + disable: function () { + if (!this._enabled) { return; } + this._featureGroup + .off('layeradd', this._enableLayerEdit, this) + .off('layerremove', this._disableLayerEdit, this); + L.Handler.prototype.disable.call(this); + this._map.fire(L.Draw.Event.EDITSTOP, { handler: this.type }); + this.fire('disabled', {handler: this.type}); + }, + + // @method addHooks(): void + addHooks: function () { + var map = this._map; + + if (map) { + map.getContainer().focus(); + + this._featureGroup.eachLayer(this._enableLayerEdit, this); + + this._tooltip = new L.Draw.Tooltip(this._map); + this._tooltip.updateContent({ + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + }); + + // Quickly access the tooltip to update for intersection checking + map._editTooltip = this._tooltip; + + this._updateTooltip(); + + this._map + .on('mousemove', this._onMouseMove, this) + .on('touchmove', this._onMouseMove, this) + .on('MSPointerMove', this._onMouseMove, this) + .on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + if (this._map) { + // Clean up selected layers. + this._featureGroup.eachLayer(this._disableLayerEdit, this); + + // Clear the backups of the original layers + this._uneditedLayerProps = {}; + + this._tooltip.dispose(); + this._tooltip = null; + + this._map + .off('mousemove', this._onMouseMove, this) + .off('touchmove', this._onMouseMove, this) + .off('MSPointerMove', this._onMouseMove, this) + .off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this); + } + }, + + // @method revertLayers(): void + revertLayers: function () { + this._featureGroup.eachLayer(function (layer) { + this._revertLayer(layer); + }, this); + }, + + // @method save(): void + save: function () { + var editedLayers = new L.LayerGroup(); + this._featureGroup.eachLayer(function (layer) { + if (layer.edited) { + editedLayers.addLayer(layer); + layer.edited = false; + } + }); + this._map.fire(L.Draw.Event.EDITED, {layers: editedLayers}); + }, + + _backupLayer: function (layer) { + var id = L.Util.stamp(layer); + + if (!this._uneditedLayerProps[id]) { + // Polyline, Polygon or Rectangle + if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) { + this._uneditedLayerProps[id] = { + latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs()) + }; + } else if (layer instanceof L.Circle) { + this._uneditedLayerProps[id] = { + latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()), + radius: layer.getRadius() + }; + } else if (layer instanceof L.Marker) { // Marker + this._uneditedLayerProps[id] = { + latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()) + }; + } + } + }, + + _getTooltipText: function () { + return ({ + text: L.drawLocal.edit.handlers.edit.tooltip.text, + subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext + }); + }, + + _updateTooltip: function () { + this._tooltip.updateContent(this._getTooltipText()); + }, + + _revertLayer: function (layer) { + var id = L.Util.stamp(layer); + layer.edited = false; + if (this._uneditedLayerProps.hasOwnProperty(id)) { + // Polyline, Polygon or Rectangle + if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) { + layer.setLatLngs(this._uneditedLayerProps[id].latlngs); + } else if (layer instanceof L.Circle) { + layer.setLatLng(this._uneditedLayerProps[id].latlng); + layer.setRadius(this._uneditedLayerProps[id].radius); + } else if (layer instanceof L.Marker) { // Marker + layer.setLatLng(this._uneditedLayerProps[id].latlng); + } + + layer.fire('revert-edited', { layer: layer }); + } + }, + + _enableLayerEdit: function (e) { + var layer = e.layer || e.target || e, + pathOptions, poly; + + // Back up this layer (if haven't before) + this._backupLayer(layer); + + if (this.options.poly) { + poly = L.Util.extend({}, this.options.poly); + layer.options.poly = poly; + } + + // Set different style for editing mode + if (this.options.selectedPathOptions) { + pathOptions = L.Util.extend({}, this.options.selectedPathOptions); + + // Use the existing color of the layer + if (pathOptions.maintainColor) { + pathOptions.color = layer.options.color; + pathOptions.fillColor = layer.options.fillColor; + } + + layer.options.original = L.extend({}, layer.options); + layer.options.editing = pathOptions; + + } + + if (layer instanceof L.Marker) { + if (layer.editing) { + layer.editing.enable(); + } + layer.dragging.enable(); + layer + .on('dragend', this._onMarkerDragEnd) + // #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again. + .on('touchmove', this._onTouchMove, this) + .on('MSPointerMove', this._onTouchMove, this) + .on('touchend', this._onMarkerDragEnd, this) + .on('MSPointerUp', this._onMarkerDragEnd, this); + } else { + layer.editing.enable(); + } + }, + + _disableLayerEdit: function (e) { + var layer = e.layer || e.target || e; + + layer.edited = false; + if (layer.editing) { + layer.editing.disable(); + } + + delete layer.options.editing; + delete layer.options.original; + // Reset layer styles to that of before select + if (this._selectedPathOptions) { + if (layer instanceof L.Marker) { + this._toggleMarkerHighlight(layer); + } else { + // reset the layer style to what is was before being selected + layer.setStyle(layer.options.previousOptions); + // remove the cached options for the layer object + delete layer.options.previousOptions; + } + } + + if (layer instanceof L.Marker) { + layer.dragging.disable(); + layer + .off('dragend', this._onMarkerDragEnd, this) + .off('touchmove', this._onTouchMove, this) + .off('MSPointerMove', this._onTouchMove, this) + .off('touchend', this._onMarkerDragEnd, this) + .off('MSPointerUp', this._onMarkerDragEnd, this); + } else { + layer.editing.disable(); + } + }, + + _onMouseMove: function (e) { + this._tooltip.updatePosition(e.latlng); + }, + + _onMarkerDragEnd: function (e) { + var layer = e.target; + layer.edited = true; + this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer}); + }, + + _onTouchMove: function (e) { + var touchEvent = e.originalEvent.changedTouches[0], + layerPoint = this._map.mouseEventToLayerPoint(touchEvent), + latlng = this._map.layerPointToLatLng(layerPoint); + e.target.setLatLng(latlng); + }, + + _hasAvailableLayers: function () { + return this._featureGroup.getLayers().length !== 0; + } +}); + + + +/** + * @class L.EditToolbar.Delete + * @aka EditToolbar.Delete + */ +L.EditToolbar.Delete = L.Handler.extend({ + statics: { + TYPE: 'remove' // not delete as delete is reserved in js + }, + + includes: L.Mixin.Events, + + // @method intialize(): void + initialize: function (map, options) { + L.Handler.prototype.initialize.call(this, map); + + L.Util.setOptions(this, options); + + // Store the selectable layer group for ease of access + this._deletableLayers = this.options.featureGroup; + + if (!(this._deletableLayers instanceof L.FeatureGroup)) { + throw new Error('options.featureGroup must be a L.FeatureGroup'); + } + + // Save the type so super can fire, need to do this as cannot do this.TYPE :( + this.type = L.EditToolbar.Delete.TYPE; + }, + + // @method enable(): void + enable: function () { + if (this._enabled || !this._hasAvailableLayers()) { + return; + } + this.fire('enabled', { handler: this.type}); + + this._map.fire(L.Draw.Event.DELETESTART, { handler: this.type }); + + L.Handler.prototype.enable.call(this); + + this._deletableLayers + .on('layeradd', this._enableLayerDelete, this) + .on('layerremove', this._disableLayerDelete, this); + }, + + // @method disable(): void + disable: function () { + if (!this._enabled) { return; } + + this._deletableLayers + .off('layeradd', this._enableLayerDelete, this) + .off('layerremove', this._disableLayerDelete, this); + + L.Handler.prototype.disable.call(this); + + this._map.fire(L.Draw.Event.DELETESTOP, { handler: this.type }); + + this.fire('disabled', { handler: this.type}); + }, + + // @method addHooks(): void + addHooks: function () { + var map = this._map; + + if (map) { + map.getContainer().focus(); + + this._deletableLayers.eachLayer(this._enableLayerDelete, this); + this._deletedLayers = new L.LayerGroup(); + + this._tooltip = new L.Draw.Tooltip(this._map); + this._tooltip.updateContent({ text: L.drawLocal.edit.handlers.remove.tooltip.text }); + + this._map.on('mousemove', this._onMouseMove, this); + } + }, + + // @method removeHooks(): void + removeHooks: function () { + if (this._map) { + this._deletableLayers.eachLayer(this._disableLayerDelete, this); + this._deletedLayers = null; + + this._tooltip.dispose(); + this._tooltip = null; + + this._map.off('mousemove', this._onMouseMove, this); + } + }, + + // @method revertLayers(): void + revertLayers: function () { + // Iterate of the deleted layers and add them back into the featureGroup + this._deletedLayers.eachLayer(function (layer) { + this._deletableLayers.addLayer(layer); + layer.fire('revert-deleted', { layer: layer }); + }, this); + }, + + // @method save(): void + save: function () { + this._map.fire(L.Draw.Event.DELETED, { layers: this._deletedLayers }); + }, + + _enableLayerDelete: function (e) { + var layer = e.layer || e.target || e; + + layer.on('click', this._removeLayer, this); + }, + + _disableLayerDelete: function (e) { + var layer = e.layer || e.target || e; + + layer.off('click', this._removeLayer, this); + + // Remove from the deleted layers so we can't accidentally revert if the user presses cancel + this._deletedLayers.removeLayer(layer); + }, + + _removeLayer: function (e) { + var layer = e.layer || e.target || e; + + this._deletableLayers.removeLayer(layer); + + this._deletedLayers.addLayer(layer); + + layer.fire('deleted'); + }, + + _onMouseMove: function (e) { + this._tooltip.updatePosition(e.latlng); + }, + + _hasAvailableLayers: function () { + return this._deletableLayers.getLayers().length !== 0; + } +}); + + + +}(window, document)); +//# sourceMappingURL=leaflet.draw-src.map \ No newline at end of file