├── dist ├── css │ └── leaflet.draw.topology.css ├── icons │ └── marker_blk.svg └── js │ └── leaflet.draw.topology.js ├── src ├── css │ └── leaflet.draw.topology.css ├── icons │ └── marker_blk.svg └── js │ └── leaflet.draw.topology.js ├── .gitignore ├── package.json ├── gruntfile.js ├── test ├── index.html ├── data.geojson └── lib │ └── zepto.min.js ├── README.md └── LICENSE.md /dist/css/leaflet.draw.topology.css: -------------------------------------------------------------------------------- 1 | .hidden{display:none}.propagateDrag{pointer-events:none} -------------------------------------------------------------------------------- /src/css/leaflet.draw.topology.css: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none; 3 | } 4 | 5 | .propagateDrag { 6 | pointer-events:none; 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | 9 | npm-debug.log 10 | node_modules 11 | 12 | .DS_Store 13 | .AppleDouble 14 | .LSOverride 15 | Icon 16 | 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear on external disk 22 | .Spotlight-V100 23 | .Trashes 24 | -------------------------------------------------------------------------------- /dist/icons/marker_blk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/icons/marker_blk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leaflet-draw-topology", 3 | "version": "0.0.3", 4 | "description": "Topological editing for Leaflet", 5 | "main": "dist/leaflet.draw.topology.js", 6 | "scripts": { 7 | "build": "grunt" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jczaplewski/Leaflet.draw.topology.git" 12 | }, 13 | "keywords": [ 14 | "topology", 15 | "editing", 16 | "vector", 17 | "maps" 18 | ], 19 | "author": "John J Czaplewski", 20 | "license": "CC0", 21 | "dependencies": { 22 | "leaflet": "*", 23 | "leaflet-geometryutil": "*", 24 | "leaflet-draw": "*" 25 | }, 26 | "devDependencies": { 27 | "grunt": "0.4.x", 28 | "grunt-contrib-uglify": "0.2.x", 29 | "grunt-contrib-watch": "0.5.x", 30 | "grunt-contrib-cssmin": "0.7.x", 31 | "grunt-cssc": "0.2.x", 32 | "matchdep": "0.3.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | require("matchdep").filterDev("grunt-*").forEach(grunt.loadNpmTasks); 4 | 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | 8 | uglify: { 9 | build: { 10 | files: { 11 | 'dist/js/leaflet.draw.topology.js': 'src/js/leaflet.draw.topology.js' 12 | } 13 | } 14 | }, 15 | 16 | cssmin: { 17 | combine: { 18 | files: { 19 | 'dist/css/leaflet.draw.topology.css': ['src/css/leaflet.draw.topology.css'] 20 | } 21 | } 22 | }, 23 | 24 | watch: { 25 | js: { 26 | files: ['src/js/leaflet.draw.topology.js'], 27 | tasks: ['uglify'] 28 | }, 29 | css: { 30 | files: ['src/css/leaflet.draw.topology.css'], 31 | tasks: ['cssmin'] 32 | } 33 | } 34 | }); 35 | 36 | grunt.registerTask('default', ['uglify', 'cssmin']); 37 | 38 | }; -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /test/data.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | -113, 13 | 37 14 | ], 15 | [ 16 | -113, 17 | 40 18 | ], 19 | [ 20 | -109, 21 | 40 22 | ], 23 | [ 24 | -109, 25 | 37 26 | ], 27 | [ 28 | -113, 29 | 37 30 | ] 31 | ] 32 | ] 33 | } 34 | }, 35 | { 36 | "type": "Feature", 37 | "properties": {}, 38 | "geometry": { 39 | "type": "Polygon", 40 | "coordinates": [ 41 | [ 42 | [ 43 | -109, 44 | 37 45 | ], 46 | [ 47 | -109, 48 | 39 49 | ], 50 | [ 51 | -104, 52 | 39 53 | ], 54 | [ 55 | -104, 56 | 37 57 | ], 58 | [ 59 | -109, 60 | 37 61 | ] 62 | ] 63 | ] 64 | } 65 | }, 66 | { 67 | "type": "Feature", 68 | "properties": {}, 69 | "geometry": { 70 | "type": "Polygon", 71 | "coordinates": [ 72 | [ 73 | [ 74 | -109, 75 | 34 76 | ], 77 | [ 78 | -109, 79 | 37 80 | ], 81 | [ 82 | -102, 83 | 37 84 | ], 85 | [ 86 | -102, 87 | 34 88 | ], 89 | [ 90 | -109, 91 | 34 92 | ] 93 | ] 94 | ] 95 | } 96 | }, 97 | { 98 | "type": "Feature", 99 | "properties": {}, 100 | "geometry": { 101 | "type": "Polygon", 102 | "coordinates": [ 103 | [ 104 | [ 105 | -104, 106 | 37 107 | ], 108 | [ 109 | -104, 110 | 40 111 | ], 112 | [ 113 | -100, 114 | 40 115 | ], 116 | [ 117 | -100, 118 | 37 119 | ], 120 | [ 121 | -104, 122 | 37 123 | ] 124 | ] 125 | ] 126 | } 127 | } 128 | ] 129 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leaflet.draw.topology 2 | Topology editing extension to [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw/). Video demo can be found [on YouTube](https://www.youtube.com/watch?v=rWVRIoIG3gc). 3 | 4 | ##Getting started 5 | The plugin can be initialized as so: 6 | 7 | ```` 8 | L.Edit.Topology.init(map, layer, options); 9 | ```` 10 | 11 | ### Parameters 12 | | Parameter | Type | Required | Description 13 | | --- | --- | --- | --- 14 | | map | [L.map](http://leafletjs.com/reference.html#map-class) | true | The map being used 15 | | layer | [LayerGroup](#http://leafletjs.com/reference.html#layergroup), [FeatureGroup](http://leafletjs.com/reference.html#featuregroup), or [GeoJson](http://leafletjs.com/reference.html#geojson) | true | The layer for which the topological relationships should be preserved 16 | | options | [L.icon](http://leafletjs.com/reference.html#icon) | false | Object with one key - `icon`. Used for defining a custom drag handle for editing. 17 | 18 | ### Example map 19 | 20 | ```` 21 | // Initialize a map 22 | var map = new L.map("map").setView([38.5, -105], 6); 23 | 24 | // Add a tile layer 25 | L.tileLayer('http://{s}.acetate.geoiq.com/tiles/acetate/{z}/{x}/{y}.png', { 26 | maxZoom: 18, 27 | attribution: ' ©2012 Esri & Stamen, Data from OSM and Natural Earth' 28 | }).addTo(map); 29 | 30 | // Load a GeoJSON file 31 | $.getJSON("data.geojson", function(data) { 32 | 33 | // Create, style, and add a GeoJson layer 34 | var geojson = L.geoJson(data, { 35 | style: { 36 | fillColor: '#777', 37 | color: '#444', 38 | weight: 3, 39 | opacity: 0.8 40 | } 41 | }).addTo(map); 42 | 43 | // Enable topology editing on the GeoJSON layer and use a custom marker 44 | L.Edit.Topology.init(map, geojson, { 45 | "icon": new L.Icon({ 46 | iconUrl: '../src/icons/marker_blk.svg', 47 | iconSize: [12, 12] 48 | }) 49 | }); 50 | }); 51 | ```` 52 | 53 | ## Dependencies 54 | - [Leaflet](http://leafletjs.com) 55 | - [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw/) 56 | - [Leaflet.Snap](https://github.com/makinacorpus/Leaflet.Snap) 57 | - [Leaflet.GeometryUtil](https://github.com/makinacorpus/Leaflet.GeometryUtil) 58 | 59 | ## Example 60 | Demo can be found in the ````test```` folder. 61 | 62 | 63 | ## Development 64 | ```` 65 | git clone https://github.com/jczaplew/Leaflet.draw.topology.git 66 | cd Leaflet.draw.topology 67 | npm install 68 | git clone https://github.com/makinacorpus/Leaflet.Snap node_modules/Leaflet.Snap 69 | ```` 70 | To validate and minify the JavaScript and CSS, run ````grunt```` 71 | 72 | 73 | ## License 74 | All code unique to this plugin uses a [CC0 1.0 Public Domain Dedication](http://creativecommons.org/publicdomain/zero/1.0/). It would be nice if you cited the author(s), but it is not necessary. [Leaflet.draw](https://github.com/Leaflet/Leaflet.draw/) and [Leaflet.Snap](https://github.com/makinacorpus/Leaflet.Snap) are used under an [MIT License](https://github.com/makinacorpus/Leaflet.Snap/blob/gh-pages/LICENSE), and [Leaflet.GeometryUtil](https://github.com/makinacorpus/Leaflet.GeometryUtil) is used under a [BSD License](https://github.com/makinacorpus/Leaflet.GeometryUtil/blob/master/LICENSE). 75 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. -------------------------------------------------------------------------------- /dist/js/leaflet.draw.topology.js: -------------------------------------------------------------------------------- 1 | if(!L.Edit.Poly||!L.Handler.MarkerSnap)throw"Error: leaflet.draw.topology requires Leaflet.Draw and Leaflet.Snap";L.Edit.Poly.prototype.addHooks=function(){this._poly._map&&(this._markerGroup||this._initMarkers(),this._poly._map.addLayer(this._markerGroup),this._findTwins())},L.Edit.Poly.prototype._findTwins=function(){Object.keys(this._poly._map._layers).forEach(function(a){Object.keys(this._poly._map._layers).forEach(function(b){this._poly._map._layers[a]._origLatLng&&this._poly._map._layers[b]._origLatLng&&this._poly._map._layers[a]._origLatLng.lat===this._poly._map._layers[b]._origLatLng.lat&&this._poly._map._layers[a]._origLatLng.lng===this._poly._map._layers[b]._origLatLng.lng&&this._poly._map._layers[a]._leaflet_id!==this._poly._map._layers[b]._leaflet_id&&(this._poly._map._layers[a]._twinMarkers||(this._poly._map._layers[a]._twinMarkers=[]),this._poly._map._layers[a]._twinMarkers.indexOf(this._poly._map._layers[b])<0&&this._poly._map._layers[a]._twinMarkers.push(this._poly._map._layers[b]),this._poly._map._layers[b]._twinMarkers||(this._poly._map._layers[b]._twinMarkers=[]),this._poly._map._layers[b]._twinMarkers.indexOf(this._poly._map._layers[a])<0&&this._poly._map._layers[b]._twinMarkers.push(this._poly._map._layers[a]))}.bind(this))}.bind(this))},L.Edit.Poly.prototype._initMarkers=function(){this._markerGroup||(this._markerGroup=new L.LayerGroup),this._markers=[];for(var a,b,c,d,e=this._poly._latlngs,a=0,c=e.length;c>a;a++)if(this._primary)d=this._createMarker(e[a],a),d.on("click",this._onMarkerClick,this),this._markers.push(d);else if(e[a]._twinLayers)for(var b=0;ba;b=a++)(0!==a||L.Polygon&&this._poly instanceof L.Polygon)&&(f=this._markers[b],g=this._markers[a],this._updatePrevNext(f,g));this._updateIndexes()},L.Edit.Poly.prototype._createMarker=function(a,b){var c=new L.Marker(a,{draggable:!0,icon:this.options.icon});return c._origLatLng=a,c._index=b?b:a._index,c._layer=a._layer,a._hasTwin&&(c._hasTwin=!0,c._twinLayers=a._twinLayers),a._isMiddle&&(c._isMiddle=!0),c.on("drag",this._onMarkerDrag,this),c.on("dragend",this._fireEdit,this),this._markerGroup.addLayer(c),c},L.Edit.Poly.prototype._onMarkerClick=function(){},L.Edit.Poly.prototype._onMarkerDrag=function(a){var b=a.target,c=(b._origLatLng,[]);L.extend(b._origLatLng,b._latlng),b._middleLeft&&b._middleLeft.setLatLng(this._getMiddleLatLng(b._prev,b)),b._middleRight&&b._middleRight.setLatLng(this._getMiddleLatLng(b,b._next)),c.push(b._layer),b._hasTwin&&b._twinMarkers.forEach(function(a){a.setLatLng(b._latlng),a.update(),this._poly._map._layers[a._layer]._latlngs.forEach(function(a){a.lat===b._origLatLng.lat&&a.lng===b._origLatLng.lng&&(a.lat=b._latlng.lat,a.lng=b._latlng.lng)}),L.extend(a._origLatLng,b._latlng),c.push(a._layer)}.bind(this)),c.forEach(function(a){this._poly._map._layers[a].editing._poly.redraw()}.bind(this))},L.Handler.MarkerSnap.prototype._updateSnap=function(a,b,c){b&&c?(a._latlng=L.latLng(c),a.update(),a.snap!=b&&(a.snap=b,a._icon&&L.DomUtil.addClass(a._icon,"marker-snapped"),this._map.fire("snap",{marker_id:a._leaflet_id,layer_id:b._leaflet_id,marker:a}),a.fire("snap",{layer:b,latlng:c}))):(a.snap&&(a._icon&&L.DomUtil.removeClass(a._icon,"marker-snapped"),this._map.fire("unsnap",{marker_id:a._leaflet_id,marker:a}),a.fire("unsnap",{layer:a.snap})),delete a.snap)},L.Edit.Topology={init:function(a,b,c,d,e){this._map=a,this._map._editStatus={layer:null,editing:!1},this._layers=b;var f=c&&c.icon?c.icon:new L.DivIcon({iconSize:new L.Point(10,10),className:"leaflet-div-icon leaflet-editing-icon"});f.options.className=f.options.className+" propagateDrag",this.marker=new L.Marker(this._map.getCenter(),{icon:f,repeatMode:!1,zIndexOffset:2e3}).addTo(this._map),this._map.on("mousemove",function(a){this.marker.setLatLng(a.latlng)}.bind(this)),this.marker.snapediting=new L.Handler.MarkerSnap(this._map,this.marker),this.marker.snapediting.addGuideLayer(b),this.marker.snapediting.enable(),this._map.removeLayer(this.marker),this.marker.snapediting.disable(),this._map.on("snap",function(a){L.DomUtil.removeClass(a.marker._icon,"hidden"),this._map._snapLayer=a.layer_id}.bind(this)),this._map.on("unsnap",function(a){L.DomUtil.addClass(a.marker._icon,"hidden"),this._map._snapLayer=""}.bind(this)),Object.keys(b._layers).forEach(function(a){b._layers[a].on("click",function(a){if(d){if(a.target.editing._enabled&&this._map._snapLayer){{new L.Marker(this.marker.getLatLng(),{icon:L.Edit.Poly.prototype.options.icon,draggable:!0})}this._map.fire("marker-created",this.marker)}}else if(a.target.editing._enabled)if(this._map._snapLayer){{new L.Marker(this.marker.getLatLng(),{icon:L.Edit.Poly.prototype.options.icon,draggable:!0})}this._map.fire("marker-created",this.marker)}else a.target.editing._primary?(this._map.removeLayer(this.marker),this.marker.snapediting.disable(),this._findAdjacencies("disable",a.target)):(this.disableEditing(this._map._editStatus.layer._leaflet_id,this._map._editStatus.layer.adjacencies),this._findAdjacencies("enable",a.target));else this._map._editStatus.editing?(this.disableEditing(this._map._editStatus.layer._leaflet_id,this._map._editStatus.layer.adjacencies),this._findAdjacencies("enable",a.target)):(this._map.addLayer(this.marker),this.marker.snapediting.enable(),this._findAdjacencies("enable",a.target))}.bind(this))}.bind(this)),b.addTo(this._map),this._map.on("marker-created",function(b){if(this._map._snapLayer){for(var c=this._map._snapLayer,d=[],e={lat:b._latlng.lat,lng:b._latlng.lng},f=this._map._layers[c].editing._poly._latlngs,g=0;g1&&f[i]._twinLayers.length>1){var a=f[h]._twinLayers.filter(function(a){return-1!=f[i]._twinLayers.indexOf(a)}.bind(this));if(a.length>1){a.splice(a.indexOf(c.toString()),1);var d=a}else var d=a;if(0===d.length){var e=f[h]._twinLayers.filter(function(a){return f[i]._twinLayers.indexOf(a)>-1}.bind(this));if(e.length!==a.length)return;e.splice(e.indexOf(c.toString()),1);var g=e[0]}else var g=d[0]}else if(f[h]._twinLayers.length>1&&1===f[i]._twinLayers.length)var g=f[i]._twinLayers[0];else if(1===f[h]._twinLayers.length&&f[i]._twinLayers.length>1)var g=f[h]._twinLayers[0];else if(1===f[h]._twinLayers.length&&1===f[i]._twinLayers.length)return;for(var j=this._map._layers[g].editing._poly._latlngs,k=0;kd)return!1;var f=(c.lng-a.lng)*(b.lng-a.lng)+(c.lat-a.lat)*(b.lat-a.lat);if(0>f)return!1;var g=(b.lng-a.lng)*(b.lng-a.lng)+(b.lat-a.lat)*(b.lat-a.lat);return f>g?!1:!0},redrawPolys:function(a){a.forEach(function(a){this._map._layers[a].editing.enabled&&(this._map._layers[a].editing.removeHooks(),this._map._layers[a].editing.addHooks())}.bind(this))},reset:function(a){Object.keys(this._map._layers).forEach(function(a){Object.keys(this._map._layers).forEach(function(b){this._map._layers[a]._latlngs&&this._map._layers[b]._latlngs&&this._map._layers[a]._latlngs.forEach(function(c,d){c._layer=a,c._index=d,this._map._layers[b]._latlngs.forEach(function(d){c.lat===d.lat&&c.lng===d.lng&&a!==b&&(c._hasTwin=!0,c._twinLayers||(c._twinLayers=[]),c._twinLayers.indexOf(b)<0&&c._twinLayers.push(b),d._hasTwin=!0,d._twinLayers||(d._twinLayers=[]),d._twinLayers.indexOf(a)<0&&d._twinLayers.push(a),this._map._layers[a].adjacencies.indexOf(b.toString())<0&&this._map._layers[a].adjacencies.push(b.toString()),this._map._layers[b].adjacencies.indexOf(a.toString())<0&&this._map._layers[b].adjacencies.push(a.toString()))}.bind(this))}.bind(this))}.bind(this))}.bind(this)),a&&a()},match:function(a){for(var b=0;b0?n.fn.concat.apply([],t):t}function F(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function q(t){return t in f?f[t]:f[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function H(t,e){return"number"!=typeof e||c[F(t)]?e:e+"px"}function I(t){var e,n;return u[t]||(e=a.createElement(t),a.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),u[t]=n),u[t]}function V(t){return"children"in t?o.call(t.children):n.map(t.childNodes,function(t){return 1==t.nodeType?t:void 0})}function U(n,i,r){for(e in i)r&&(R(i[e])||A(i[e]))?(R(i[e])&&!R(n[e])&&(n[e]={}),A(i[e])&&!A(n[e])&&(n[e]=[]),U(n[e],i[e],r)):i[e]!==t&&(n[e]=i[e])}function B(t,e){return null==e?n(t):n(t).filter(e)}function J(t,e,n,i){return Z(e)?e.call(t,n,i):e}function X(t,e,n){null==n?t.removeAttribute(e):t.setAttribute(e,n)}function W(e,n){var i=e.className,r=i&&i.baseVal!==t;return n===t?r?i.baseVal:i:void(r?i.baseVal=n:e.className=n)}function Y(t){var e;try{return t?"true"==t||("false"==t?!1:"null"==t?null:/^0/.test(t)||isNaN(e=Number(t))?/^[\[\{]/.test(t)?n.parseJSON(t):t:e):t}catch(i){return t}}function G(t,e){e(t);for(var n in t.childNodes)G(t.childNodes[n],e)}var t,e,n,i,C,N,r=[],o=r.slice,s=r.filter,a=window.document,u={},f={},c={"column-count":1,columns:1,"font-weight":1,"line-height":1,opacity:1,"z-index":1,zoom:1},l=/^\s*<(\w+|!)[^>]*>/,h=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,p=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,d=/^(?:body|html)$/i,m=/([A-Z])/g,g=["val","css","html","text","data","width","height","offset"],v=["after","prepend","before","append"],y=a.createElement("table"),x=a.createElement("tr"),b={tr:a.createElement("tbody"),tbody:y,thead:y,tfoot:y,td:x,th:x,"*":a.createElement("div")},w=/complete|loaded|interactive/,E=/^[\w-]*$/,j={},T=j.toString,S={},O=a.createElement("div"),P={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},A=Array.isArray||function(t){return t instanceof Array};return S.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var i,r=t.parentNode,o=!r;return o&&(r=O).appendChild(t),i=~S.qsa(r,e).indexOf(t),o&&O.removeChild(t),i},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return s.call(t,function(e,n){return t.indexOf(e)==n})},S.fragment=function(e,i,r){var s,u,f;return h.test(e)&&(s=n(a.createElement(RegExp.$1))),s||(e.replace&&(e=e.replace(p,"<$1>")),i===t&&(i=l.test(e)&&RegExp.$1),i in b||(i="*"),f=b[i],f.innerHTML=""+e,s=n.each(o.call(f.childNodes),function(){f.removeChild(this)})),R(r)&&(u=n(s),n.each(r,function(t,e){g.indexOf(t)>-1?u[t](e):u.attr(t,e)})),s},S.Z=function(t,e){return t=t||[],t.__proto__=n.fn,t.selector=e||"",t},S.isZ=function(t){return t instanceof S.Z},S.init=function(e,i){var r;if(!e)return S.Z();if("string"==typeof e)if(e=e.trim(),"<"==e[0]&&l.test(e))r=S.fragment(e,RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=S.qsa(a,e)}else{if(Z(e))return n(a).ready(e);if(S.isZ(e))return e;if(A(e))r=k(e);else if(D(e))r=[e],e=null;else if(l.test(e))r=S.fragment(e.trim(),RegExp.$1,i),e=null;else{if(i!==t)return n(i).find(e);r=S.qsa(a,e)}}return S.Z(r,e)},n=function(t,e){return S.init(t,e)},n.extend=function(t){var e,n=o.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){U(t,n,e)}),t},S.qsa=function(t,e){var n,i="#"==e[0],r=!i&&"."==e[0],s=i||r?e.slice(1):e,a=E.test(s);return _(t)&&a&&i?(n=t.getElementById(s))?[n]:[]:1!==t.nodeType&&9!==t.nodeType?[]:o.call(a&&!i?r?t.getElementsByClassName(s):t.getElementsByTagName(e):t.querySelectorAll(e))},n.contains=function(t,e){return t!==e&&t.contains(e)},n.type=L,n.isFunction=Z,n.isWindow=$,n.isArray=A,n.isPlainObject=R,n.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},n.inArray=function(t,e,n){return r.indexOf.call(e,t,n)},n.camelCase=C,n.trim=function(t){return null==t?"":String.prototype.trim.call(t)},n.uuid=0,n.support={},n.expr={},n.map=function(t,e){var n,r,o,i=[];if(M(t))for(r=0;r=0?e:e+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return r.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return Z(t)?this.not(this.not(t)):n(s.call(this,function(e){return S.matches(e,t)}))},add:function(t,e){return n(N(this.concat(n(t,e))))},is:function(t){return this.length>0&&S.matches(this[0],t)},not:function(e){var i=[];if(Z(e)&&e.call!==t)this.each(function(t){e.call(this,t)||i.push(this)});else{var r="string"==typeof e?this.filter(e):M(e)&&Z(e.item)?o.call(e):n(e);this.forEach(function(t){r.indexOf(t)<0&&i.push(t)})}return n(i)},has:function(t){return this.filter(function(){return D(t)?n.contains(this,t):n(this).find(t).size()})},eq:function(t){return-1===t?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!D(t)?t:n(t)},last:function(){var t=this[this.length-1];return t&&!D(t)?t:n(t)},find:function(t){var e,i=this;return e="object"==typeof t?n(t).filter(function(){var t=this;return r.some.call(i,function(e){return n.contains(e,t)})}):1==this.length?n(S.qsa(this[0],t)):this.map(function(){return S.qsa(this,t)})},closest:function(t,e){var i=this[0],r=!1;for("object"==typeof t&&(r=n(t));i&&!(r?r.indexOf(i)>=0:S.matches(i,t));)i=i!==e&&!_(i)&&i.parentNode;return n(i)},parents:function(t){for(var e=[],i=this;i.length>0;)i=n.map(i,function(t){return(t=t.parentNode)&&!_(t)&&e.indexOf(t)<0?(e.push(t),t):void 0});return B(e,t)},parent:function(t){return B(N(this.pluck("parentNode")),t)},children:function(t){return B(this.map(function(){return V(this)}),t)},contents:function(){return this.map(function(){return o.call(this.childNodes)})},siblings:function(t){return B(this.map(function(t,e){return s.call(V(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return n.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=I(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var e=Z(t);if(this[0]&&!e)var i=n(t).get(0),r=i.parentNode||this.length>1;return this.each(function(o){n(this).wrapAll(e?t.call(this,o):r?i.cloneNode(!0):i)})},wrapAll:function(t){if(this[0]){n(this[0]).before(t=n(t));for(var e;(e=t.children()).length;)t=e.first();n(t).append(this)}return this},wrapInner:function(t){var e=Z(t);return this.each(function(i){var r=n(this),o=r.contents(),s=e?t.call(this,i):t;o.length?o.wrapAll(s):r.append(s)})},unwrap:function(){return this.parent().each(function(){n(this).replaceWith(n(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(e){return this.each(function(){var i=n(this);(e===t?"none"==i.css("display"):e)?i.show():i.hide()})},prev:function(t){return n(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return n(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0===arguments.length?this.length>0?this[0].innerHTML:null:this.each(function(e){var i=this.innerHTML;n(this).empty().append(J(this,t,e,i))})},text:function(e){return 0===arguments.length?this.length>0?this[0].textContent:null:this.each(function(){this.textContent=e===t?"":""+e})},attr:function(n,i){var r;return"string"==typeof n&&i===t?0==this.length||1!==this[0].nodeType?t:"value"==n&&"INPUT"==this[0].nodeName?this.val():!(r=this[0].getAttribute(n))&&n in this[0]?this[0][n]:r:this.each(function(t){if(1===this.nodeType)if(D(n))for(e in n)X(this,e,n[e]);else X(this,n,J(this,i,t,this.getAttribute(n)))})},removeAttr:function(t){return this.each(function(){1===this.nodeType&&X(this,t)})},prop:function(e,n){return e=P[e]||e,n===t?this[0]&&this[0][e]:this.each(function(t){this[e]=J(this,n,t,this[e])})},data:function(e,n){var i=this.attr("data-"+e.replace(m,"-$1").toLowerCase(),n);return null!==i?Y(i):t},val:function(t){return 0===arguments.length?this[0]&&(this[0].multiple?n(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value):this.each(function(e){this.value=J(this,t,e,this.value)})},offset:function(t){if(t)return this.each(function(e){var i=n(this),r=J(this,t,e,i.offset()),o=i.offsetParent().offset(),s={top:r.top-o.top,left:r.left-o.left};"static"==i.css("position")&&(s.position="relative"),i.css(s)});if(0==this.length)return null;var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(t,i){if(arguments.length<2){var r=this[0],o=getComputedStyle(r,"");if(!r)return;if("string"==typeof t)return r.style[C(t)]||o.getPropertyValue(t);if(A(t)){var s={};return n.each(A(t)?t:[t],function(t,e){s[e]=r.style[C(e)]||o.getPropertyValue(e)}),s}}var a="";if("string"==L(t))i||0===i?a=F(t)+":"+H(t,i):this.each(function(){this.style.removeProperty(F(t))});else for(e in t)t[e]||0===t[e]?a+=F(e)+":"+H(e,t[e])+";":this.each(function(){this.style.removeProperty(F(e))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(n(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return t?r.some.call(this,function(t){return this.test(W(t))},q(t)):!1},addClass:function(t){return t?this.each(function(e){i=[];var r=W(this),o=J(this,t,e,r);o.split(/\s+/g).forEach(function(t){n(this).hasClass(t)||i.push(t)},this),i.length&&W(this,r+(r?" ":"")+i.join(" "))}):this},removeClass:function(e){return this.each(function(n){return e===t?W(this,""):(i=W(this),J(this,e,n,i).split(/\s+/g).forEach(function(t){i=i.replace(q(t)," ")}),void W(this,i.trim()))})},toggleClass:function(e,i){return e?this.each(function(r){var o=n(this),s=J(this,e,r,W(this));s.split(/\s+/g).forEach(function(e){(i===t?!o.hasClass(e):i)?o.addClass(e):o.removeClass(e)})}):this},scrollTop:function(e){if(this.length){var n="scrollTop"in this[0];return e===t?n?this[0].scrollTop:this[0].pageYOffset:this.each(n?function(){this.scrollTop=e}:function(){this.scrollTo(this.scrollX,e)})}},scrollLeft:function(e){if(this.length){var n="scrollLeft"in this[0];return e===t?n?this[0].scrollLeft:this[0].pageXOffset:this.each(n?function(){this.scrollLeft=e}:function(){this.scrollTo(e,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),i=this.offset(),r=d.test(e[0].nodeName)?{top:0,left:0}:e.offset();return i.top-=parseFloat(n(t).css("margin-top"))||0,i.left-=parseFloat(n(t).css("margin-left"))||0,r.top+=parseFloat(n(e[0]).css("border-top-width"))||0,r.left+=parseFloat(n(e[0]).css("border-left-width"))||0,{top:i.top-r.top,left:i.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||a.body;t&&!d.test(t.nodeName)&&"static"==n(t).css("position");)t=t.offsetParent;return t})}},n.fn.detach=n.fn.remove,["width","height"].forEach(function(e){var i=e.replace(/./,function(t){return t[0].toUpperCase()});n.fn[e]=function(r){var o,s=this[0];return r===t?$(s)?s["inner"+i]:_(s)?s.documentElement["scroll"+i]:(o=this.offset())&&o[e]:this.each(function(t){s=n(this),s.css(e,J(this,r,t,s[e]()))})}}),v.forEach(function(t,e){var i=e%2;n.fn[t]=function(){var t,o,r=n.map(arguments,function(e){return t=L(e),"object"==t||"array"==t||null==e?e:S.fragment(e)}),s=this.length>1;return r.length<1?this:this.each(function(t,a){o=i?a:a.parentNode,a=0==e?a.nextSibling:1==e?a.firstChild:2==e?a:null,r.forEach(function(t){if(s)t=t.cloneNode(!0);else if(!o)return n(t).remove();G(o.insertBefore(t,a),function(t){null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src||window.eval.call(window,t.innerHTML)})})})},n.fn[i?t+"To":"insert"+(e?"Before":"After")]=function(e){return n(e)[t](this),this}}),S.Z.prototype=n.fn,S.uniq=N,S.deserializeValue=Y,n.zepto=S,n}();window.Zepto=Zepto,void 0===window.$&&(window.$=Zepto),function(t){function l(t){return t._zid||(t._zid=e++)}function h(t,e,n,i){if(e=p(e),e.ns)var r=d(e.ns);return(s[l(t)]||[]).filter(function(t){return!(!t||e.e&&t.e!=e.e||e.ns&&!r.test(t.ns)||n&&l(t.fn)!==l(n)||i&&t.sel!=i)})}function p(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function d(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function m(t,e){return t.del&&!u&&t.e in f||!!e}function g(t){return c[t]||u&&f[t]||t}function v(e,i,r,o,a,u,f){var h=l(e),d=s[h]||(s[h]=[]);i.split(/\s/).forEach(function(i){if("ready"==i)return t(document).ready(r);var s=p(i);s.fn=r,s.sel=a,s.e in c&&(r=function(e){var n=e.relatedTarget;return!n||n!==this&&!t.contains(this,n)?s.fn.apply(this,arguments):void 0}),s.del=u;var l=u||r;s.proxy=function(t){if(t=j(t),!t.isImmediatePropagationStopped()){t.data=o;var i=l.apply(e,t._args==n?[t]:[t].concat(t._args));return i===!1&&(t.preventDefault(),t.stopPropagation()),i}},s.i=d.length,d.push(s),"addEventListener"in e&&e.addEventListener(g(s.e),s.proxy,m(s,f))})}function y(t,e,n,i,r){var o=l(t);(e||"").split(/\s/).forEach(function(e){h(t,e,n,i).forEach(function(e){delete s[o][e.i],"removeEventListener"in t&&t.removeEventListener(g(e.e),e.proxy,m(e,r))})})}function j(e,i){return(i||!e.isDefaultPrevented)&&(i||(i=e),t.each(E,function(t,n){var r=i[t];e[t]=function(){return this[n]=x,r&&r.apply(i,arguments)},e[n]=b}),(i.defaultPrevented!==n?i.defaultPrevented:"returnValue"in i?i.returnValue===!1:i.getPreventDefault&&i.getPreventDefault())&&(e.isDefaultPrevented=x)),e}function T(t){var e,i={originalEvent:t};for(e in t)w.test(e)||t[e]===n||(i[e]=t[e]);return j(i,t)}var n,e=1,i=Array.prototype.slice,r=t.isFunction,o=function(t){return"string"==typeof t},s={},a={},u="onfocusin"in window,f={focus:"focusin",blur:"focusout"},c={mouseenter:"mouseover",mouseleave:"mouseout"};a.click=a.mousedown=a.mouseup=a.mousemove="MouseEvents",t.event={add:v,remove:y},t.proxy=function(e,n){if(r(e)){var i=function(){return e.apply(n,arguments)};return i._zid=l(e),i}if(o(n))return t.proxy(e[n],e);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,i){return this.on(t,e,n,i,1)};var x=function(){return!0},b=function(){return!1},w=/^([A-Z]|returnValue$|layer[XY]$)/,E={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,s,a,u,f){var c,l,h=this;return e&&!o(e)?(t.each(e,function(t,e){h.on(t,s,a,e,f)}),h):(o(s)||r(u)||u===!1||(u=a,a=s,s=n),(r(a)||a===!1)&&(u=a,a=n),u===!1&&(u=b),h.each(function(n,r){f&&(c=function(t){return y(r,t.type,u),u.apply(this,arguments)}),s&&(l=function(e){var n,o=t(e.target).closest(s,r).get(0);return o&&o!==r?(n=t.extend(T(e),{currentTarget:o,liveFired:r}),(c||u).apply(o,[n].concat(i.call(arguments,1)))):void 0}),v(r,e,u,a,s,l||c)}))},t.fn.off=function(e,i,s){var a=this;return e&&!o(e)?(t.each(e,function(t,e){a.off(t,i,e)}),a):(o(i)||r(s)||s===!1||(s=i,i=n),s===!1&&(s=b),a.each(function(){y(this,e,s,i)}))},t.fn.trigger=function(e,n){return e=o(e)||t.isPlainObject(e)?t.Event(e):j(e),e._args=n,this.each(function(){"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,n){var i,r;return this.each(function(s,a){i=T(o(e)?t.Event(e):e),i._args=n,i.target=a,t.each(h(a,e.type||e),function(t,e){return r=e.proxy(i),i.isImmediatePropagationStopped()?!1:void 0})}),r},"focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.trigger(e)}}),["focus","blur"].forEach(function(e){t.fn[e]=function(t){return t?this.bind(e,t):this.each(function(){try{this[e]()}catch(t){}}),this}}),t.Event=function(t,e){o(t)||(e=t,t=e.type);var n=document.createEvent(a[t]||"Events"),i=!0;if(e)for(var r in e)"bubbles"==r?i=!!e[r]:n[r]=e[r];return n.initEvent(t,i,!0),j(n)}}(Zepto),function(t){function l(e,n,i){var r=t.Event(n);return t(e).trigger(r,i),!r.isDefaultPrevented()}function h(t,e,i,r){return t.global?l(e||n,i,r):void 0}function p(e){e.global&&0===t.active++&&h(e,null,"ajaxStart")}function d(e){e.global&&!--t.active&&h(e,null,"ajaxStop")}function m(t,e){var n=e.context;return e.beforeSend.call(n,t,e)===!1||h(e,n,"ajaxBeforeSend",[t,e])===!1?!1:void h(e,n,"ajaxSend",[t,e])}function g(t,e,n,i){var r=n.context,o="success";n.success.call(r,t,o,e),i&&i.resolveWith(r,[t,o,e]),h(n,r,"ajaxSuccess",[e,n,t]),y(o,e,n)}function v(t,e,n,i,r){var o=i.context;i.error.call(o,n,e,t),r&&r.rejectWith(o,[n,e,t]),h(i,o,"ajaxError",[n,i,t||e]),y(e,n,i)}function y(t,e,n){var i=n.context;n.complete.call(i,e,t),h(n,i,"ajaxComplete",[e,n]),d(n)}function x(){}function b(t){return t&&(t=t.split(";",2)[0]),t&&(t==f?"html":t==u?"json":s.test(t)?"script":a.test(t)&&"xml")||"text"}function w(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function E(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()||(e.url=w(e.url,e.data),e.data=void 0)}function j(e,n,i,r){return t.isFunction(n)&&(r=i,i=n,n=void 0),t.isFunction(i)||(r=i,i=void 0),{url:e,data:n,success:i,dataType:r}}function S(e,n,i,r){var o,s=t.isArray(n),a=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),r&&(n=i?r:r+"["+(a||"object"==o||"array"==o?n:"")+"]"),!r&&s?e.add(u.name,u.value):"array"==o||!i&&"object"==o?S(e,u,i,n):e.add(n,u)})}var i,r,e=0,n=window.document,o=/)<[^<]*)*<\/script>/gi,s=/^(?:text|application)\/javascript/i,a=/^(?:text|application)\/xml/i,u="application/json",f="text/html",c=/^\s*$/;t.active=0,t.ajaxJSONP=function(i,r){if(!("type"in i))return t.ajax(i);var f,h,o=i.jsonpCallback,s=(t.isFunction(o)?o():o)||"jsonp"+ ++e,a=n.createElement("script"),u=window[s],c=function(e){t(a).triggerHandler("error",e||"abort")},l={abort:c};return r&&r.promise(l),t(a).on("load error",function(e,n){clearTimeout(h),t(a).off().remove(),"error"!=e.type&&f?g(f[0],l,i,r):v(null,n||"error",l,i,r),window[s]=u,f&&t.isFunction(u)&&u(f[0]),u=f=void 0}),m(l,i)===!1?(c("abort"),l):(window[s]=function(){f=arguments},a.src=i.url.replace(/\?(.+)=\?/,"?$1="+s),n.head.appendChild(a),i.timeout>0&&(h=setTimeout(function(){c("timeout")},i.timeout)),l)},t.ajaxSettings={type:"GET",beforeSend:x,success:x,error:x,complete:x,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:u,xml:"application/xml, text/xml",html:f,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0},t.ajax=function(e){var n=t.extend({},e||{}),o=t.Deferred&&t.Deferred();for(i in t.ajaxSettings)void 0===n[i]&&(n[i]=t.ajaxSettings[i]);p(n),n.crossDomain||(n.crossDomain=/^([\w-]+:)?\/\/([^\/]+)/.test(n.url)&&RegExp.$2!=window.location.host),n.url||(n.url=window.location.toString()),E(n),n.cache===!1&&(n.url=w(n.url,"_="+Date.now()));var s=n.dataType,a=/\?.+=\?/.test(n.url);if("jsonp"==s||a)return a||(n.url=w(n.url,n.jsonp?n.jsonp+"=?":n.jsonp===!1?"":"callback=?")),t.ajaxJSONP(n,o);var j,u=n.accepts[s],f={},l=function(t,e){f[t.toLowerCase()]=[t,e]},h=/^([\w-]+:)\/\//.test(n.url)?RegExp.$1:window.location.protocol,d=n.xhr(),y=d.setRequestHeader;if(o&&o.promise(d),n.crossDomain||l("X-Requested-With","XMLHttpRequest"),l("Accept",u||"*/*"),(u=n.mimeType||u)&&(u.indexOf(",")>-1&&(u=u.split(",",2)[0]),d.overrideMimeType&&d.overrideMimeType(u)),(n.contentType||n.contentType!==!1&&n.data&&"GET"!=n.type.toUpperCase())&&l("Content-Type",n.contentType||"application/x-www-form-urlencoded"),n.headers)for(r in n.headers)l(r,n.headers[r]);if(d.setRequestHeader=l,d.onreadystatechange=function(){if(4==d.readyState){d.onreadystatechange=x,clearTimeout(j);var e,i=!1;if(d.status>=200&&d.status<300||304==d.status||0==d.status&&"file:"==h){s=s||b(n.mimeType||d.getResponseHeader("content-type")),e=d.responseText;try{"script"==s?(1,eval)(e):"xml"==s?e=d.responseXML:"json"==s&&(e=c.test(e)?null:t.parseJSON(e))}catch(r){i=r}i?v(i,"parsererror",d,n,o):g(e,d,n,o)}else v(d.statusText||null,d.status?"error":"abort",d,n,o)}},m(d,n)===!1)return d.abort(),v(null,"abort",d,n,o),d;if(n.xhrFields)for(r in n.xhrFields)d[r]=n.xhrFields[r];var T="async"in n?n.async:!0;d.open(n.type,n.url,T,n.username,n.password);for(r in f)y.apply(d,f[r]);return n.timeout>0&&(j=setTimeout(function(){d.onreadystatechange=x,d.abort(),v(null,"timeout",d,n,o)},n.timeout)),d.send(n.data?n.data:null),d},t.get=function(){return t.ajax(j.apply(null,arguments))},t.post=function(){var e=j.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=j.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,i){if(!this.length)return this;var a,r=this,s=e.split(/\s/),u=j(e,n,i),f=u.success;return s.length>1&&(u.url=s[0],a=s[1]),u.success=function(e){r.html(a?t("
").html(e.replace(o,"")).find(a):e),f&&f.apply(r,arguments)},t.ajax(u),this};var T=encodeURIComponent;t.param=function(t,e){var n=[];return n.add=function(t,e){this.push(T(t)+"="+T(e))},S(n,t,e),n.join("&").replace(/%20/g,"+")}}(Zepto),function(t){t.fn.serializeArray=function(){var n,e=[];return t([].slice.call(this.get(0).elements)).each(function(){n=t(this);var i=n.attr("type");"fieldset"!=this.nodeName.toLowerCase()&&!this.disabled&&"submit"!=i&&"reset"!=i&&"button"!=i&&("radio"!=i&&"checkbox"!=i||this.checked)&&e.push({name:n.attr("name"),value:n.val()})}),e},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(e)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(Zepto),function(t){"__proto__"in{}||t.extend(t.zepto,{Z:function(e,n){return e=e||[],t.extend(e,t.fn),e.selector=n||"",e.__Z=!0,e},isZ:function(e){return"array"===t.type(e)&&"__Z"in e}});try{getComputedStyle(void 0)}catch(e){var n=getComputedStyle;window.getComputedStyle=function(t){try{return n(t)}catch(e){return null}}}}(Zepto); -------------------------------------------------------------------------------- /src/js/leaflet.draw.topology.js: -------------------------------------------------------------------------------- 1 | if (L.Edit.Poly && L.Handler.MarkerSnap) { 2 | 3 | L.Edit.Poly.prototype.addHooks = function () { 4 | if (this._poly._map) { 5 | if (!this._markerGroup) { 6 | this._initMarkers(); 7 | } 8 | this._poly._map.addLayer(this._markerGroup); 9 | this._findTwins(); 10 | } 11 | }; 12 | 13 | L.Edit.Poly.prototype._findTwins = function() { 14 | Object.keys(this._poly._map._layers).forEach(function(d, i) { 15 | Object.keys(this._poly._map._layers).forEach(function(x, y) { 16 | if (this._poly._map._layers[d]._origLatLng && this._poly._map._layers[x]._origLatLng && this._poly._map._layers[d]._origLatLng.lat === this._poly._map._layers[x]._origLatLng.lat && this._poly._map._layers[d]._origLatLng.lng === this._poly._map._layers[x]._origLatLng.lng) { 17 | if (this._poly._map._layers[d]._leaflet_id !== this._poly._map._layers[x]._leaflet_id) { 18 | if (!this._poly._map._layers[d]._twinMarkers) { 19 | this._poly._map._layers[d]._twinMarkers = []; 20 | } 21 | if (this._poly._map._layers[d]._twinMarkers.indexOf(this._poly._map._layers[x]) < 0) { 22 | this._poly._map._layers[d]._twinMarkers.push(this._poly._map._layers[x]); 23 | } 24 | 25 | if (!this._poly._map._layers[x]._twinMarkers) { 26 | this._poly._map._layers[x]._twinMarkers = []; 27 | } 28 | if (this._poly._map._layers[x]._twinMarkers.indexOf(this._poly._map._layers[d]) < 0) { 29 | this._poly._map._layers[x]._twinMarkers.push(this._poly._map._layers[d]); 30 | } 31 | } 32 | } 33 | }.bind(this)); 34 | }.bind(this)); 35 | }; 36 | 37 | L.Edit.Poly.prototype._initMarkers = function() { 38 | if (!this._markerGroup) { 39 | this._markerGroup = new L.LayerGroup(); 40 | } 41 | this._markers = []; 42 | 43 | var latlngs = this._poly._latlngs, 44 | i, j, len, marker; 45 | 46 | for (var i = 0, len = latlngs.length; i < len; i++) { 47 | if (this._primary) { 48 | marker = this._createMarker(latlngs[i], i); 49 | marker.on('click', this._onMarkerClick, this); 50 | this._markers.push(marker); 51 | } else { 52 | if (latlngs[i]._twinLayers) { 53 | for (var j = 0; j < latlngs[i]._twinLayers.length; j++) { 54 | if (this._poly._map._layers[latlngs[i]._twinLayers[j]].editing._primary) { 55 | marker = this._createMarker(latlngs[i], i); 56 | marker.on('click', this._onMarkerClick, this); 57 | this._markers.push(marker); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | var markerLeft, markerRight; 64 | 65 | for (var i = 0, j = len - 1; i < len; j = i++) { 66 | if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) { 67 | continue; 68 | } 69 | 70 | markerLeft = this._markers[j]; 71 | markerRight = this._markers[i]; 72 | 73 | this._updatePrevNext(markerLeft, markerRight); 74 | } 75 | this._updateIndexes(); 76 | }; 77 | 78 | L.Edit.Poly.prototype._createMarker = function(latlng, index) { 79 | var marker = new L.Marker(latlng, { 80 | draggable: true, 81 | icon: this.options.icon 82 | }); 83 | marker._origLatLng = latlng; 84 | marker._index = (index) ? index : latlng._index; 85 | marker._layer = latlng._layer; 86 | 87 | if (latlng._hasTwin) { 88 | marker._hasTwin = true; 89 | marker._twinLayers = latlng._twinLayers 90 | } 91 | 92 | if (latlng._isMiddle) { 93 | marker._isMiddle = true; 94 | } 95 | /*marker.on('mouseover', function(d) { 96 | console.log("layer ", d.target._layer, " -- ", d.target._index); 97 | });*/ 98 | marker.on('drag', this._onMarkerDrag, this); 99 | marker.on('dragend', this._fireEdit, this); 100 | 101 | this._markerGroup.addLayer(marker); 102 | 103 | return marker; 104 | }; 105 | 106 | L.Edit.Poly.prototype._onMarkerClick = function(e) { 107 | /* For now, just disable the ability to remove markers, 108 | but eventually you should be able to remove markers, 109 | including those that have shared vertices */ 110 | }; 111 | 112 | L.Edit.Poly.prototype._onMarkerDrag = function(e) { 113 | var marker = e.target; 114 | var origLatLng = marker._origLatLng; 115 | var toRedraw = []; 116 | 117 | L.extend(marker._origLatLng, marker._latlng); 118 | 119 | if (marker._middleLeft) { 120 | marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker)); 121 | } 122 | if (marker._middleRight) { 123 | marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next)); 124 | } 125 | 126 | toRedraw.push(marker._layer); 127 | 128 | if (marker._hasTwin) { 129 | marker._twinMarkers.forEach(function(d) { 130 | d.setLatLng(marker._latlng); 131 | d.update(); 132 | 133 | this._poly._map._layers[d._layer]._latlngs.forEach(function(q) { 134 | if (q.lat === marker._origLatLng.lat && q.lng === marker._origLatLng.lng) { 135 | q.lat = marker._latlng.lat; 136 | q.lng = marker._latlng.lng; 137 | } 138 | }); 139 | 140 | L.extend(d._origLatLng, marker._latlng); 141 | 142 | toRedraw.push(d._layer); 143 | 144 | }.bind(this)); 145 | } 146 | 147 | toRedraw.forEach(function(d) { 148 | this._poly._map._layers[d].editing._poly.redraw(); 149 | }.bind(this)); 150 | }; 151 | 152 | // Update Leaflet.snap to fire a snap/unsnap on the map 153 | L.Handler.MarkerSnap.prototype._updateSnap = function(marker, layer, latlng) { 154 | // Layer is guide layer being snapped to 155 | if (layer && latlng) { 156 | marker._latlng = L.latLng(latlng); 157 | marker.update(); 158 | if (marker.snap != layer) { 159 | // marker.snap is guide layer being snapped to 160 | marker.snap = layer; 161 | if (marker._icon) L.DomUtil.addClass(marker._icon, 'marker-snapped'); 162 | this._map.fire('snap', {"marker_id": marker._leaflet_id, "layer_id": layer._leaflet_id, "marker": marker}); 163 | marker.fire('snap', {layer:layer, latlng: latlng}); 164 | } 165 | } 166 | else { 167 | if (marker.snap) { 168 | if (marker._icon) L.DomUtil.removeClass(marker._icon, 'marker-snapped'); 169 | this._map.fire('unsnap', {"marker_id": marker._leaflet_id, "marker": marker}); 170 | marker.fire('unsnap', {layer:marker.snap}); 171 | } 172 | delete marker['snap']; 173 | } 174 | }; 175 | 176 | L.Edit.Topology = { 177 | init: function(map, layer, options, restricted, callback) { 178 | this._map = map; 179 | this._map._editStatus = { 180 | "layer": null, 181 | "editing": false 182 | }; 183 | 184 | this._layers = layer; 185 | 186 | var icon = (options && options.icon) ? options.icon : 187 | new L.DivIcon({ 188 | iconSize: new L.Point(10, 10), 189 | className: "leaflet-div-icon leaflet-editing-icon" 190 | }); 191 | icon.options.className = icon.options.className + " propagateDrag"; 192 | 193 | this.marker = new L.Marker(this._map.getCenter(), { 194 | icon: icon, 195 | repeatMode: false, 196 | zIndexOffset: 2000 197 | }).addTo(this._map); 198 | 199 | this._map.on('mousemove', function(e) { 200 | this.marker.setLatLng(e.latlng); 201 | }.bind(this)); 202 | 203 | this.marker.snapediting = new L.Handler.MarkerSnap(this._map, this.marker); 204 | this.marker.snapediting.addGuideLayer(layer); 205 | this.marker.snapediting.enable(); 206 | 207 | this._map.removeLayer(this.marker); 208 | this.marker.snapediting.disable(); 209 | 210 | this._map.on('snap', function(e) { 211 | L.DomUtil.removeClass(e.marker._icon, 'hidden'); 212 | this._map._snapLayer = e.layer_id; 213 | }.bind(this)); 214 | 215 | this._map.on('unsnap', function(e) { 216 | L.DomUtil.addClass(e.marker._icon, 'hidden'); 217 | this._map._snapLayer = ""; 218 | }.bind(this)); 219 | 220 | Object.keys(layer._layers).forEach(function(j) { 221 | /*layer._layers[j].on("contextmenu", function(d) { 222 | console.log(d.target._latlngs); 223 | });*/ 224 | layer._layers[j].on("click", function(d) { 225 | // When a polygon is clicked, toggle editing for it and all adjacent polygons 226 | // If editing is already toggled, disable editing 227 | if (restricted) { 228 | if (d.target.editing._enabled) { 229 | if (this._map._snapLayer) { 230 | var newMarker = new L.Marker(this.marker.getLatLng(), { 231 | icon: L.Edit.Poly.prototype.options.icon, 232 | draggable: true 233 | }); 234 | this._map.fire('marker-created', this.marker); 235 | } 236 | } 237 | } else { 238 | if (d.target.editing._enabled) { 239 | // If editing is enabled and the guide is snapped to this layer, add a new vertex 240 | if (this._map._snapLayer) { 241 | var newMarker = new L.Marker(this.marker.getLatLng(), { 242 | icon: L.Edit.Poly.prototype.options.icon, 243 | draggable: true 244 | }); 245 | this._map.fire('marker-created', this.marker); 246 | // If the guide is not snapped to this layer... 247 | } else { 248 | // ...and the layer being edited was clicked 249 | if (d.target.editing._primary) { 250 | this._map.removeLayer(this.marker); 251 | this.marker.snapediting.disable(); 252 | this._findAdjacencies("disable", d.target); 253 | // 254 | } else { 255 | this.disableEditing(this._map._editStatus.layer._leaflet_id, this._map._editStatus.layer.adjacencies); 256 | this._findAdjacencies("enable", d.target); 257 | } 258 | } 259 | // If editing is not enabled on this layer... 260 | } else { 261 | if (this._map._editStatus.editing) { 262 | this.disableEditing(this._map._editStatus.layer._leaflet_id, this._map._editStatus.layer.adjacencies); 263 | this._findAdjacencies("enable", d.target); 264 | } else { 265 | this._map.addLayer(this.marker); 266 | this.marker.snapediting.enable(); 267 | this._findAdjacencies("enable", d.target); 268 | } 269 | 270 | } 271 | } 272 | 273 | }.bind(this)); 274 | }.bind(this)); 275 | 276 | layer.addTo(this._map); 277 | 278 | this._map.on('marker-created', function(vertex) { 279 | // Make sure marker is snapped to a polygon 280 | if (this._map._snapLayer) { 281 | var poly = this._map._snapLayer, 282 | toRedraw = []; 283 | //console.log("snapped to ", poly); 284 | var markerToInsert = { 285 | "lat": vertex._latlng.lat, 286 | "lng": vertex._latlng.lng 287 | }; 288 | 289 | var polyCoordinates = this._map._layers[poly].editing._poly._latlngs; 290 | // Check if the added vertex is between each pair of latlngs in the polygon 291 | for (var i = 0; i < polyCoordinates.length; i++) { 292 | // Make sure the current index + 1 isn't greater than the length of the latlng array 293 | if (i + 1 <= polyCoordinates.length - 1) { 294 | if (this._isBetween(polyCoordinates[i], polyCoordinates[i + 1], vertex._latlng)) { 295 | var vertex1 = i, 296 | vertex2 = i + 1; 297 | break; 298 | } 299 | // Otherwise, check between last vertex and first vertex 300 | } else { 301 | if (this._isBetween(polyCoordinates[i], polyCoordinates[0], vertex._latlng)) { 302 | var vertex1 = i, 303 | vertex2 = 0; 304 | break; 305 | } 306 | } 307 | } 308 | 309 | /* If both vertices either side of the new vertex have a twin or twins, 310 | we need to insert a vertex on that layer */ 311 | if (polyCoordinates[vertex1]._hasTwin && polyCoordinates[vertex2]._hasTwin) { 312 | var otherMarker = (function() { 313 | // Check if each has more than one twin layer 314 | if (polyCoordinates[vertex1]._twinLayers.length > 1 && polyCoordinates[vertex2]._twinLayers.length > 1) { 315 | // ...find the intersection of their shared layers... 316 | //console.log("1 twin layers - ", polyCoordinates[vertex1]._twinLayers); 317 | //console.log("2 twin layers - ", polyCoordinates[vertex2]._twinLayers); 318 | var intersection = polyCoordinates[vertex1]._twinLayers.filter(function(n) { 319 | return polyCoordinates[vertex2]._twinLayers.indexOf(n) != -1 320 | }.bind(this)); 321 | // If there are multiple intersection layers, remove layer snapped to 322 | if (intersection.length > 1) { 323 | intersection.splice(intersection.indexOf(poly.toString()), 1); 324 | var touchingPolys = intersection; 325 | } else { 326 | var touchingPolys = intersection; 327 | } 328 | 329 | // If there are no touching polys... 330 | if (touchingPolys.length === 0) { 331 | // find union 332 | var union = polyCoordinates[vertex1]._twinLayers.filter(function(n) { 333 | return polyCoordinates[vertex2]._twinLayers.indexOf(n) > -1 334 | }.bind(this)); 335 | if (union.length === intersection.length) { 336 | union.splice(union.indexOf(poly.toString()), 1); 337 | var otherPoly = union[0]; 338 | } else { 339 | // There is only one polygon at this point 340 | return; 341 | } 342 | } else { 343 | var otherPoly = touchingPolys[0]; 344 | } 345 | 346 | } else if (polyCoordinates[vertex1]._twinLayers.length > 1 && polyCoordinates[vertex2]._twinLayers.length === 1) { 347 | var otherPoly = polyCoordinates[vertex2]._twinLayers[0]; 348 | } else if (polyCoordinates[vertex1]._twinLayers.length === 1 && polyCoordinates[vertex2]._twinLayers.length > 1) { 349 | var otherPoly = polyCoordinates[vertex1]._twinLayers[0]; 350 | } else if (polyCoordinates[vertex1]._twinLayers.length === 1 && polyCoordinates[vertex2]._twinLayers.length === 1) { 351 | return; 352 | } 353 | 354 | var otherPolyCoordinates = this._map._layers[otherPoly].editing._poly._latlngs; 355 | 356 | // Check if the added vertex is between each pair of latlngs in the other polygon 357 | for (var i = 0; i < otherPolyCoordinates.length; i++) { 358 | // Make sure the current index + 1 isn't greater than the length of the latlng array 359 | if (i + 1 <= otherPolyCoordinates.length - 1) { 360 | if (this._isBetween(otherPolyCoordinates[i], otherPolyCoordinates[i + 1], vertex._latlng)) { 361 | var otherVertex1 = i, 362 | otherVertex2 = i + 1; 363 | break; 364 | } 365 | // Otherwise, check between last vertex and first vertex 366 | } else { 367 | if (this._isBetween(otherPolyCoordinates[i], otherPolyCoordinates[0], vertex._latlng)) { 368 | var otherVertex1 = i, 369 | otherVertex2 = 0; 370 | break; 371 | } 372 | } 373 | } 374 | 375 | var otherMarkerToInsert = { 376 | "lat": vertex._latlng.lat, 377 | "lng": vertex._latlng.lng, 378 | "_hasTwin": true, 379 | "_twin": { 380 | "layer": poly 381 | }, 382 | "_twinLayers": [poly.toString(), otherPoly] 383 | }; 384 | 385 | return { 386 | "otherPoly": otherPoly, 387 | "otherPolyCoordinates": otherPolyCoordinates, 388 | "otherMarkerToInsert": otherMarkerToInsert, 389 | "otherVertex2": otherVertex2 390 | }; 391 | 392 | }).bind(this)(); 393 | } 394 | 395 | if (otherMarker) { 396 | if (!this._map._layers[otherMarker.otherPoly].editing._primary && !this._map._layers[poly].editing._primary) { 397 | return; 398 | } 399 | markerToInsert._hasTwin = true; 400 | markerToInsert._twinLayers = [poly.toString(), otherMarker.otherPoly]; 401 | 402 | otherMarker.otherPolyCoordinates.splice(otherMarker.otherVertex2, 0, otherMarker.otherMarkerToInsert); 403 | toRedraw.push(otherMarker.otherPoly); 404 | 405 | // Splice the new latlng into the polygon the new marker snapped to 406 | polyCoordinates.splice(vertex2, 0, markerToInsert); 407 | 408 | // Add the snapped polygon to the redraw queue 409 | toRedraw.push(poly); 410 | 411 | // Find all overlapping vertexes 412 | this.match(toRedraw); 413 | 414 | // Redraw all affected polygons 415 | this.redrawPolys(toRedraw); 416 | } else if (!map._layers[poly].editing._primary) { 417 | return; 418 | } else { 419 | // No other layer to add a vertex to 420 | // Splice the new latlng into the polygon the new marker snapped to 421 | polyCoordinates.splice(vertex2, 0, markerToInsert); 422 | 423 | // Add the snapped polygon to the redraw queue 424 | toRedraw.push(poly); 425 | 426 | // Find all overlapping vertexes 427 | this.match(toRedraw); 428 | 429 | // Redraw all affected polygons 430 | this.redrawPolys(toRedraw); 431 | } 432 | 433 | } // end if (this._map.snapLayer) 434 | 435 | }.bind(this)); 436 | 437 | setTimeout(function() { 438 | Object.keys(this._map._layers).forEach(function(d, i) { 439 | if (this._map._layers[d]._latlngs) { 440 | this._map._layers[d].adjacencies = []; 441 | } 442 | }.bind(this)); 443 | this.missingVertices(callback); 444 | }.bind(this), 200); 445 | }, 446 | 447 | _findAdjacencies: function(type, target) { 448 | var layers = []; 449 | target.adjacencies.forEach(function(e) { 450 | if (e !== target._leaflet_id && layers.indexOf(e.toString()) < 0) { 451 | layers.push(e.toString()); 452 | } 453 | }.bind(this)); 454 | 455 | // Enable or disable 456 | if (type === "enable") { 457 | this.enableEditing(target._leaflet_id, layers); 458 | } else { 459 | this.disableEditing(target._leaflet_id, layers); 460 | } 461 | }, 462 | 463 | enableEditing: function(target, layers) { 464 | this._map._editStatus = { 465 | "layer": this._map._layers[target], 466 | "editing": true 467 | }; 468 | 469 | this._map._layers[target].editing._enabled = true; 470 | this._map._layers[target].editing._primary = true; 471 | this._map._layers[target].editing.addHooks(); 472 | this._map._layers[target].setStyle({ 473 | fillColor: '#eee', 474 | color: '#666', 475 | opacity: 0.5 476 | }); 477 | 478 | layers.forEach(function(d) { 479 | if (d !== target.toString()) { 480 | this._map._layers[d].editing._enabled = true; 481 | this._map._layers[d].editing._neighbor = true; 482 | this._map._layers[d].editing.addHooks(); 483 | this._map._layers[d].setStyle({ 484 | fillColor: '#777', 485 | color: '#666', 486 | opacity: 0.8 487 | }); 488 | } 489 | 490 | }.bind(this)); 491 | }, 492 | 493 | destroy: function() { 494 | delete this.marker; 495 | this._map.off('mousemove'); 496 | 497 | this._map.off('snap'); 498 | 499 | this._map.off('unsnap'); 500 | this._map.off('marker-created'); 501 | }, 502 | 503 | disableEditing: function(target, layers) { 504 | this._map._editStatus = { 505 | "layer": null, 506 | "editing": false 507 | }; 508 | 509 | this._map._layers[target].editing._enabled = false; 510 | this._map._layers[target].editing._primary = false; 511 | this._map._layers[target].editing.removeHooks(); 512 | this._map._layers[target].setStyle({ 513 | fillColor: '#000', 514 | color: '#444', 515 | opacity: 0.8 516 | }); 517 | 518 | layers.forEach(function(d) { 519 | this._map._layers[d].editing._enabled = false; 520 | this._map._layers[d].editing._neighbor = false; 521 | this._map._layers[d].editing.removeHooks(); 522 | }.bind(this)); 523 | 524 | this._layers.setStyle({ 525 | fillColor: '#000', 526 | color: '#444', 527 | opacity: 0.95 528 | }); 529 | }, 530 | 531 | /* Largely borrowed from the excellent Turf.js 532 | https://github.com/Turfjs/turf-distance/blob/master/index.js */ 533 | _distance: function(a, b) { 534 | function toRadians(degree) { 535 | return degree * Math.PI / 180; 536 | } 537 | 538 | //var R = 57.2957795, 539 | var R = 1, 540 | dLat = toRadians(b.lat - a.lat), 541 | dLon = toRadians(b.lng - a.lng); 542 | 543 | var x = 544 | Math.sin(dLat/2) * Math.sin(dLat/2) + 545 | Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(toRadians(a.lat)) * Math.cos(toRadians(b.lat)); 546 | 547 | var c = 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)); 548 | 549 | // Return distance in degrees 550 | return (R * c)/2; 551 | }, 552 | 553 | // Is c beween a and b? 554 | _isBetween: function(a, b, c) { 555 | var epsilon = this._distance(a, b); 556 | 557 | var crossProduct = (c.lat - a.lat) * (b.lng - a.lng) - (c.lng - a.lng) * (b.lat - a.lat); 558 | if (Math.abs(crossProduct) > epsilon) { 559 | return false; 560 | } 561 | 562 | var dotProduct = (c.lng - a.lng) * (b.lng - a.lng) + (c.lat - a.lat) * (b.lat - a.lat); 563 | if (dotProduct < 0) { 564 | return false; 565 | } 566 | 567 | var squaredLengthBA = (b.lng - a.lng) * (b.lng - a.lng) + (b.lat - a.lat) * (b.lat - a.lat); 568 | if (dotProduct > squaredLengthBA) { 569 | return false; 570 | } 571 | 572 | return true; 573 | }, 574 | 575 | redrawPolys: function(polys) { 576 | // polys is an array of _leaflet_ids 577 | polys.forEach(function(d) { 578 | if (this._map._layers[d].editing.enabled) { 579 | this._map._layers[d].editing.removeHooks(); 580 | this._map._layers[d].editing.addHooks(); 581 | } 582 | }.bind(this)); 583 | }, 584 | 585 | reset: function(callback) { 586 | Object.keys(this._map._layers).forEach(function(d, i) { 587 | Object.keys(this._map._layers).forEach(function(x, y) { 588 | if (this._map._layers[d]._latlngs && this._map._layers[x]._latlngs) { 589 | this._map._layers[d]._latlngs.forEach(function(a, b) { 590 | a._layer = d; 591 | a._index = b; 592 | this._map._layers[x]._latlngs.forEach(function(c, e) { 593 | if (a.lat === c.lat && a.lng === c.lng) { 594 | if (d !== x) { 595 | a._hasTwin = true; 596 | if (!a._twinLayers) { 597 | a._twinLayers = []; 598 | } 599 | if (a._twinLayers.indexOf(x) < 0) { 600 | a._twinLayers.push(x); 601 | } 602 | 603 | c._hasTwin = true; 604 | if (!c._twinLayers) { 605 | c._twinLayers = []; 606 | } 607 | if (c._twinLayers.indexOf(d) < 0) { 608 | c._twinLayers.push(d); 609 | } 610 | 611 | if (this._map._layers[d].adjacencies.indexOf(x.toString()) < 0) { 612 | this._map._layers[d].adjacencies.push(x.toString()); 613 | } 614 | if (this._map._layers[x].adjacencies.indexOf(d.toString()) < 0) { 615 | this._map._layers[x].adjacencies.push(d.toString()); 616 | } 617 | } 618 | } 619 | }.bind(this)); 620 | }.bind(this)); 621 | } 622 | }.bind(this)); 623 | }.bind(this)); 624 | if (callback) { 625 | callback(); 626 | } 627 | }, 628 | 629 | match: function(layers) { 630 | for (var i = 0; i < layers.length; i++) { 631 | layers[i] = layers[i].toString(); 632 | } 633 | layers.forEach(function(d, i) { 634 | layers.forEach(function(x, y) { 635 | this._map._layers[d]._latlngs.forEach(function(a, b) { 636 | a._layer = d; 637 | a._index = b; 638 | this._map._layers[x]._latlngs.forEach(function(c, e) { 639 | if (a.lat === c.lat && a.lng === c.lng) { 640 | if (d !== x) { 641 | a._hasTwin = true; 642 | if (!a._twinLayers) { 643 | a._twinLayers = []; 644 | } 645 | if (a._twinLayers.indexOf(x) < 0) { 646 | a._twinLayers.push(x); 647 | } 648 | 649 | c._hasTwin = true; 650 | if (!c._twinLayers) { 651 | c._twinLayers = []; 652 | } 653 | if (c._twinLayers.indexOf(d) < 0) { 654 | c._twinLayers.push(d); 655 | } 656 | 657 | if (this._map._layers[d].adjacencies.indexOf(x.toString()) < 0) { 658 | this._map._layers[d].adjacencies.push(x.toString()); 659 | } 660 | if (this._map._layers[x].adjacencies.indexOf(d.toString()) < 0) { 661 | this._map._layers[x].adjacencies.push(d.toString()); 662 | } 663 | } 664 | } 665 | }.bind(this)); 666 | }.bind(this)); 667 | }.bind(this)); 668 | }.bind(this)); 669 | }, 670 | 671 | // (layerA, layerB, layerA-latlng, layerA-latlng-index, layerB-latlng) 672 | addMissingVertex: function(layerA, layerB, a, b, c) { 673 | if (this._isBetween(a, this._map._layers[layerA]._latlngs[b + 1], c)) { 674 | var A = [a.lat, a.lng], 675 | B = [this._map._layers[layerA]._latlngs[b + 1].lat, this._map._layers[layerA]._latlngs[b + 1].lng], 676 | C = [c.lat, c.lng]; 677 | 678 | var intAC = A.filter(function(n) { 679 | return C.indexOf(n) != -1; 680 | }); 681 | 682 | var intBC = B.filter(function(n) { 683 | return C.indexOf(n) != -1; 684 | }); 685 | 686 | if (intAC.length === 1 && intBC.length === 1) { 687 | var toAdd = { 688 | "lat": c.lat, 689 | "lng": c.lng, 690 | "layer": layerA, 691 | "_twinLayers": [layerA, layerB], 692 | "_hasTwin": true 693 | } 694 | this._map._layers[layerA]._latlngs.splice(b + 1, 0, toAdd); 695 | } 696 | } 697 | }, 698 | 699 | missingVertices: function(callback) { 700 | Object.keys(this._map._layers).forEach(function(d) { 701 | Object.keys(this._map._layers).forEach(function(x) { 702 | /* Make sure the layer is indeed a polygon (_latlngs) and that 703 | we are not comparing a given layer to itself (d != x) */ 704 | if (this._map._layers[d]._latlngs && this._map._layers[x]._latlngs && d != x) { 705 | 706 | this._map._layers[d]._latlngs.forEach(function(a, b) { 707 | this._map._layers[x]._latlngs.forEach(function(c, e) { 708 | 709 | if (b + 1 <= this._map._layers[d]._latlngs.length - 1 && b < this._map._layers[d]._latlngs.length - 1) { 710 | this.addMissingVertex(d, x, a, b, c); 711 | 712 | } else if (b === this._map._layers[d]._latlngs.length - 1) { 713 | this.addMissingVertex(d, x, a, -1, c) 714 | } 715 | }.bind(this)); 716 | }.bind(this)); 717 | 718 | } 719 | }.bind(this)); 720 | }.bind(this)); 721 | this.reset(callback); 722 | }, 723 | 724 | editLayer: function(layer) { 725 | this._map.addLayer(this.marker); 726 | this.marker.snapediting.enable(); 727 | this._findAdjacencies("enable", layer); 728 | } 729 | 730 | }; // End L.Edit.Topology 731 | 732 | } else { 733 | throw "Error: leaflet.draw.topology requires Leaflet.Draw and Leaflet.Snap"; 734 | } 735 | --------------------------------------------------------------------------------