1 2(function () { 3 'use strict'; 4 5 /** 6 * GoogleMutant by Iván Sánchez Ortega <[email protected]> https://ivan.sanchezortega.es 7 * Source and issue tracking: https://gitlab.com/IvanSanchez/Leaflet.GridLayer.GoogleMutant/ 8 * 9 * Based on techniques from https://github.com/shramov/leaflet-plugins 10 * and https://avinmathew.com/leaflet-and-google-maps/ , but relying on MutationObserver. 11 * 12 * "THE BEER-WARE LICENSE": 13 * <[email protected]> wrote this file. As long as you retain this notice you 14 * can do whatever you want with this stuff. If we meet some day, and you think 15 * this stuff is worth it, you can buy me a beer in return. 16 * 17 * Uses MIT-licensed code from https://github.com/rsms/js-lru/ 18 */ 19 20 // This implementation of LRUMap is a copy of https://github.com/rsms/js-lru/ , 21 // trivially adapted for ES6 exports. 22 23 /* 24 The MIT License 25 26 Copyright (c) 2010-2020 Rasmus Andersson <https://rsms.me/> 27 28 Permission is hereby granted, free of charge, to any person obtaining a copy 29 of this software and associated documentation files (the "Software"), to deal 30 in the Software without restriction, including without limitation the rights 31 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 32 copies of the Software, and to permit persons to whom the Software is 33 furnished to do so, subject to the following conditions: 34 35 The above copyright notice and this permission notice shall be included in 36 all copies or substantial portions of the Software. 37 38 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 44 THE SOFTWARE. 45 */ 46 47 /** 48 * A doubly linked list-based Least Recently Used (LRU) cache. Will keep most 49 * recently used items while discarding least recently used items when its limit 50 * is reached. 51 * 52 * Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/> 53 * See README.md for details. 54 * 55 * Illustration of the design: 56 * 57 * entry entry entry entry 58 * ______ ______ ______ ______ 59 * | head |.newer => | |.newer => | |.newer => | tail | 60 * | A | | B | | C | | D | 61 * |______| <= older.|______| <= older.|______| <= older.|______| 62 * 63 * removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added 64 */ 65 66 var NEWER = Symbol("newer"); 67 var OLDER = Symbol("older"); 68 69 var LRUMap = function LRUMap(limit, entries) { 70 if (typeof limit !== "number") { 71 // called as (entries) 72 entries = limit; 73 limit = 0; 74 } 75 76 this.size = 0; 77 this.limit = limit; 78 this.oldest = this.newest = undefined; 79 this._keymap = new Map(); 80 81 if (entries) { 82 this.assign(entries); 83 if (limit < 1) { 84 this.limit = this.size; 85 } 86 } 87 }; 88 89 LRUMap.prototype._markEntryAsUsed = function _markEntryAsUsed (entry) { 90 if (entry === this.newest) { 91 // Already the most recenlty used entry, so no need to update the list 92 return; 93 } 94 // HEAD--------------TAIL 95 // <.older .newer> 96 // <--- add direction -- 97 // A B C <D> E 98 if (entry[NEWER]) { 99 if (entry === this.oldest) { 100 this.oldest = entry[NEWER]; 101 } 102 entry[NEWER][OLDER] = entry[OLDER]; // C <-- E. 103 } 104 if (entry[OLDER]) { 105 entry[OLDER][NEWER] = entry[NEWER]; // C. --> E 106 } 107 entry[NEWER] = undefined; // D --x 108 entry[OLDER] = this.newest; // D. --> E 109 if (this.newest) { 110 this.newest[NEWER] = entry; // E. <-- D 111 } 112 this.newest = entry; 113 }; 114 115 LRUMap.prototype.assign = function assign (entries) { 116 var entry, 117 limit = this.limit || Number.MAX_VALUE; 118 this._keymap.clear(); 119 var it = entries[Symbol.iterator](); 120 for (var itv = it.next(); !itv.done; itv = it.next()) { 121 var e = new Entry(itv.value[0], itv.value[1]); 122 this._keymap.set(e.key, e); 123 if (!entry) { 124 this.oldest = e; 125 } else { 126 entry[NEWER] = e; 127 e[OLDER] = entry; 128 } 129 entry = e; 130 if (limit-- == 0) { 131 throw new Error("overflow"); 132 } 133 } 134 this.newest = entry; 135 this.size = this._keymap.size; 136 }; 137 138 LRUMap.prototype.get = function get (key) { 139 // First, find our cache entry 140 var entry = this._keymap.get(key); 141 if (!entry) { return; } // Not cached. Sorry. 142 // As <key> was found in the cache, register it as being requested recently 143 this._markEntryAsUsed(entry); 144 return entry.value; 145 }; 146
147 LRUMap.prototype.set = function set (key, value) { 148 var entry = this._keymap.get(key); 149 150 if (entry) { 151 // update existing 152 entry.value = value; 153 this._markEntryAsUsed(entry); 154 return this; 155 } 156 157 // new entry 158 this._keymap.set(key, (entry = new Entry(key, value))); 159 160 if (this.newest) { 161 // link previous tail to the new tail (entry) 162 this.newest[NEWER] = entry; 163 entry[OLDER] = this.newest; 164 } else { 165 // we're first in -- yay 166 this.oldest = entry; 167 } 168 169 // add new entry to the end of the linked list -- it's now the freshest entry. 170 this.newest = entry; 171 ++this.size; 172 if (this.size > this.limit) { 173 // we hit the limit -- remove the head 174 this.shift(); 175 } 176 177 return this; 178 }; 179 180 LRUMap.prototype.shift = function shift () { 181 // todo: handle special case when limit == 1 182 var entry = this.oldest; 183 if (entry) { 184 if (this.oldest[NEWER]) { 185 // advance the list 186 this.oldest = this.oldest[NEWER]; 187 this.oldest[OLDER] = undefined; 188 } else { 189 // the cache is exhausted 190 this.oldest = undefined; 191 this.newest = undefined; 192 } 193 // Remove last strong reference to <entry> and remove links from the purged 194 // entry being returned: 195 entry[NEWER] = entry[OLDER] = undefined; 196 this._keymap.delete(entry.key); 197 --this.size; 198 return [entry.key, entry.value]; 199 } 200 }; 201 202 // ------------------------------------------------------------------------------------- 203 // Following code (until end of class definition) is optional and can be removed without 204 // breaking the core functionality. 205 206 LRUMap.prototype.find = function find (key) { 207 var e = this._keymap.get(key); 208 return e ? e.value : undefined; 209 }; 210 211 LRUMap.prototype.has = function has (key) { 212 return this._keymap.has(key); 213 }; 214 215 LRUMap.prototype.delete = function delete$1 (key) { 216 var entry = this._keymap.get(key); 217 if (!entry) { return; } 218 this._keymap.delete(entry.key); 219 if (entry[NEWER] && entry[OLDER]) { 220 // relink the older entry with the newer entry 221 entry[OLDER][NEWER] = entry[NEWER]; 222 entry[NEWER][OLDER] = entry[OLDER]; 223 } else if (entry[NEWER]) { 224 // remove the link to us 225 entry[NEWER][OLDER] = undefined; 226 // link the newer entry to head 227 this.oldest = entry[NEWER]; 228 } else if (entry[OLDER]) { 229 // remove the link to us 230 entry[OLDER][NEWER] = undefined; 231 // link the newer entry to head 232 this.newest = entry[OLDER]; 233 } else { 234 // if(entry[OLDER] === undefined && entry.newer === undefined) { 235 this.oldest = this.newest = undefined; 236 } 237 238 this.size--; 239 return entry.value; 240 }; 241 242 LRUMap.prototype.clear = function clear () { 243 // Not clearing links should be safe, as we don't expose live links to user 244 this.oldest = this.newest = undefined; 245 this.size = 0; 246 this._keymap.clear(); 247 }; 248 249 LRUMap.prototype.keys = function keys () { 250 return new KeyIterator(this.oldest); 251 }; 252 253 LRUMap.prototype.values = function values () { 254 return new ValueIterator(this.oldest); 255 }; 256 257 LRUMap.prototype.entries = function entries () { 258 return this; 259 }; 260 261 LRUMap.prototype[Symbol.iterator] = function () { 262 return new EntryIterator(this.oldest); 263 }; 264 265 LRUMap.prototype.forEach = function forEach (fun, thisObj) { 266 if (typeof thisObj !== "object") { 267 thisObj = this; 268 } 269 var entry = this.oldest; 270 while (entry) { 271 fun.call(thisObj, entry.value, entry.key, this); 272 entry = entry[NEWER]; 273 } 274 }; 275 276 /** Returns a JSON (array) representation */ 277 LRUMap.prototype.toJSON = function toJSON () { 278 var s = new Array(this.size), 279 i = 0, 280 entry = this.oldest; 281 while (entry) { 282 s[i++] = { key: entry.key, value: entry.value }; 283 entry = entry[NEWER]; 284 } 285 return s; 286 }; 287 288 /** Returns a String representation */ 289 LRUMap.prototype.toString = function toString () { 290 var s = "", 291 entry = this.oldest; 292 while (entry) { 293 s += String(entry.key) + ":" + entry.value; 294 entry = entry[NEWER]; 295 if (entry) { 296 s += " < "; 297 } 298 } 299 return s; 300 }; 301 302 function Entry(key, value) { 303 this.key = key; 304 this.value = value; 305 this[NEWER] = undefined; 306 this[OLDER] = undefined; 307 } 308 309 function EntryIterator(oldestEntry) { 310 this.entry = oldestEntry; 311 } 312 EntryIterator.prototype[Symbol.iterator] = function () { 313 return this; 314 }; 315 EntryIterator.prototype.next = function () { 316 var ent = this.entry; 317 if (ent) { 318 this.entry = ent[NEWER]; 319 return { done: false, value: [ent.key, ent.value] }; 320 } else { 321 return { done: true, value: undefined }; 322 } 323 }; 324 325 function KeyIterator(oldestEntry) { 326 this.entry = oldestEntry; 327 } 328 KeyIterator.prototype[Symbol.iterator] = function () { 329 return this; 330 }; 331 KeyIterator.prototype.next = function () { 332 var ent = this.entry; 333 if (ent) { 334 this.entry = ent[NEWER]; 335 return { done: false, value: ent.key }; 336 } else { 337 return { done: true, value: undefined }; 338 } 339 }; 340 341 function ValueIterator(oldestEntry) { 342 this.entry = oldestEntry; 343 } 344 ValueIterator.prototype[Symbol.iterator] = function () { 345 return this; 346 }; 347 ValueIterator.prototype.next = function () { 348 var ent = this.entry; 349 if (ent) { 350 this.entry = ent[NEWER]; 351 return { done: false, value: ent.value }; 352 } else { 353 return { done: true, value: undefined }; 354 } 355 }; 356 357 // GoogleMutant by Iván Sánchez Ortega <[email protected]> 358 359 function waitForAPI(callback, context) { 360 var checkCounter = 0, 361 intervalId = null; 362 363 intervalId = setInterval(function () { 364 if (checkCounter >= 20) { 365 clearInterval(intervalId); 366 throw new Error("window.google not found after 10 seconds"); 367 } 368 if (!!window.google && !!window.google.maps && !!window.google.maps.Map) { 369 clearInterval(intervalId); 370 callback.call(context); 371 } 372 ++checkCounter; 373 }, 500); 374 } 375 376 // ðclass GridLayer.GoogleMutant 377 // ðextends GridLayer 378 L.GridLayer.GoogleMutant = L.GridLayer.extend({ 379 options: { 380 maxZoom: 21, // can be 23, but ugly if more than maxNativeZoom 381 // ðoption type: String = 'roadmap' 382 // Google's map type. Valid values are 'roadmap', 'satellite', 'terrain' or 'hybrid'. 383 type: "roadmap", 384 maxNativeZoom: 21, 385 }, 386 387 initialize: function (options) { 388 L.GridLayer.prototype.initialize.call(this, options); 389 390 // Couple data structures indexed by tile key 391 this._tileCallbacks = {}; // Callbacks for promises for tiles that are expected 392 this._lru = new LRUMap(100); // Tile LRU cache 393 394 this._imagesPerTile = this.options.type === "hybrid" ? 2 : 1; 395 396 this._boundOnMutatedImage = this._onMutatedImage.bind(this); 397 }, 398 399 onAdd: function (map) { 400 var this$1 = this; 401 402 L.GridLayer.prototype.onAdd.call(this, map); 403 this._initMutantContainer(); 404 405 // Attribution and logo nodes are not mutated a second time if the 406 // mutant is removed and re-added to the map, hence they are 407 // not cleaned up on layer removal, so they can be added here. 408 if (this._logoContainer) { 409 map._controlCorners.bottomleft.appendChild(this._logoContainer); 410 } 411 if (this._attributionContainer) { 412 map._controlCorners.bottomright.appendChild(this._attributionContainer); 413 } 414 415 waitForAPI(function () { 416 if (!this$1._map) { 417 return; 418 } 419 this$1._initMutant(); 420 421 //handle layer being added to a map for which there are no Google tiles at the given zoom 422 google.maps.event.addListenerOnce(this$1._mutant, "idle", function () { 423 if (!this$1._map) { 424 return; 425 } 426 this$1._checkZoomLevels(); 427 this$1._mutantIsReady = true; 428 }); 429 }); 430 }, 431 432 onRemove: function (map) { 433 L.GridLayer.prototype.onRemove.call(this, map); 434 this._observer.disconnect(); 435 map._container.removeChild(this._mutantContainer); 436 if (this._logoContainer) { 437 L.DomUtil.remove(this._logoContainer); 438 } 439 if (this._attributionContainer) { 440 L.DomUtil.remove(this._attributionContainer); 441 } 442 if (this._mutant) { 443 google.maps.event.clearListeners(this._mutant, "idle"); 444 } 445 }, 446 447 // ðmethod addGoogleLayer(name: String, options?: Object): this 448 // Adds layer with the given name and options to the google Map instance. 449 // `name`: one of the google maps API layers, with it's constructor available in `google.maps` object. 450 // currently following values supported: 'TrafficLayer', 'TransitLayer', 'BicyclingLayer'. 451 // `options`: see https://developers.google.com/maps/documentation/javascript/reference/map 452 addGoogleLayer: function (googleLayerName, options) { 453 var this$1 = this; 454 455 if (!this._subLayers) { this._subLayers = {}; } 456 this.whenReady(function () { 457 var Constructor = google.maps[googleLayerName]; 458 var googleLayer = new Constructor(options); 459 googleLayer.setMap(this$1._mutant); 460 this$1._subLayers[googleLayerName] = googleLayer; 461 }); 462 return this; 463 }, 464 465 // ðmethod removeGoogleLayer(name: String): this 466 // Removes layer with the given name from the google Map instance. 467 removeGoogleLayer: function (googleLayerName) { 468 var this$1 = this; 469 470 this.whenReady(function () { 471 var googleLayer = this$1._subLayers && this$1._subLayers[googleLayerName]; 472 if (googleLayer) { 473 googleLayer.setMap(null); 474 delete this$1._subLayers[googleLayerName]; 475 } 476 }); 477 return this; 478 }, 479 480 _initMutantContainer: function () { 481 if (!this._mutantContainer) { 482 this._mutantContainer = L.DomUtil.create( 483 "div", 484 "leaflet-google-mutant leaflet-top leaflet-left" 485 ); 486 this._mutantContainer.id = "_MutantContainer_" + L.Util.stamp(this._mutantContainer); 487 this._mutantContainer.style.pointerEvents = "none"; 488 this._mutantContainer.style.visibility = "hidden"; 489 490 L.DomEvent.off(this._mutantContainer); 491 } 492 this._map.getContainer().appendChild(this._mutantContainer); 493 494 this.setOpacity(this.options.opacity); 495 var style = this._mutantContainer.style; 496 if (this._map.options.zoomSnap < 1) { 497 // Fractional zoom needs a bigger mutant container in order to load more (smaller) tiles 498 style.width = "180%"; 499 style.height = "180%"; 500 } else { 501 style.width = "100%"; 502 style.height = "100%"; 503 } 504 style.zIndex = -1; 505 506 this._attachObserver(this._mutantContainer); 507 }, 508 509 _initMutant: function () { 510 if (this._mutant) { 511 return; 512 } 513 514 var map = new google.maps.Map(this._mutantContainer, { 515 center: { lat: 0, lng: 0 }, 516 zoom: 0, 517 tilt: 0, 518 mapTypeId: this.options.type, 519 disableDefaultUI: true, 520 keyboardShortcuts: false, 521 draggable: false, 522 disableDoubleClickZoom: true, 523 scrollwheel: false, 524 styles: this.options.styles || [], 525 backgroundColor: "transparent", 526 }); 527 528 this._mutant = map; 529 530 this._update(); 531 532 // ðevent spawned 533 // Fired when the mutant has been created. 534 this.fire("spawned", { mapObject: map }); 535 536 this._waitControls(); 537 this.once('controls_ready', this._setupAttribution); 538 }, 539 540 _attachObserver: function _attachObserver(node) { 541 if (!this._observer) { this._observer = new MutationObserver(this._onMutations.bind(this)); } 542 543 // pass in the target node, as well as the observer options 544 this._observer.observe(node, { childList: true, subtree: true }); 545 546 // if we are reusing an old _mutantContainer, we must manually detect 547 // all existing tiles in it 548 Array.prototype.forEach.call(node.querySelectorAll("img"), this._boundOnMutatedImage); 549 }, 550 551 _waitControls: function () { 552 var this$1 = this; 553 554 var id = setInterval(function () { 555 var layoutManager = this$1._mutant.__gm.layoutManager; 556 if (!layoutManager) { return; } 557 clearInterval(id); 558 var positions; 559 // iterate through obfuscated key names to find positions set (atm: layoutManager.o) 560 Object.keys(layoutManager).forEach(function(key) { 561 var el = layoutManager[key]; 562 if (el.get) { 563 if (el.get(1) instanceof Node) { 564 positions = el; 565 } 566 } 567 }); 568 // ðevent controls_ready 569 // Fired when controls positions get available (passed in `positions` property). 570 this$1.fire("controls_ready", { positions: positions }); 571 }, 50); 572 }, 573 574 _setupAttribution: function (ev) { 575 if (!this._map) { 576 return; 577 } 578 // https://developers.google.com/maps/documentation/javascript/reference/control#ControlPosition 579 var pos = google.maps.ControlPosition; 580 var ctr = this._attributionContainer = ev.positions.get(pos.BOTTOM_RIGHT); 581 L.DomUtil.addClass(ctr, "leaflet-control leaflet-control-attribution"); 582 L.DomEvent.disableClickPropagation(ctr); 583 ctr.style.height = "14px"; 584 this._map._controlCorners.bottomright.appendChild(ctr); 585 586 this._logoContainer = ev.positions.get(pos.BOTTOM_LEFT); 587 this._logoContainer.style.pointerEvents = "auto"; 588 this._map._controlCorners.bottomleft.appendChild(this._logoContainer); 589 }, 590 591 _onMutations: function _onMutations(mutations) { 592 for (var i = 0; i < mutations.length; ++i) { 593 var mutation = mutations[i]; 594 for (var j = 0; j < mutation.addedNodes.length; ++j) { 595 var node = mutation.addedNodes[j]; 596 597 if (node instanceof HTMLImageElement) { 598 this._onMutatedImage(node); 599 } else if (node instanceof HTMLElement) { 600 Array.prototype.forEach.call( 601 node.querySelectorAll("img"), 602 this._boundOnMutatedImage 603 ); 604 } 605 } 606 } 607 }, 608 609 // Only images which 'src' attrib match this will be considered for moving around. 610 // Looks like some kind of string-based protobuf, maybe?? 611 // Only the roads (and terrain, and vector-based stuff) match this pattern 612 _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+|VinaFnapurmBegrtn)!/, 613 614 // On the other hand, raster imagery matches this other pattern 615 _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+|VinaFnapurmBegrtn)/, 616 617 _onMutatedImage: function _onMutatedImage(imgNode) { 618 var coords; 619 var match = imgNode.src.match(this._roadRegexp); 620 var sublayer = 0; 621 622 if (match) { 623 coords = { 624 z: match[1], 625 x: match[2], 626 y: match[3], 627 }; 628 if (this._imagesPerTile > 1) { 629 imgNode.style.zIndex = 1; 630 sublayer = 1; 631 } 632 } else { 633 match = imgNode.src.match(this._satRegexp); 634 if (match) { 635 coords = { 636 x: match[1], 637 y: match[2], 638 z: match[3], 639 }; 640 } 641 // imgNode.style.zIndex = 0; 642 sublayer = 0; 643 } 644 645 if (coords) { 646 var tileKey = this._tileCoordsToKey(coords); 647 imgNode.style.position = "absolute"; 648 649 var key = tileKey + "/" + sublayer; 650 // Cache img so it can also be used in subsequent tile requests 651 this._lru.set(key, imgNode); 652 653 if (key in this._tileCallbacks && this._tileCallbacks[key]) {
654 // Use the tile for *all* pending callbacks. They'll be cloned anyway. 655 this._tileCallbacks[key].forEach(function (callback) { return callback(imgNode); }); 656 delete this._tileCallbacks[key]; 657 } 658 } 659 }, 660 661 createTile: function (coords, done) { 662 var key = this._tileCoordsToKey(coords), 663 tileContainer = L.DomUtil.create("div"); 664 665 tileContainer.style.textAlign = "left"; 666 tileContainer.dataset.pending = this._imagesPerTile; 667 done = done.bind(this, null, tileContainer); 668 669 for (var i = 0; i < this._imagesPerTile; ++i) { 670 var key2 = key + "/" + i, 671 imgNode = this._lru.get(key2); 672 if (imgNode) { 673 tileContainer.appendChild(this._clone(imgNode)); 674 --tileContainer.dataset.pending; 675 } else { 676 this._tileCallbacks[key2] = this._tileCallbacks[key2] || []; 677 this._tileCallbacks[key2].push( 678 function (c /*, k2*/) { 679 return function (imgNode) { 680 c.appendChild(this._clone(imgNode)); 681 --c.dataset.pending; 682 if (!parseInt(c.dataset.pending)) { 683 done(); 684 } 685 }.bind(this); 686 }.bind(this)(tileContainer /*, key2*/) 687 ); 688 } 689 } 690 691 if (!parseInt(tileContainer.dataset.pending)) { 692 L.Util.requestAnimFrame(done); 693 } 694 return tileContainer; 695 }, 696 697 _clone: function (imgNode) { 698 var clonedImgNode = imgNode.cloneNode(true); 699 clonedImgNode.style.visibility = "visible"; 700 return clonedImgNode; 701 }, 702 703 _checkZoomLevels: function () { 704 //setting the zoom level on the Google map may result in a different zoom level than the one requested 705 //(it won't go beyond the level for which they have data). 706 var zoomLevel = this._map.getZoom(), 707 gMapZoomLevel = this._mutant.getZoom(); 708 709 if (!zoomLevel || !gMapZoomLevel) { return; } 710 711 if ( 712 gMapZoomLevel !== zoomLevel || //zoom levels are out of sync, Google doesn't have data 713 gMapZoomLevel > this.options.maxNativeZoom 714 ) { 715 //at current location, Google does have data (contrary to maxNativeZoom) 716 //Update maxNativeZoom 717 this._setMaxNativeZoom(gMapZoomLevel); 718 } 719 }, 720 721 _setMaxNativeZoom: function (zoomLevel) { 722 if (zoomLevel !== this.options.maxNativeZoom) { 723 this.options.maxNativeZoom = zoomLevel; 724 this._resetView(); 725 } 726 }, 727 728 _update: function (center) { 729 // zoom level check needs to happen before super's implementation (tile addition/creation) 730 // otherwise tiles may be missed if maxNativeZoom is not yet correctly determined 731 if (this._mutant) { 732 center = center || this._map.getCenter(); 733 var _center = new google.maps.LatLng(center.lat, center.lng), 734 zoom = Math.round(this._map.getZoom()), 735 mutantZoom = this._mutant.getZoom(); 736 737 this._mutant.setCenter(_center); 738 739 //ignore fractional zoom levels 740 if (zoom !== mutantZoom) { 741 this._mutant.setZoom(zoom); 742 743 if (this._mutantIsReady) { this._checkZoomLevels(); } 744 //else zoom level check will be done later by 'idle' handler 745 } 746 } 747 748 L.GridLayer.prototype._update.call(this, center); 749 }, 750 751 // @method whenReady(fn: Function, context?: Object): this 752 // Runs the given function `fn` when the mutant gets initialized, or immediately 753 // if it's already initialized, optionally passing a function context. 754 whenReady: function (callback, context) { 755 if (this._mutant) { 756 callback.call(context || this, { target: this }); 757 } else { 758 this.on("spawned", callback, context); 759 } 760 return this; 761 }, 762 }); 763 764 // ðfactory gridLayer.googleMutant(options) 765 // Returns a new `GridLayer.GoogleMutant` given its options 766 L.gridLayer.googleMutant = function (options) { 767 return new L.GridLayer.GoogleMutant(options); 768 }; 769 770}()); 771//# sourceMappingURL=Leaflet.GoogleMutant.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.