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