├── LICENSE ├── README.md └── leaflet-fullHash.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 KoGor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | leaflet-fullHash 2 | ================ 3 | 4 | Add dynamic URL hash for Leaflet map (map view and active layers). For those who also lack layers state in [leaflet-hash plugin](https://github.com/mlevans/leaflet-hash). Now you can easily link user to specific map view with certain active layers. 5 | 6 | ### Demo 7 | You can view a demo of leaflet-fullHash here: [kogor.github.io/leaflet-fullHash](http://kogor.github.io/leaflet-fullHash/index.html). 8 | 9 | ### Getting started 10 | 1. Include [leaflet-fullHash.js](https://github.com/KoGor/leaflet-fullHash/blob/master/leaflet-fullHash.js). 11 | 12 | 2. Once you have initialized the map (an instance of [L.Map](http://leafletjs.com/reference.html#map-class)), add the following code: 13 | 14 | ```javascript 15 | // Assuming your map instance is in a variable called map 16 | var allMapLayers = {'base_layer_name': leaflet_layer_object, 17 | 'overlay_name': leaflet_layer_object, 18 | 'another_overlay_name': leaflet_layer_object}; 19 | var hash = new L.Hash(map, allMapLayers); 20 | ``` 21 | Here `leaflet_layer_object` should be instance of any Leaflet layer (based on [ILayer](http://leafletjs.com/reference.html#ilayer)). 22 | 23 | ### License 24 | 25 | MIT License. See [LICENSE](https://github.com/KoGor/leaflet-fullHash/blob/master/LICENSE) for details. 26 | -------------------------------------------------------------------------------- /leaflet-fullHash.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | var HAS_HASHCHANGE = (function() { 3 | var doc_mode = window.documentMode; 4 | return ('onhashchange' in window) && 5 | (doc_mode === undefined || doc_mode > 7); 6 | })(); 7 | 8 | L.Hash = function(map, options) { 9 | this.onHashChange = L.Util.bind(this.onHashChange, this); 10 | 11 | if (map) { 12 | this.init(map, options); 13 | } 14 | }; 15 | 16 | L.Hash.parseHash = function(hash) { 17 | if(hash.indexOf('#') === 0) { 18 | hash = hash.substr(1); 19 | } 20 | var args = hash.split("/"); 21 | if (args.length == 4) { 22 | var zoom = parseInt(args[0], 10), 23 | lat = parseFloat(args[1]), 24 | lon = parseFloat(args[2]), 25 | layers = (args[3]).split("-"); 26 | if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { 27 | return false; 28 | } else { 29 | return { 30 | center: new L.LatLng(lat, lon), 31 | zoom: zoom, 32 | layers: layers 33 | }; 34 | } 35 | } else { 36 | return false; 37 | } 38 | }; 39 | 40 | L.Hash.formatHash = function(map) { 41 | var center = map.getCenter(), 42 | zoom = map.getZoom(), 43 | precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)), 44 | layers = []; 45 | 46 | //console.log(this.options); 47 | var options = this.options; 48 | //Check active layers 49 | for(var key in options) { 50 | if (options.hasOwnProperty(key)) { 51 | if (map.hasLayer(options[key])) { 52 | layers.push(key); 53 | }; 54 | }; 55 | }; 56 | 57 | return "#" + [zoom, 58 | center.lat.toFixed(precision), 59 | center.lng.toFixed(precision), 60 | layers.join("-") 61 | ].join("/"); 62 | }, 63 | 64 | L.Hash.prototype = { 65 | map: null, 66 | lastHash: null, 67 | 68 | parseHash: L.Hash.parseHash, 69 | formatHash: L.Hash.formatHash, 70 | 71 | init: function(map, options) { 72 | this.map = map; 73 | L.Util.setOptions(this, options); 74 | 75 | // reset the hash 76 | this.lastHash = null; 77 | this.onHashChange(); 78 | 79 | if (!this.isListening) { 80 | this.startListening(); 81 | } 82 | }, 83 | 84 | removeFrom: function(map) { 85 | if (this.changeTimeout) { 86 | clearTimeout(this.changeTimeout); 87 | } 88 | 89 | if (this.isListening) { 90 | this.stopListening(); 91 | } 92 | 93 | this.map = null; 94 | }, 95 | 96 | onMapMove: function() { 97 | // bail if we're moving the map (updating from a hash), 98 | // or if the map is not yet loaded 99 | 100 | if (this.movingMap || !this.map._loaded) { 101 | return false; 102 | } 103 | 104 | var hash = this.formatHash(this.map); 105 | if (this.lastHash != hash) { 106 | location.replace(hash); 107 | this.lastHash = hash; 108 | } 109 | }, 110 | 111 | movingMap: false, 112 | update: function() { 113 | var hash = location.hash; 114 | if (hash === this.lastHash) { 115 | return; 116 | } 117 | var parsed = this.parseHash(hash); 118 | if (parsed) { 119 | this.movingMap = true; 120 | 121 | this.map.setView(parsed.center, parsed.zoom); 122 | var layers = parsed.layers, 123 | options = this.options, 124 | that = this; 125 | //Add/remove layers 126 | this.map.eachLayer(function(layer) { 127 | that.map.removeLayer(layer); 128 | }); 129 | 130 | layers.forEach(function(element, index, array) { 131 | //console.log(options[element]); 132 | that.map.addLayer(options[element]); 133 | }); 134 | 135 | this.movingMap = false; 136 | } else { 137 | this.onMapMove(this.map); 138 | } 139 | }, 140 | 141 | // defer hash change updates every 100ms 142 | changeDefer: 100, 143 | changeTimeout: null, 144 | onHashChange: function() { 145 | // throttle calls to update() so that they only happen every 146 | // `changeDefer` ms 147 | if (!this.changeTimeout) { 148 | var that = this; 149 | this.changeTimeout = setTimeout(function() { 150 | that.update(); 151 | that.changeTimeout = null; 152 | }, this.changeDefer); 153 | } 154 | }, 155 | 156 | isListening: false, 157 | hashChangeInterval: null, 158 | startListening: function() { 159 | this.map.on("moveend layeradd layerremove", this.onMapMove, this); 160 | 161 | if (HAS_HASHCHANGE) { 162 | L.DomEvent.addListener(window, "hashchange", this.onHashChange); 163 | } else { 164 | clearInterval(this.hashChangeInterval); 165 | this.hashChangeInterval = setInterval(this.onHashChange, 50); 166 | } 167 | this.isListening = true; 168 | }, 169 | 170 | stopListening: function() { 171 | this.map.off("moveend layeradd layerremove", this.onMapMove, this); 172 | 173 | if (HAS_HASHCHANGE) { 174 | L.DomEvent.removeListener(window, "hashchange", this.onHashChange); 175 | } else { 176 | clearInterval(this.hashChangeInterval); 177 | } 178 | this.isListening = false; 179 | }, 180 | 181 | _keyByValue: function(obj, value) { 182 | for(var key in obj) { 183 | if (obj.hasOwnProperty(key)) { 184 | if (obj[key] === value) { 185 | return key; 186 | } else { return null; }; 187 | }; 188 | }; 189 | } 190 | }; 191 | L.hash = function(map, options) { 192 | return new L.Hash(map, options); 193 | }; 194 | L.Map.prototype.addHash = function() { 195 | this._hash = L.hash(this, this.options); 196 | }; 197 | L.Map.prototype.removeHash = function() { 198 | this._hash.removeFrom(); 199 | }; 200 | })(window); 201 | --------------------------------------------------------------------------------