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) { return; } // Not cached. Sorry. 141 // As <key> was found in the cache, register it as being requested recently 142 this._markEntryAsUsed(entry); 143 return entry.value; 144 }; 145
146 LRUMap.prototype.set = function set (key, value) { 147 var entry = this._keymap.get(key); 148 149 if (entry) { 150 // update existing 151 entry.value = value; 152 this._markEntryAsUsed(entry); 153 return this; 154 } 155 156 // new entry 157 this._keymap.set(key, (entry = new Entry(key, value))); 158 159 if (this.newest) { 160 // link previous tail to the new tail (entry) 161 this.newest[NEWER] = entry; 162 entry[OLDER] = this.newest; 163 } else { 164 // we're first in -- yay 165 this.oldest = entry; 166 } 167 168 // add new entry to the end of the linked list -- it's now the freshest entry. 169 this.newest = entry; 170 ++this.size; 171 if (this.size > this.limit) { 172 // we hit the limit -- remove the head 173 this.shift(); 174 } 175 176 return this; 177 }; 178 179 LRUMap.prototype.shift = function shift () { 180 // todo: handle special case when limit == 1 181 var entry = this.oldest; 182 if (entry) { 183 if (this.oldest[NEWER]) { 184 // advance the list 185 this.oldest = this.oldest[NEWER]; 186 this.oldest[OLDER] = undefined; 187 } else { 188 // the cache is exhausted 189 this.oldest = undefined; 190 this.newest = undefined; 191 } 192 // Remove last strong reference to <entry> and remove links from the purged 193 // entry being returned: 194 entry[NEWER] = entry[OLDER] = undefined; 195 this._keymap.delete(entry.key); 196 --this.size; 197 return [entry.key, entry.value]; 198 } 199 }; 200 201 // ------------------------------------------------------------------------------------- 202 // Following code (until end of class definition) is optional and can be removed without 203 // breaking the core functionality. 204 205 LRUMap.prototype.find = function find (key) { 206 var e = this._keymap.get(key); 207 return e ? e.value : undefined; 208 }; 209 210 LRUMap.prototype.has = function has (key) { 211 return this._keymap.has(key); 212 }; 213 214 LRUMap.prototype.delete = function delete$1 (key) { 215 var entry = this._keymap.get(key); 216 if (!entry) { return; } 217 this._keymap.delete(entry.key); 218 if (entry[NEWER] && entry[OLDER]) { 219 // relink the older entry with the newer entry 220 entry[OLDER][NEWER] = entry[NEWER]; 221 entry[NEWER][OLDER] = entry[OLDER]; 222 } else if (entry[NEWER]) { 223 // remove the link to us 224 entry[NEWER][OLDER] = undefined; 225 // link the newer entry to head 226 this.oldest = entry[NEWER]; 227 } else if (entry[OLDER]) { 228 // remove the link to us 229 entry[OLDER][NEWER] = undefined; 230 // link the newer entry to head 231 this.newest = entry[OLDER]; 232 } else { 233 // if(entry[OLDER] === undefined && entry.newer === undefined) { 234 this.oldest = this.newest = undefined; 235 } 236 237 this.size--; 238 return entry.value; 239 }; 240 241 LRUMap.prototype.clear = function clear () { 242 // Not clearing links should be safe, as we don't expose live links to user 243 this.oldest = this.newest = undefined; 244 this.size = 0; 245 this._keymap.clear(); 246 }; 247 248 LRUMap.prototype.keys = function keys () { 249 return new KeyIterator(this.oldest); 250 }; 251 252 LRUMap.prototype.values = function values () { 253 return new ValueIterator(this.oldest); 254 }; 255 256 LRUMap.prototype.entries = function entries () { 257 return this; 258 }; 259 260 LRUMap.prototype[Symbol.iterator] = function () { 261 return new EntryIterator(this.oldest); 262 }; 263 264 LRUMap.prototype.forEach = function forEach (fun, thisObj) { 265 if (typeof thisObj !== "object") { 266 thisObj = this; 267 } 268 var entry = this.oldest; 269 while (entry) { 270 fun.call(thisObj, entry.value, entry.key, this); 271 entry = entry[NEWER]; 272 } 273 }; 274 275 /** Returns a JSON (array) representation */ 276 LRUMap.prototype.toJSON = function toJSON () { 277 var s = new Array(this.size), 278 i = 0, 279 entry = this.oldest; 280 while (entry) { 281 s[i++] = { key: entry.key, value: entry.value }; 282 entry = entry[NEWER]; 283 } 284 return s; 285 }; 286 287 /** Returns a String representation */ 288 LRUMap.prototype.toString = function toString () { 289 var s = "", 290 entry = this.oldest; 291 while (entry) { 292 s += String(entry.key) + ":" + entry.value; 293 entry = entry[NEWER]; 294 if (entry) { 295 s += " < "; 296 } 297 } 298 return s; 299 }; 300 301 function Entry(key, value) { 302 this.key = key; 303 this.value = value; 304 this[NEWER] = undefined; 305 this[OLDER] = undefined; 306 } 307 308 function EntryIterator(oldestEntry) { 309 this.entry = oldestEntry; 310 } 311 EntryIterator.prototype[Symbol.iterator] = function () { 312 return this; 313 }; 314 EntryIterator.prototype.next = function () { 315 var ent = this.entry; 316 if (ent) { 317 this.entry = ent[NEWER]; 318 return { done: false, value: [ent.key, ent.value] }; 319 } else { 320 return { done: true, value: undefined }; 321 } 322 }; 323 324 function KeyIterator(oldestEntry) { 325 this.entry = oldestEntry; 326 } 327 KeyIterator.prototype[Symbol.iterator] = function () { 328 return this; 329 }; 330 KeyIterator.prototype.next = function () { 331 var ent = this.entry; 332 if (ent) { 333 this.entry = ent[NEWER]; 334 return { done: false, value: ent.key }; 335 } else { 336 return { done: true, value: undefined }; 337 } 338 }; 339 340 function ValueIterator(oldestEntry) { 341 this.entry = oldestEntry; 342 } 343 ValueIterator.prototype[Symbol.iterator] = function () { 344 return this; 345 }; 346 ValueIterator.prototype.next = function () { 347 var ent = this.entry; 348 if (ent) { 349 this.entry = ent[NEWER]; 350 return { done: false, value: ent.value }; 351 } else { 352 return { done: true, value: undefined }; 353 } 354 }; 355 356 // GoogleMutant by Iván Sánchez Ortega <[email protected]> 357 358 function waitForAPI(callback, context) { 359 var checkCounter = 0, 360 intervalId = null; 361 362 intervalId = setInterval(function () { 363 if (checkCounter >= 20) { 364 clearInterval(intervalId); 365 throw new Error("window.google not found after 10 seconds"); 366 } 367 if (!!window.google && !!window.google.maps && !!window.google.maps.Map) { 368 clearInterval(intervalId); 369 callback.call(context); 370 } 371 ++checkCounter; 372 }, 500); 373 } 374 375 // ðclass GridLayer.GoogleMutant 376 // ðextends GridLayer 377 L.GridLayer.GoogleMutant = L.GridLayer.extend({ 378 options: {
379 minZoom: 0, 380 maxZoom: 21, // can be 23, but ugly if more than maxNativeZoom 381 tileSize: 256, 382 subdomains: "abc", 383 errorTileUrl: "", 384 attribution: "", // The mutant container will add its own attribution anyways. 385 opacity: 1, 386 continuousWorld: false, 387 noWrap: false, 388 // ðoption type: String = 'roadmap' 389 // Google's map type. Valid values are 'roadmap', 'satellite' or 'terrain'. 'hybrid' is not really supported. 390 type: "roadmap", 391 maxNativeZoom: 21, 392 }, 393 394 initialize: function (options) { 395 L.GridLayer.prototype.initialize.call(this, options); 396 397 // Couple data structures indexed by tile key 398 this._tileCallbacks = {}; // Callbacks for promises for tiles that are expected 399 this._lru = new LRUMap(100); // Tile LRU cache 400 401 this._imagesPerTile = this.options.type === "hybrid" ? 2 : 1; 402 403 this._boundOnMutatedImage = this._onMutatedImage.bind(this); 404 }, 405 406 onAdd: function (map) { 407 var this$1 = this; 408 409 L.GridLayer.prototype.onAdd.call(this, map); 410 this._initMutantContainer(); 411 412 // Attribution and logo nodes are not mutated a second time if the 413 // mutant is removed and re-added to the map, hence they are 414 // not cleaned up on layer removal, so they can be added here. 415 if (this._logoContainer) { 416 map._controlCorners.bottomleft.appendChild(this._logoContainer); 417 } 418 if (this._attributionContainer) { 419 map._controlCorners.bottomright.appendChild(this._attributionContainer); 420 } 421 422 waitForAPI(function () { 423 if (!this$1._map) { 424 return; 425 } 426 this$1._initMutant(); 427 428 //handle layer being added to a map for which there are no Google tiles at the given zoom 429 google.maps.event.addListenerOnce(this$1._mutant, "idle", function () { 430 if (!this$1._map) { 431 return; 432 } 433 this$1._checkZoomLevels(); 434 this$1._mutantIsReady = true; 435 }); 436 }); 437 }, 438 439 onRemove: function (map) { 440 L.GridLayer.prototype.onRemove.call(this, map); 441 this._observer.disconnect(); 442 map._container.removeChild(this._mutantContainer); 443 if (this._logoContainer) { 444 L.DomUtil.remove(this._logoContainer); 445 } 446 if (this._attributionContainer) { 447 L.DomUtil.remove(this._attributionContainer); 448 } 449 if (this._mutant) { 450 google.maps.event.clearListeners(this._mutant, "idle"); 451 } 452 }, 453 454 // ðmethod addGoogleLayer(name: String, options?: Object): this 455 // Adds layer with the given name and options to the google Map instance. 456 // `name`: one of the google maps API layers, with it's constructor available in `google.maps` object. 457 // currently following values supported: 'TrafficLayer', 'TransitLayer', 'BicyclingLayer'. 458 // `options`: see https://developers.google.com/maps/documentation/javascript/reference/map 459 addGoogleLayer: function (googleLayerName, options) { 460 var this$1 = this; 461 462 if (!this._subLayers) { this._subLayers = {}; } 463 this.whenReady(function () { 464 var Constructor = google.maps[googleLayerName]; 465 var googleLayer = new Constructor(options); 466 googleLayer.setMap(this$1._mutant); 467 this$1._subLayers[googleLayerName] = googleLayer; 468 }); 469 return this; 470 }, 471 472 // ðmethod removeGoogleLayer(name: String): this 473 // Removes layer with the given name from the google Map instance. 474 removeGoogleLayer: function (googleLayerName) { 475 var this$1 = this; 476 477 this.whenReady(function () { 478 var googleLayer = this$1._subLayers && this$1._subLayers[googleLayerName]; 479 if (googleLayer) { 480 googleLayer.setMap(null); 481 delete this$1._subLayers[googleLayerName]; 482 } 483 }); 484 return this; 485 }, 486 487 _initMutantContainer: function () { 488 if (!this._mutantContainer) { 489 this._mutantContainer = L.DomUtil.create( 490 "div", 491 "leaflet-google-mutant leaflet-top leaflet-left" 492 ); 493 this._mutantContainer.id = "_MutantContainer_" + L.Util.stamp(this._mutantContainer); 494 this._mutantContainer.style.zIndex = 800; //leaflet map pane at 400, controls at 1000 495 this._mutantContainer.style.pointerEvents = "none"; 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 var this$1 = this; 518 519 if (this._mutant) { 520 return; 521 } 522 523 var map = new google.maps.Map(this._mutantContainer, { 524 center: { lat: 0, lng: 0 }, 525 zoom: 0, 526 tilt: 0, 527 mapTypeId: this.options.type, 528 disableDefaultUI: true, 529 keyboardShortcuts: false, 530 draggable: false, 531 disableDoubleClickZoom: true, 532 scrollwheel: false, 533 streetViewControl: false, 534 styles: this.options.styles || {}, 535 backgroundColor: "transparent", 536 }); 537 538 this._mutant = map; 539 540 google.maps.event.addListenerOnce(map, "idle", function () { 541 var nodes = this$1._mutantContainer.querySelectorAll("a"); 542 for (var i = 0; i < nodes.length; ++i) { 543 nodes[i].style.pointerEvents = "auto"; 544 } 545 }); 546 547 this._update(); 548 549 // ðevent spawned 550 // Fired when the mutant has been created. 551 this.fire("spawned", { mapObject: map }); 552 }, 553 554 _attachObserver: function _attachObserver(node) { 555 if (!this._observer) { this._observer = new MutationObserver(this._onMutations.bind(this)); } 556 557 // pass in the target node, as well as the observer options 558 this._observer.observe(node, { childList: true, subtree: true }); 559 560 // if we are reusing an old _mutantContainer, we must manually detect 561 // all existing tiles in it 562 Array.prototype.forEach.call(node.querySelectorAll("img"), this._boundOnMutatedImage); 563 }, 564 565 _onMutations: function _onMutations(mutations) { 566 for (var i = 0; i < mutations.length; ++i) { 567 var mutation = mutations[i]; 568 for (var j = 0; j < mutation.addedNodes.length; ++j) { 569 var node = mutation.addedNodes[j]; 570 571 if (node instanceof HTMLImageElement) { 572 this._onMutatedImage(node); 573 } else if (node instanceof HTMLElement) { 574 Array.prototype.forEach.call( 575 node.querySelectorAll("img"), 576 this._boundOnMutatedImage 577 ); 578 579 // Check for, and remove, the "Google Maps can't load correctly" div. 580 // You *are* loading correctly, you dumbwit. 581 if (node.style.backgroundColor === "white") { 582 L.DomUtil.remove(node); 583 } 584 585 // Check for, and remove, the "For development purposes only" divs on the aerial/hybrid tiles. 586 if (node.textContent.indexOf("For development purposes only") === 0) { 587 L.DomUtil.remove(node); 588 } 589 590 // Check for, and remove, the "Sorry, we have no imagery here" 591 // empty <div>s. The [style*="text-align: center"] selector 592 // avoids matching the attribution notice. 593 // This empty div doesn't have a reference to the tile 594 // coordinates, so it's not possible to mark the tile as 595 // failed. 596 Array.prototype.forEach.call( 597 node.querySelectorAll('div[draggable=false][style*="text-align: center"]'), 598 L.DomUtil.remove 599 ); 600 601 // Move Google attributions to leaflet's bottom-right control container 602 if ( 603 node.querySelectorAll(".gmnoprint").length > 0 || 604 node.querySelectorAll('a[title="Click to see this area on Google Maps"]') 605 .length > 0 606 ) { 607 var ctr = (this._attributionContainer = L.DomUtil.create( 608 "div", 609 "leaflet-control leaflet-control-attribution" 610 )); 611 L.DomEvent.disableClickPropagation(ctr); 612 ctr.style.height = "14px"; 613 ctr.style.background = "none"; 614 this._map._controlCorners.bottomright.appendChild(ctr); 615 ctr.appendChild(node); 616 } 617 618 // Move Google logo to leaflet's bottom-left control container 619 if (node.style.zIndex == 1000000) { 620 this._map._controlCorners.bottomleft.appendChild(node); 621 this._logoContainer = node; 622 } 623 } 624 } 625 } 626 }, 627 628 // Only images which 'src' attrib match this will be considered for moving around. 629 // Looks like some kind of string-based protobuf, maybe?? 630 // Only the roads (and terrain, and vector-based stuff) match this pattern 631 _roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/, 632 633 // On the other hand, raster imagery matches this other pattern 634 _satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/, 635 636 // On small viewports, when zooming in/out, a static image is requested 637 // This will not be moved around, just removed from the DOM. 638 _staticRegExp: /StaticMapService\.GetMapImage/, 639 640 _onMutatedImage: function _onMutatedImage(imgNode) { 641 var coords; 642 var match = imgNode.src.match(this._roadRegexp); 643 var sublayer = 0; 644 645 if (match) { 646 coords = { 647 z: match[1], 648 x: match[2], 649 y: match[3], 650 }; 651 if (this._imagesPerTile > 1) { 652 imgNode.style.zIndex = 1; 653 sublayer = 1; 654 } 655 } else { 656 match = imgNode.src.match(this._satRegexp); 657 if (match) { 658 coords = { 659 x: match[1], 660 y: match[2], 661 z: match[3], 662 }; 663 } 664 // imgNode.style.zIndex = 0; 665 sublayer = 0; 666 } 667 668 if (coords) { 669 var tileKey = this._tileCoordsToKey(coords); 670 imgNode.style.position = "absolute"; 671 imgNode.style.visibility = "hidden"; 672 673 var key = tileKey + "/" + sublayer; 674 // Cache img so it can also be used in subsequent tile requests 675 this._lru.set(key, imgNode); 676 677 if (key in this._tileCallbacks && this._tileCallbacks[key]) {
678 // Use the tile for *all* pending callbacks. They'll be cloned anyway. 679 this._tileCallbacks[key].forEach(function (callback) { return callback(imgNode); }); 680 delete this._tileCallbacks[key]; 681 } 682 } else if (imgNode.src.match(this._staticRegExp)) { 683 imgNode.style.visibility = "hidden"; 684 } 685 }, 686 687 createTile: function (coords, done) { 688 var key = this._tileCoordsToKey(coords), 689 tileContainer = L.DomUtil.create("div"); 690 691 tileContainer.style.textAlign = "left"; 692 tileContainer.dataset.pending = this._imagesPerTile; 693 done = done.bind(this, null, tileContainer); 694 695 for (var i = 0; i < this._imagesPerTile; ++i) { 696 var key2 = key + "/" + i, 697 imgNode = this._lru.get(key2); 698 if (imgNode) { 699 tileContainer.appendChild(this._clone(imgNode)); 700 --tileContainer.dataset.pending; 701 } else { 702 this._tileCallbacks[key2] = this._tileCallbacks[key2] || []; 703 this._tileCallbacks[key2].push( 704 function (c /*, k2*/) { 705 return function (imgNode) { 706 c.appendChild(this._clone(imgNode)); 707 --c.dataset.pending; 708 if (!parseInt(c.dataset.pending)) { 709 done(); 710 } 711 }.bind(this); 712 }.bind(this)(tileContainer /*, key2*/) 713 ); 714 } 715 } 716 717 if (!parseInt(tileContainer.dataset.pending)) { 718 L.Util.requestAnimFrame(done); 719 } 720 return tileContainer; 721 }, 722 723 _clone: function (imgNode) { 724 var clonedImgNode = imgNode.cloneNode(true); 725 clonedImgNode.style.visibility = "visible"; 726 return clonedImgNode; 727 }, 728 729 _checkZoomLevels: function () { 730 //setting the zoom level on the Google map may result in a different zoom level than the one requested 731 //(it won't go beyond the level for which they have data). 732 var zoomLevel = this._map.getZoom(), 733 gMapZoomLevel = this._mutant.getZoom(); 734 735 if (!zoomLevel || !gMapZoomLevel) { return; } 736 737 if ( 738 gMapZoomLevel !== zoomLevel || //zoom levels are out of sync, Google doesn't have data 739 gMapZoomLevel > this.options.maxNativeZoom 740 ) { 741 //at current location, Google does have data (contrary to maxNativeZoom) 742 //Update maxNativeZoom 743 this._setMaxNativeZoom(gMapZoomLevel); 744 } 745 }, 746 747 _setMaxNativeZoom: function (zoomLevel) { 748 if (zoomLevel !== this.options.maxNativeZoom) { 749 this.options.maxNativeZoom = zoomLevel; 750 this._resetView(); 751 } 752 }, 753 754 _update: function (center) { 755 // zoom level check needs to happen before super's implementation (tile addition/creation) 756 // otherwise tiles may be missed if maxNativeZoom is not yet correctly determined 757 if (this._mutant) { 758 center = center || this._map.getCenter(); 759 var _center = new google.maps.LatLng(center.lat, center.lng), 760 zoom = Math.round(this._map.getZoom()), 761 mutantZoom = this._mutant.getZoom(); 762 763 this._mutant.setCenter(_center); 764 765 //ignore fractional zoom levels 766 if (zoom !== mutantZoom) { 767 this._mutant.setZoom(zoom); 768 769 if (this._mutantIsReady) { this._checkZoomLevels(); } 770 //else zoom level check will be done later by 'idle' handler 771 } 772 } 773 774 L.GridLayer.prototype._update.call(this, center); 775 }, 776 777 // @method whenReady(fn: Function, context?: Object): this 778 // Runs the given function `fn` when the mutant gets initialized, or immediately 779 // if it's already initialized, optionally passing a function context. 780 whenReady: function (callback, context) { 781 if (this._mutant) { 782 callback.call(context || this, { target: this }); 783 } else { 784 this.on("spawned", callback, context); 785 } 786 return this; 787 }, 788 }); 789 790 // ðfactory gridLayer.googleMutant(options) 791 // Returns a new `GridLayer.GoogleMutant` given its options 792 L.gridLayer.googleMutant = function (options) { 793 return new L.GridLayer.GoogleMutant(options); 794 }; 795 796}()); 797//# 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.