├── .gitignore ├── LICENSE.txt ├── Makefile ├── README.md ├── VERSION ├── examples ├── editmarker.png ├── editmarker2.png ├── example1.html ├── example2.html ├── index.html └── tile.png └── src └── leaflet-editable-polyline.js /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | *.swp 3 | *.swo 4 | examples/leaflet.css 5 | examples/leaflet.js 6 | dist/* 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GIT_PORCELAIN_STATUS=$(shell git status --porcelain) 2 | VERSION=$(shell head -1 VERSION) 3 | LEAFLET_VERSION=1.6.0 4 | 5 | build: clean 6 | mkdir dist 7 | yui-compressor src/leaflet-editable-polyline.js > "dist/leaflet-editable-polyline-$(VERSION).js" 8 | clean: 9 | rm -Rf dist 10 | get-leaflet-dist: 11 | # Will retrieve leaflet files if needed: 12 | wget https://unpkg.com/leaflet@$(LEAFLET_VERSION)/dist/leaflet.css -O examples/leaflet.css 13 | wget https://unpkg.com/leaflet@$(LEAFLET_VERSION)/dist/leaflet.js -O examples/leaflet.js 14 | open-examples: get-leaflet-dist 15 | open examples/index.html 16 | show-firefox: get-leaflet-dist 17 | firefox examples/index.html 18 | github-pages: check-all-commited 19 | git branch -D gh-pages 20 | git checkout -b gh-pages 21 | rm .gitignore 22 | cp examples/* . 23 | git add . 24 | git commit -m "gh-pages" 25 | git checkout master 26 | git clean -f 27 | git checkout -- . 28 | check-all-commited: 29 | if [ -n "$(GIT_PORCELAIN_STATUS)" ]; \ 30 | then \ 31 | echo 'YOU HAVE UNCOMMITED CHANGES'; \ 32 | git status; \ 33 | exit 1; \ 34 | fi 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leaflet.js Editable Polylines plugin 2 | 3 | **Work in progress** 4 | 5 | ## Why another editable polyline plugin? 6 | 7 | Most editable polylines have performance problems on bigger polylines because of too many markers shown on the map. 8 | 9 | ## Features 10 | 11 | * Show editable markers only for a selected part of the map and only if no more than specified number of points are shown. 12 | * Add points between two points, before the first point or after the last 13 | * Split polylines 14 | * Keep context data with every point (even if new points are added before this one or the current polyline is splitted from the original one). 15 | 16 | BTW, Leaflet.Editable.Polyline is still a work in progress and some APIs may change. 17 | 18 | **TODO:** 19 | 20 | * Join polylines 21 | 22 | ## Examples 23 | 24 | * [Simple example](http://tkrajina.github.io/leaflet-editable-polyline/example1.html) 25 | * [Example with 20,000 points](http://tkrajina.github.io/leaflet-editable-polyline/example2.html) (you need to zoom close enough to be able to edit it). 26 | 27 | ## How to... 28 | 29 | Initialize the polyline: 30 | 31 | L.tileLayer(osmUrl, {minZoom: 0, maxZoom: 15, attribution: osmAttrib}).addTo(map); 32 | var coordinates = [ [45.2750072361, 13.7187695503], [45.2757622049, 13.7198746204], [45.2763963762, 13.7197780609] /*, ...*/ ]; 33 | var polyline = L.Polyline.PolylineEditor(coordinates, {maxMarkers: 100}).addTo(map); 34 | 35 | The initialization method is: 36 | 37 | L.Polyline.PolylineEditor(coordinates, options, contexts); 38 | 39 | ...or... 40 | 41 | var polyline = L.Polyline.PolylineEditor(coordinates, options); 42 | polyline.addToMap; 43 | 44 | Options is a normal Leaflet polyline options object with some additions: 45 | 46 | * **maxMarkers** is a max number of editable markers to be shown. That means that if the current map bounds are such that more than maxMarkers points of the polyline are in map bounds -- the polyline **will not be editable**. This can be used for very large polylines where too many editable markers would make editing too CPU-intennsive. 47 | * **pointIcon** icon to be shown for point markers. 48 | * **newPointIcon** icon to be shown for middle point markers (markers used when creating new points). 49 | * **newPolylines** if true then double-click on map will create a new polyline (with only one point). 50 | 51 | The editing can be done with: 52 | 53 | * **drag the point marker** to move it around 54 | * **right-click on point marker** to __remove__ the point 55 | * **drag the middle point marker** to create new points between two existing 56 | * **right-click on middle point marker** to __split__ the point 57 | * **click on the first or last point** to add a new first/last point 58 | 59 | When the editing is done, you can retrieve all points with: 60 | 61 | var points = polyline.getPoints() 62 | for(var i = 0; i < points.length; i++) { 63 | var point = points[i]; 64 | console.log('latLng=' + point.getLatLng()); 65 | if(point.context) { 66 | // The original position of this point (new point may be added or 67 | // removed before this one when editing): 68 | console.log('originalPointNo=' + point.context.originalPointNo); 69 | console.log('originalPolylineNo=' + point.context.originalPolylineNo); 70 | } 71 | } 72 | 73 | The **contexts** can be *null* or an array of objects. 74 | Values from the contexts will be stored with every point and can be retrieved later with point.context. 75 | 76 | You can add as many editable polylines as you need. 77 | The resulting polylines can be retrieved with: 78 | 79 | var polylines = map.getEditablePolylines() 80 | 81 | ## License 82 | 83 | Leaflet.Editable.Polyline is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 84 | 85 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.2.beta 2 | -------------------------------------------------------------------------------- /examples/editmarker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkrajina/leaflet-editable-polyline/cb27d5b133f082cdfbdb8f23c877c1743826f631/examples/editmarker.png -------------------------------------------------------------------------------- /examples/editmarker2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkrajina/leaflet-editable-polyline/cb27d5b133f082cdfbdb8f23c877c1743826f631/examples/editmarker2.png -------------------------------------------------------------------------------- /examples/example1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Editable polyline: work in progress 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |

15 | Get points 16 | Reset polyline 17 |

18 | 19 | 20 |
21 | 22 | README 23 | 24 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/example2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Editable polyline: work in progress 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 | README 17 | 18 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Editable polyline: work in progress 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Leaflet editable polyline

13 | 17 | 18 |
19 | 20 | See README for more info. 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /examples/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tkrajina/leaflet-editable-polyline/cb27d5b133f082cdfbdb8f23c877c1743826f631/examples/tile.png -------------------------------------------------------------------------------- /src/leaflet-editable-polyline.js: -------------------------------------------------------------------------------- 1 | L.Polyline.polylineEditor = L.Polyline.extend({ 2 | _prepareMapIfNeeded: function() { 3 | var that = this; 4 | that._changed = false; 5 | 6 | if(this._map._editablePolylines != null) { 7 | return 8 | } 9 | 10 | // Container for all editable polylines on this map: 11 | this._map._editablePolylines = []; 12 | this._map._editablePolylinesEnabled = true; 13 | 14 | // Click anywhere on map to add a new point-polyline: 15 | if(this._options.newPolylines) { 16 | console.log('click na map'); 17 | that._map.on('dblclick', function(event) { 18 | console.log('click, target=' + (event.target == that._map) + ' type=' + event.type); 19 | if(that._map.isEditablePolylinesBusy()) 20 | return; 21 | 22 | var latLng = event.latlng; 23 | if(that._options.newPolylineConfirmMessage) 24 | if(!confirm(that._options.newPolylineConfirmMessage)) 25 | return 26 | 27 | var contexts = [{'originalPolylineNo': null, 'originalPointNo': null}]; 28 | L.Polyline.PolylineEditor([latLng], that._options, contexts).addTo(that._map); 29 | 30 | that._showBoundMarkers(); 31 | that._changed = true; 32 | }); 33 | } 34 | 35 | /** 36 | * Check if there is *any* busy editable polyline on this map. 37 | */ 38 | this._map.isEditablePolylinesBusy = function() { 39 | var map = this; 40 | for(var i = 0; i < map._editablePolylines.length; i++) 41 | if(map._editablePolylines[i]._isBusy()) 42 | return true; 43 | 44 | return false; 45 | }; 46 | 47 | /** 48 | * Enable/disable editing. 49 | */ 50 | this._map.setEditablePolylinesEnabled = function(enabled) { 51 | var map = this; 52 | map._editablePolylinesEnabled = enabled; 53 | for(var i = 0; i < map._editablePolylines.length; i++) { 54 | var polyline = map._editablePolylines[i]; 55 | if(enabled) { 56 | polyline._showBoundMarkers(); 57 | } else { 58 | polyline._hideAll(); 59 | } 60 | } 61 | }; 62 | 63 | /* 64 | * Utility method added to this map to retreive editable 65 | * polylines. 66 | */ 67 | this._map.getEditablePolylines = function() { 68 | var map = this; 69 | return map._editablePolylines; 70 | } 71 | 72 | this._map.fixAroundEditablePoint = function(marker) { 73 | var map = this; 74 | for(var i = 0; i < map._editablePolylines.length; i++) { 75 | var polyline = map._editablePolylines[i]; 76 | polyline._reloadPolyline(marker); 77 | } 78 | } 79 | }, 80 | /** 81 | * Will add all needed methods to this polyline. 82 | */ 83 | _addMethods: function() { 84 | var that = this; 85 | 86 | this._init = function(options, contexts) { 87 | this._prepareMapIfNeeded(); 88 | 89 | /** 90 | * Since all point editing is done by marker events, markers 91 | * will be the main holder of the polyline points locations. 92 | * Every marker contains a reference to the newPointMarker 93 | * *before* him (=> the first marker has newPointMarker=null). 94 | */ 95 | this._parseOptions(options); 96 | 97 | this._markers = []; 98 | var points = this.getLatLngs(); 99 | var length = points.length; 100 | for(var i = 0; i < length; i++) { 101 | var marker = this._addMarkers(i, points[i]); 102 | if(! ('context' in marker)) { 103 | marker.context = {} 104 | if(that._contexts != null) { 105 | marker.context = contexts[i]; 106 | } 107 | } 108 | 109 | if(marker.context && ! ('originalPointNo' in marker.context)) 110 | marker.context.originalPointNo = i; 111 | if(marker.context && ! ('originalPolylineNo' in marker.context)) 112 | marker.context.originalPolylineNo = that._map._editablePolylines.length; 113 | } 114 | 115 | // Map move => show different editable markers: 116 | var map = this._map; 117 | this._map.on("zoomend", function(e) { 118 | that._showBoundMarkers(); 119 | }); 120 | this._map.on("moveend", function(e) { 121 | that._showBoundMarkers(); 122 | }); 123 | 124 | if(this._desiredPolylineNo && this._desiredPolylineNo != null) { 125 | this._map._editablePolylines.splice(this._desiredPolylineNo, 0, this); 126 | } else { 127 | this._map._editablePolylines.push(this); 128 | } 129 | }; 130 | 131 | /** 132 | * Check if is busy adding/moving new nodes. Note, there may be 133 | * *other* editable polylines on the same map which *are* busy. 134 | */ 135 | this._isBusy = function() { 136 | return that._busy; 137 | }; 138 | 139 | this._setBusy = function(busy) { 140 | that._busy = busy; 141 | }; 142 | 143 | /** 144 | * Get markers for this polyline. 145 | */ 146 | this.getPoints = function() { 147 | return this._markers; 148 | }; 149 | 150 | this.isChanged = function() { 151 | return this._changed; 152 | } 153 | 154 | this._parseOptions = function(options) { 155 | if(!options) 156 | options = {}; 157 | 158 | // Do not show edit markers if more than maxMarkers would be shown: 159 | if(!('maxMarkers' in options)) 160 | options.maxMarkers = 100; 161 | if(!('newPolylines' in options)) 162 | options.newPolylines = false; 163 | if(!('newPolylineConfirmMessage' in options)) 164 | options.newPolylineConfirmMessage = ''; 165 | if(!('addFirstLastPointEvent' in options)) 166 | options.addFirstLastPointEvent = 'click'; 167 | if(!('customPointListeners' in options)) 168 | options.customPointListeners = {}; 169 | if(!('customNewPointListeners' in options)) 170 | options.customNewPointListeners = {}; 171 | 172 | this._options = options; 173 | 174 | // Icons: 175 | if(!options.pointIcon) 176 | this._options.pointIcon = L.icon({ iconUrl: 'editmarker.png', iconSize: [11, 11], iconAnchor: [6, 6] }); 177 | if(!options.newPointIcon) 178 | this._options.newPointIcon = L.icon({ iconUrl: 'editmarker2.png', iconSize: [11, 11], iconAnchor: [6, 6] }); 179 | }; 180 | 181 | /** 182 | * Show only markers in current map bounds *is* there are only a certain 183 | * number of markers. This method is called on eventy that change map 184 | * bounds. 185 | */ 186 | this._showBoundMarkers = function() { 187 | if (!that._map) { 188 | return; 189 | } 190 | 191 | this._setBusy(false); 192 | 193 | if(!that._map._editablePolylinesEnabled) { 194 | console.log('Do not show because editing is disabled'); 195 | return; 196 | } 197 | 198 | var bounds = that._map.getBounds(); 199 | var found = 0; 200 | for(var polylineNo in that._map._editablePolylines) { 201 | var polyline = that._map._editablePolylines[polylineNo]; 202 | for(var markerNo in polyline._markers) { 203 | var marker = polyline._markers[markerNo]; 204 | if(bounds.contains(marker.getLatLng())) 205 | found += 1; 206 | } 207 | } 208 | 209 | for(var polylineNo in that._map._editablePolylines) { 210 | var polyline = that._map._editablePolylines[polylineNo]; 211 | for(var markerNo in polyline._markers) { 212 | var marker = polyline._markers[markerNo]; 213 | if(found < that._options.maxMarkers) { 214 | that._setMarkerVisible(marker, bounds.contains(marker.getLatLng())); 215 | that._setMarkerVisible(marker.newPointMarker, markerNo > 0 && bounds.contains(marker.getLatLng())); 216 | } else { 217 | that._setMarkerVisible(marker, false); 218 | that._setMarkerVisible(marker.newPointMarker, false); 219 | } 220 | } 221 | } 222 | }; 223 | 224 | /** 225 | * Used when adding/moving points in order to disable the user to mess 226 | * with other markers (+ easier to decide where to put the point 227 | * without too many markers). 228 | */ 229 | this._hideAll = function(except) { 230 | this._setBusy(true); 231 | for(var polylineNo in that._map._editablePolylines) { 232 | console.log("hide " + polylineNo + " markers"); 233 | var polyline = that._map._editablePolylines[polylineNo]; 234 | for(var markerNo in polyline._markers) { 235 | var marker = polyline._markers[markerNo]; 236 | if(except == null || except != marker) 237 | polyline._setMarkerVisible(marker, false); 238 | if(except == null || except != marker.newPointMarker) 239 | polyline._setMarkerVisible(marker.newPointMarker, false); 240 | } 241 | } 242 | } 243 | 244 | /** 245 | * Show/hide marker. 246 | */ 247 | this._setMarkerVisible = function(marker, show) { 248 | if(!marker) 249 | return; 250 | 251 | var map = this._map; 252 | if(show) { 253 | if(!marker._visible) { 254 | if(!marker._map) { // First show for this marker: 255 | marker.addTo(map); 256 | } else { // Marker was already shown and hidden: 257 | map.addLayer(marker); 258 | } 259 | marker._map = map; 260 | } 261 | marker._visible = true; 262 | } else { 263 | if(marker._visible) { 264 | map.removeLayer(marker); 265 | } 266 | marker._visible = false; 267 | } 268 | }; 269 | 270 | /** 271 | * Reload polyline. If it is busy, then the bound markers will not be 272 | * shown. 273 | */ 274 | this._reloadPolyline = function(fixAroundPointNo) { 275 | that.setLatLngs(that._getMarkerLatLngs()); 276 | if(fixAroundPointNo != null) 277 | that._fixAround(fixAroundPointNo); 278 | that._showBoundMarkers(); 279 | that._changed = true; 280 | } 281 | 282 | /** 283 | * Add two markers (a point marker and his newPointMarker) for a 284 | * single point. 285 | * 286 | * Markers are not added on the map here, the marker.addTo(map) is called 287 | * only later when needed first time because of performance issues. 288 | */ 289 | this._addMarkers = function(pointNo, latLng, fixNeighbourPositions) { 290 | var that = this; 291 | var points = this.getLatLngs(); 292 | var marker = L.marker(latLng, {draggable: true, icon: this._options.pointIcon}); 293 | 294 | marker.newPointMarker = null; 295 | marker.on('dragstart', function(event) { 296 | var pointNo = that._getPointNo(event.target); 297 | var previousPoint = pointNo && pointNo > 0 ? that._markers[pointNo - 1].getLatLng() : null; 298 | var nextPoint = pointNo < that._markers.length - 1 ? that._markers[pointNo + 1].getLatLng() : null; 299 | that._setupDragLines(marker, previousPoint, nextPoint); 300 | that._hideAll(marker); 301 | }); 302 | marker.on('dragend', function(event) { 303 | var marker = event.target; 304 | var pointNo = that._getPointNo(event.target); 305 | setTimeout(function() { 306 | that._reloadPolyline(pointNo); 307 | }, 25); 308 | }); 309 | marker.on('contextmenu', function(event) { 310 | var marker = event.target; 311 | var pointNo = that._getPointNo(event.target); 312 | that._map.removeLayer(marker); 313 | that._map.removeLayer(newPointMarker); 314 | that._markers.splice(pointNo, 1); 315 | that._reloadPolyline(pointNo); 316 | }); 317 | marker.on(that._options.addFirstLastPointEvent, function(event) { 318 | 319 | console.log('click on marker'); 320 | var marker = event.target; 321 | var pointNo = that._getPointNo(event.target); 322 | console.log('pointNo=' + pointNo + ' that._markers.length=' + that._markers.length); 323 | event.dont; 324 | if(pointNo == 0 || pointNo == that._markers.length - 1) { 325 | console.log('first or last'); 326 | that._prepareForNewPoint(marker, pointNo == 0 ? 0 : pointNo + 1); 327 | } else { 328 | console.log('not first or last'); 329 | } 330 | }); 331 | 332 | var previousPoint = points[pointNo == 0 ? pointNo : pointNo - 1]; 333 | var newPointMarker = L.marker([(latLng.lat + previousPoint.lat) / 2., 334 | (latLng.lng + previousPoint.lng) / 2.], 335 | {draggable: true, icon: this._options.newPointIcon}); 336 | marker.newPointMarker = newPointMarker; 337 | newPointMarker.on('dragstart', function(event) { 338 | var pointNo = that._getPointNo(event.target); 339 | var previousPoint = that._markers[pointNo - 1].getLatLng(); 340 | var nextPoint = that._markers[pointNo].getLatLng(); 341 | that._setupDragLines(marker.newPointMarker, previousPoint, nextPoint); 342 | 343 | that._hideAll(marker.newPointMarker); 344 | }); 345 | newPointMarker.on('dragend', function(event) { 346 | var marker = event.target; 347 | var pointNo = that._getPointNo(event.target); 348 | that._addMarkers(pointNo, marker.getLatLng(), true); 349 | setTimeout(function() { 350 | that._reloadPolyline(); 351 | }, 25); 352 | }); 353 | newPointMarker.on('contextmenu', function(event) { 354 | // 1. Remove this polyline from map 355 | var marker = event.target; 356 | var pointNo = that._getPointNo(marker); 357 | var markers = that.getPoints(); 358 | that._hideAll(); 359 | 360 | var secondPartMarkers = that._markers.slice(pointNo, pointNo.length); 361 | that._markers.splice(pointNo, that._markers.length - pointNo); 362 | 363 | that._reloadPolyline(); 364 | 365 | var points = []; 366 | var contexts = []; 367 | for(var i = 0; i < secondPartMarkers.length; i++) { 368 | var marker = secondPartMarkers[i]; 369 | points.push(marker.getLatLng()); 370 | contexts.push(marker.context); 371 | } 372 | 373 | console.log('points:' + points); 374 | console.log('contexts:' + contexts); 375 | 376 | // Need to know the current polyline order numbers, because 377 | // the splitted one need to be inserted immediately after: 378 | var originalPolylineNo = that._map._editablePolylines.indexOf(that); 379 | 380 | L.Polyline.PolylineEditor(points, that._options, contexts, originalPolylineNo + 1) 381 | .addTo(that._map); 382 | 383 | that._showBoundMarkers(); 384 | }); 385 | 386 | this._markers.splice(pointNo, 0, marker); 387 | 388 | // User-defined custom event listeners: 389 | if(that._options.customPointListeners) 390 | for(var eventName in that._options.customPointListeners) 391 | marker.on(eventName, that._options.customPointListeners[eventName]); 392 | if(that._options.customNewPointListeners) 393 | for(var eventName in that._options.customNewPointListeners) 394 | newPointMarker.on(eventName, that._options.customNewPointListeners[eventName]); 395 | 396 | if(fixNeighbourPositions) { 397 | this._fixAround(pointNo); 398 | } 399 | 400 | return marker; 401 | }; 402 | 403 | /** 404 | * Event handlers for first and last point. 405 | */ 406 | this._prepareForNewPoint = function(marker, pointNo) { 407 | // This is slightly delayed to prevent the same propagated event 408 | // to be catched here: 409 | setTimeout( 410 | function() { 411 | that._hideAll(); 412 | that._setupDragLines(marker, marker.getLatLng()); 413 | that._map.once('click', function(event) { 414 | if(that._markers.length == 1) { 415 | pointNo += 1; 416 | } 417 | console.log('dodajemo na ' + pointNo + ' - ' + event.latlng); 418 | that._addMarkers(pointNo, event.latlng, true); 419 | that._reloadPolyline(); 420 | }); 421 | }, 422 | 100 423 | ); 424 | }; 425 | 426 | /** 427 | * Fix nearby new point markers when the new point is created. 428 | */ 429 | this._fixAround = function(pointNoOrMarker) { 430 | if((typeof pointNoOrMarker) == 'number') 431 | var pointNo = pointNoOrMarker; 432 | else 433 | var pointNo = that._markers.indexOf(pointNoOrMarker); 434 | 435 | if(pointNo < 0) 436 | return; 437 | 438 | var previousMarker = pointNo == 0 ? null : that._markers[pointNo - 1]; 439 | var marker = that._markers[pointNo]; 440 | var nextMarker = pointNo < that._markers.length - 1 ? that._markers[pointNo + 1] : null; 441 | if(marker && previousMarker) { 442 | marker.newPointMarker.setLatLng([(previousMarker.getLatLng().lat + marker.getLatLng().lat) / 2., 443 | (previousMarker.getLatLng().lng + marker.getLatLng().lng) / 2.]); 444 | } 445 | if(marker && nextMarker) { 446 | nextMarker.newPointMarker.setLatLng([(marker.getLatLng().lat + nextMarker.getLatLng().lat) / 2., 447 | (marker.getLatLng().lng + nextMarker.getLatLng().lng) / 2.]); 448 | } 449 | }; 450 | 451 | /** 452 | * Find the order number of the marker. 453 | */ 454 | this._getPointNo = function(marker) { 455 | for(var i = 0; i < this._markers.length; i++) { 456 | if(marker == this._markers[i] || marker == this._markers[i].newPointMarker) { 457 | return i; 458 | } 459 | } 460 | return -1; 461 | }; 462 | 463 | /** 464 | * Get polyline latLngs based on marker positions. 465 | */ 466 | this._getMarkerLatLngs = function() { 467 | var result = []; 468 | for(var i = 0; i < this._markers.length; i++) 469 | result.push(this._markers[i].getLatLng()); 470 | return result; 471 | }; 472 | 473 | this._setupDragLines = function(marker, point1, point2) { 474 | var line1 = null; 475 | var line2 = null; 476 | if(point1) line1 = L.polyline([marker.getLatLng(), point1], {dasharray: "5,1", weight: 1}) 477 | .addTo(that._map); 478 | if(point2) line2 = L.polyline([marker.getLatLng(), point2], {dasharray: "5,1", weight: 1}) 479 | .addTo(that._map); 480 | 481 | var moveHandler = function(event) { 482 | if(line1) 483 | line1.setLatLngs([event.latlng, point1]); 484 | if(line2) 485 | line2.setLatLngs([event.latlng, point2]); 486 | }; 487 | 488 | var stopHandler = function(event) { 489 | if (that._map) { 490 | that._map.off('mousemove', moveHandler); 491 | marker.off('dragend', stopHandler); 492 | if(line1) that._map.removeLayer(line1); 493 | if(line2) that._map.removeLayer(line2); 494 | console.log('STOPPED'); 495 | if(event.target != that._map) { 496 | that._map.fire('click', event); 497 | } 498 | } 499 | }; 500 | 501 | that._map.on('mousemove', moveHandler); 502 | marker.on('dragend', stopHandler); 503 | 504 | that._map.once('click', stopHandler); 505 | marker.once('click', stopHandler); 506 | if(line1) line1.once('click', stopHandler); 507 | if(line2) line2.once('click', stopHandler); 508 | } 509 | } 510 | }); 511 | 512 | L.Polyline.polylineEditor.addInitHook(function () { 513 | this.on('add', function(event) { 514 | this._map = event.target._map; 515 | this._addMethods(); 516 | 517 | /** 518 | * When addint a new point we must disable the user to mess with other 519 | * markers. One way is to check everywhere if the user is busy. The 520 | * other is to just remove other markers when the user is doing 521 | * somethinng. 522 | * 523 | * TODO: Decide the right way to do this and then leave only _busy or 524 | * _hideAll(). 525 | */ 526 | this._busy = false; 527 | this._initialized = false; 528 | 529 | this._init(this._options, this._contexts); 530 | 531 | this._initialized = true; 532 | 533 | return this; 534 | }); 535 | 536 | this.on('remove', function(event) { 537 | var polyline = event.target; 538 | var map = polyline._map; 539 | var polylines = map.getEditablePolylines(); 540 | var index = polylines.indexOf(polyline); 541 | if (index > -1) { 542 | polylines[index]._markers.forEach(function(marker) { 543 | map.removeLayer(marker); 544 | if(marker.newPointMarker) 545 | map.removeLayer(marker.newPointMarker); 546 | }); 547 | polylines.splice(index, 1); 548 | } 549 | }); 550 | }); 551 | 552 | /** 553 | * Construct a new editable polyline. 554 | * 555 | * latlngs ... a list of points (or two-element tuples with coordinates) 556 | * options ... polyline options 557 | * contexts ... custom contexts for every point in the polyline. Must have the 558 | * same number of elements as latlngs and this data will be 559 | * preserved when new points are added or polylines splitted. 560 | * polylineNo ... insert this polyline in a specific order (used when splitting). 561 | * 562 | * More about contexts: 563 | * This is an array of objects that will be kept as "context" for every 564 | * point. Marker will keep this value as marker.context. New markers will 565 | * have context set to null. 566 | * 567 | * Contexts must be the same size as the polyline size! 568 | * 569 | * By default, even without calling this method -- every marker will have 570 | * context with one value: marker.context.originalPointNo with the 571 | * original order number of this point. The order may change if some 572 | * markers before this one are delted or new added. 573 | */ 574 | L.Polyline.PolylineEditor = function(latlngs, options, contexts, polylineNo) { 575 | // Since the app code may not be able to explicitly call the 576 | // initialization of all editable polylines (if the user created a new 577 | // one by splitting an existing), with this method you can control the 578 | // options for new polylines: 579 | if(options.prepareOptions) { 580 | options.prepareOptions(options); 581 | } 582 | 583 | var result = new L.Polyline.polylineEditor(latlngs, options); 584 | result._options = options; 585 | result._contexts = contexts; 586 | result._desiredPolylineNo = polylineNo 587 | 588 | return result; 589 | }; 590 | --------------------------------------------------------------------------------