1class MapBuilder { 2 3 // DEFAULT VALUES 4 zoom = 14; 5 maxZoom = 18; 6 minZoom = 3; 7 8 9 // SETTINGS 10 listOfAllMarkers = []; 11 12 APIMap; 13 APIGecode; 14 15 _useMapyCz = false; 16 _useGoogleMaps = false; 17 _useOpenStreetMap = false 18 19 link; 20 logo; 21 copyright; 22 23 mapRect = null; 24 25 numberOfRows = 0; 26 numberOfCols = 0; 27 minimumImagesinView = 0; 28 29 markers = []; 30 31 map = null; 32 container = null; 33 containerWrap = null; 34 35 wheel = false; 36 mouseDown = false; 37 38 mouseDownX = 0; 39 mouseDownY = 0; 40 mouseDownTop = 0 41 mouseDownLeft = 0; 42 43 mapContainerLeft = 0; 44 mapContainerTop = 0; 45 46 isMapRendered = false; 47 isClusteringDisabled = false; 48 clusteringDistance = 0.2; 49 50 offsetY = 0; 51 offsetX = 0; 52 53 renderedMapCenterX = 0; 54 renderedMapCenterY = 0; 55 renderedMapStartX = 0; 56 renderedMapStartY = 0; 57 renderedMapEndX = 0; 58 renderedMapEndY = 0; 59 60 requiredCenter = { 61 zoom: null, 62 lat: null, 63 lon: null 64 }; 65 66 calledRender = false; 67 calledStart = false; 68 readyToRender = false; 69 70 timerToOverlay = null; 71 72 lastMouseMoveEvents = []; 73 74 disableCtrlBool = false; 75 allowOneTouchScrollBool = false; 76 77 mapMoving = false; 78 79 constructor(map) { 80 81 if (!map) { 82 return; 83 } 84 85 if (!map.offsetParent) { 86 87 let observer = new IntersectionObserver(function (entries, observer) { 88 entries.forEach(entry => { 89 if (entry.intersectionRatio == 0) { 90 return; 91 } 92 observer.disconnect(); 93 this.#start(map); 94 this.render(); 95 }); 96 }.bind(this)); 97 98 observer.observe(map); 99 100 return; 101 } 102 103 this.#start(map); 104 } 105 106 #start(map) { 107 108 this.calledStart = true; 109 110 this.map = map; 111 this.map.classList.add('js_map'); 112 113 if (mapyCzKey.match(/^\{[a-zA-Z0-9:]+\}$/)) { 114 mapyCzKey = ''; 115 } 116 117 if (googleMapsKey.match(/^\{[a-zA-Z0-9:]+\}$/)) { 118 googleMapsKey = ''; 119 } 120 121 if (mapyCzKey) { 122 this.useMapyCz(mapyCzKey); 123 } else if (googleMapsKey) { 124 this.useGoogleMaps(googleMapsKey); 125 } else { 126 this.useOpenStreetMap(); 127 } 128 129 this.map.style.backgroundColor = 'white'; 130 this.map.style.cursor = 'grab'; 131 this.map.style.overflow = 'hidden'; 132 this.map.style.position = 'relative'; 133 134 if (this.map.hasAttribute('disable-ctrl')) { 135 this.disableCtrlBool = true; 136 } 137 138 if (this.map.hasAttribute('allow-one-touch-scroll')) { 139 this.allowOneTouchScrollBool = true; 140 } 141 } 142 143 #getTwoPointDistance(x1, x2) { 144 return Math.max(x1, x2) - Math.min(x1, x2); 145 } 146 147 #getDistance(event) { 148 let dx = this.#getTwoPointDistance(event.touches[0].clientX, event.touches[1].clientX); 149 let dy = this.#getTwoPointDistance(event.touches[0].clientY, event.touches[1].clientY); 150 return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); 151 }; 152 153 #overlay(text) { 154 let overlay = document.createElement('div'); 155 overlay.classList.add('js_map_overlay'); 156 overlay.style.position = 'absolute' 157 overlay.style.width = '100%'; 158 overlay.style.height = '100%'; 159 overlay.style.top = '0'; 160 overlay.style.left = '0'; 161 overlay.style.backgroundColor = 'black'; 162 overlay.style.opacity = '0'; 163 overlay.style.justifyContent = 'center'; 164 overlay.style.alignItems = 'center'; 165 overlay.style.display = 'flex'; 166 overlay.style.zIndex = 101; 167 overlay.style.color = 'white'; 168 overlay.style.fontSize = '30px'; 169 overlay.style.textAlign = 'center'; 170 overlay.style.fontWeight = 'bold'; 171 overlay.style.transition = 'all 0.3s ease'; 172 overlay.style.visibility = 'hidden'; 173 overlay.style.padding = '25px'; 174 overlay.style.userSelect = 'none'; 175 176 if (this.offsetX && this.offsetX > 0) { 177 overlay.style.paddingLeft = (25 + this.offsetX) + 'px'; 178 } 179 if (this.offsetX && this.offsetX < 0) { 180 overlay.style.paddingRight = (25 + Math.abs(this.offsetX)) + 'px'; 181 } 182 183 if (this.offsetY && this.offsetY > 0) { 184 overlay.style.paddingTop = (25 + this.offsetY) + 'px'; 185 } 186 if (this.offsetY && this.offsetY < 0) { 187 overlay.style.paddingBottom = (25 + Math.abs(this.offsetY)) + 'px'; 188 } 189 190 overlay.textContent = text; 191 this.map.append(overlay); 192 193 return overlay; 194 } 195 196 #getTranslateX(el) { 197 198 let offset = parseFloat(el.getAttribute('translateX')); 199 if (!offset) { 200 return 0; 201 } 202 203 return offset; 204 } 205 206 #getTranslateY(el) { 207 208 let offset = parseFloat(el.getAttribute('translateY')); 209 if (!offset) { 210 return 0; 211 } 212 213 return offset; 214 } 215 216 async useGoogleMaps(key) { 217 218 this._useGoogleMaps = true; 219 this.APIMap = 'https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?session={session}&key=' + key;
220 this.APIGecode = 'https://maps.googleapis.com/maps/api/geocode/json?address={query}&key=' + key; 221 222 let ratio = Math.ceil(this.getPixelDensityRatio()); 223 if (ratio >= 3) { 224 ratio = 4; 225 } 226 227 let response = await fetch('https://tile.googleapis.com/v1/createSession?mapType=roadmap&language=' + this.getLang() + '&key=' + key + '&highDpi=true&scale=scaleFactor' + ratio + 'x', { 228 method: 'POST', 229 body: JSON.stringify({ 230 mapType: 'roadmap', 231 language: 'cs', 232 region: 'CZ', 233 styles: typeof googleMapsStyles == 'undefined' ? [{}] : googleMapsStyles 234 }), 235 headers: { 236 'Content-Type': 'application/json', 237 } 238 }); 239 240 let data = await response.json(); 241 if ('error' in data) { 242 243 if ('message' in data.error) { 244 console.error('PÅi vytváÅenà session k tvorbÄ Google mapy doÅ¡lo k chybÄ!\n' + data.error.message); 245 } else { 246 console.error('Mapu se nepodaÅilo vykreslit. Zkontrolujte prosÃm správnost API klÃÄe.'); 247 } 248 249 return; 250 } 251 this.APIMap = this.APIMap.replace('{session}', data.session); 252 253 fetch('https://tile.googleapis.com/tile/v1/viewport?session=' + data.session + '&key=' + key + '&zoom=10&north=5&south=5&east=5&west=5') 254 .then((response) => response.json()) 255 .then(function(data) { 256 257 this.addAttribution({ 258 logo: googleMapsLogo, 259 logoHref: 'https://www.google.cz/maps', 260 name: 'Google', 261 copyright: data.copyright 262 }); 263 264 this.readyToRender = true; 265 if (this.calledRender) { 266 this.render(); 267 } 268 }.bind(this)); 269 } 270 271 useMapyCz(key) { 272 console.log('Use Mapy.cz ' + key); 273 274 let ratio = Math.ceil(this.getPixelDensityRatio()); 275 if (ratio > 1) { 276 ratio = '@2x'; 277 } else { 278 ratio = ''; 279 } 280 281 this._useMapyCz = true; 282 this.APIMap = 'https://api.mapy.cz/v1/maptiles/outdoor/256' + ratio + '/{z}/{x}/{y}?lang=' + this.getLang() + '&apikey=' + key; 283 this.APIGecode = 'https://api.mapy.cz/v1/geocode?query={query}&limit=1&lang=cs&apikey=' + key; 284 285 this.addAttribution({ 286 logo: mapyCzLogo, 287 logoHref: 'https://mapy.cz/', 288 copyright: '© Seznam.cz a.s. a dalÅ¡Ã', 289 copyrightHref: 'https://api.mapy.cz/copyright', 290 name: 'Seznam' 291 }); 292 293 this.readyToRender = true; 294 } 295 296 useOpenStreetMap() { 297 298 this._useOpenStreetMap = true; 299 this.APIMap = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; 300 this.APIGecode = 'https://nominatim.openstreetmap.org/search?q={query}&format=json'; 301 302 this.addAttribution({ 303 copyright: 'OpenStreetMap', 304 copyrightHref: 'https://www.openstreetmap.org/copyright' 305 }); 306 this.readyToRender = true; 307 } 308 309 addAttribution(options) { 310 311 if ('logo' in options) { 312 let logo = document.createElement(('logoHref' in options) ? 'a' : 'span'); 313 if ('logoHref' in options) { 314 logo.href = options.logoHref; 315 } 316 let img = document.createElement('img'); 317 img.src = options.logo; 318 img.alt = (options.name ?? 'Map') + ' logo'; 319 img.draggable = false; 320 img.role = 'presentation'; 321 logo.append(img); 322 logo.ariaLabel = (options.name ?? 'Map') + ' logo'; 323 logo.style.position = 'absolute'; 324 logo.style.bottom = '10px'; 325 logo.style.left = '10px'; 326 logo.style.zIndex = 50; 327 logo.style.userSelect = 'none'; 328 this.map.append(logo); 329 } 330 331 let copyright = document.createElement(('copyrightHref' in options) ? 'a' : 'span'); 332 if ('copyrightHref' in options) { 333 copyright.href = options.copyrightHref; 334 } 335 copyright.innerHTML = options.copyright; 336 copyright.style.position = 'absolute'; 337 copyright.style.bottom = '0px'; 338 copyright.style.right = '0px'; 339 copyright.style.padding = '2px 4px'; 340 copyright.style.backgroundColor = '#ffffff82'; 341 copyright.style.fontWeight = '500'; 342 copyright.style.zIndex = 50; 343 copyright.style.userSelect = 'none';
344 copyright.style.textDecoration = 'none'; 345 copyright.style.fontSize = '12px'; 346 this.map.append(copyright); 347 348 } 349 350 addMarker( name = '' ) { 351 let marker = new MapMarker(name); 352 this.listOfAllMarkers.push(marker); 353 return marker; 354 } 355 356 checkMarkersCoordinates( fce ) { 357 358 let count = 0; 359 let countOfRequests = 0; 360 361 // Remove invalid markers 362 let i = this.listOfAllMarkers.length - 1; 363 while (i >= 0) { 364 if ((this.listOfAllMarkers[i].getLat() == null || this.listOfAllMarkers[i].getLon() == null) && this.listOfAllMarkers[i].getAddress().replaceAll(',', '').replaceAll('/', '').trim() == '') { 365 this.listOfAllMarkers.splice(i, 1); 366 } 367 368 i -= 1; 369 } 370 371 for (let marker of this.listOfAllMarkers) { 372 if (marker.getLat() != null && marker.getLon() != null) { 373 continue; 374 } 375 376 count++; 377 } 378 379 if (count > 0) { 380 381 for (let i = 0; i < this.listOfAllMarkers.length; i++) { 382 383 let marker = this.listOfAllMarkers[i]; 384 385 if (marker.getLat() != null && marker.getLon() != null) { 386 continue; 387 } 388 389 this.getLatLon(marker.getAddress(), function(lat, lon) { 390 391 countOfRequests++; 392 393 marker.setLat(lat); 394 marker.setLon(lon); 395 if (countOfRequests == count) { 396 let newArray = []; 397 for (let marker of this.listOfAllMarkers) { 398 if (marker.getLat() != null && marker.getLon() != null) { 399 newArray.push(Object.assign(Object.create(Object.getPrototypeOf(marker)), marker)); 400 } 401 } 402 this.listOfAllMarkers = newArray; 403 fce(); 404 } 405 }.bind(this)); 406 } 407 408 return; 409 } 410 411 fce(); 412 } 413 414 render() { 415 416 if (this.calledStart == false) { 417 return; 418 } 419 420 this.calledRender = true; 421 if (this.readyToRender == false) { 422 console.error('Mapa nenà jeÅ¡tÄ pÅipravená na naÄtenÃ!'); 423 return; 424 } 425 426 this.checkMarkersCoordinates(this.renderMap.bind(this)); 427 } 428 429 renderMarkers() { 430 431 let markers = this.map.querySelectorAll('.js_map_marker'); 432 for (let marker of markers) { 433 marker.remove(); 434 } 435 436 this.setMarkersToMap(); 437 } 438 439 renderMap() { 440 441 if (!mapCtrlZoom || mapCtrlZoom.match(/^\{[a-zA-Z0-9:]+\}$/)) { 442 mapCtrlZoom = 'Text {common::1::mapCtrlZoom} nebyl nalezen'; 443 } 444 this.overlayCtrl = this.#overlay(mapCtrlZoom); 445 446 if (!mapTwoTouches || mapTwoTouches.match(/^\{[a-zA-Z0-9:]+\}$/)) { 447 mapTwoTouches = 'Text {common::1::mapTwoTouches} nebyl nalezen'; 448 } 449 this.overlayTouches = this.#overlay(mapTwoTouches); 450 451 this.onResize(); 452 453 // Get tile coords for markers 454 for (let marker in this.listOfAllMarkers.entries()) { 455 let cache = this.toTileCoords(marker.getLat(), marker.getLon()); 456 marker.setX(cache.x); 457 marker.setY(cache.y); 458 459 if (isNaN(marker.getID()) || !marker.getID()) { 460 marker.setID(i); 461 } 462 } 463 464 let zoom = this.requiredCenter.zoom; 465 let lat = this.requiredCenter.lat; 466 let lon = this.requiredCenter.lon; 467 468 if (lat == null || lon == null) { 469 470 if (this.listOfAllMarkers.length == 0) { 471 zoom = 18; 472 lat = 49.85811; 473 lon = 18.27361; 474 console.error('Mapa neobsahuje žádné body k vykreslenà nebo jsou vÅ¡echny zadané body nevalidnÃ!'); 475 476 } else { 477 478 let middle = this.getMiddleFit(this.listOfAllMarkers); 479 zoom = zoom ? zoom : middle.zoom; 480 lat = middle.lat; 481 lon = middle.lon; 482 } 483 } 484 485 // Test image 486 if (this._useMapyCz || this._useOpenStreetMap) { 487 var http = new XMLHttpRequest(); 488 http.open('HEAD', this.APIMap.replace('{z}', 10).replace('{x}', 1).replace('{y}', 1), false); 489 http.send(); 490 if (http.status == 403 || http.status == 404) { 491 console.error('Mapu se nepodaÅilo vykreslit. Zkontrolujte prosÃm správnost API klÃÄe.'); 492 return; 493 } 494 } 495 496 this.loadMap({ 497 lat: lat, 498 lon: lon, 499 zoom: zoom,
500 useOffset: true 501 }); 502 503 if (this.listOfAllMarkers.length > 0) { 504 let marker = document.createElement('img'); 505 marker.onload = function() { 506 MapMarker.markerWidth = marker.naturalWidth 507 MapMarker.markerHeight = marker.naturalHeight; 508 this.setMarkersToMap(); 509 }.bind(this) 510 marker.onerror = function() { 511 console.error('Map pin se nepodaÅilo naÄÃst!') 512 } 513 marker.src = this.listOfAllMarkers[0].image; 514 } 515 this.map.addEventListener('wheel', this.onWheel.bind(this)); 516 document.addEventListener('mouseup', this.onMouseUp.bind(this)); 517 document.addEventListener('touchend', this.onMouseUp.bind(this)); 518 document.addEventListener('mousemove', this.onMouseMove.bind(this)); 519 document.addEventListener('touchmove', this.onMouseMove.bind(this)); 520 this.map.addEventListener('mousedown', this.onMouseDown.bind(this)); 521 this.map.addEventListener('touchstart', this.onMouseDown.bind(this)); 522 this.map.addEventListener('dblclick', this.onDblClick.bind(this)); 523 window.addEventListener('resize', this.onResize.bind(this)); 524 document.addEventListener('touchmove', (e) => { 525 if (e.target.closest('.js_map')) { 526 e.preventDefault(); 527 } 528 }, {passive: false}); 529 530 this.isMapRendered = true; 531 } 532 533 onResize() { 534 535 console.log('onResize()'); 536 537 this.mapRect = this.map.getBoundingClientRect(); 538 539 this.numberOfRows = Math.ceil(this.mapRect.height / 256) + 1; 540 if (this.numberOfRows % 2 == 0) { 541 this.numberOfRows += 1; 542 } 543 544 this.numberOfCols = Math.ceil(this.mapRect.width / 256) + 1; 545 if (this.numberOfCols % 2 == 0) { 546 this.numberOfCols += 1; 547 } 548 549 this.minimumImagesinView = Math.ceil((this.mapRect.width / 256) * (this.mapRect.height / 256)) + 2; 550 } 551 552 onDblClick(e) { 553 if (!e.target.closest('img:not(.js_map_marker)')) { 554 return; 555 } 556 557 this.onWheel({ 558 target: e.target.closest('img:not(.js_map_marker)'), 559 clientX: e.clientX, 560 clientY: e.clientY, 561 ctrlKey: true, 562 deltaY: -1, 563 preventDefault: function() {} 564 }); 565 } 566 567 onWheel(e) { 568 569 570 if (!e.ctrlKey && this.disableCtrlBool == false) { 571 572 this.overlayCtrl.style.visibility = 'visible'; 573 this.overlayCtrl.style.opacity = '0.5'; 574 if (this.timerToOverlay) { 575 clearTimeout(this.timerToOverlay); 576 } 577 this.timerToOverlay = setTimeout(function() { 578 this.overlayCtrl.style.visibility = 'hidden'; 579 this.overlayCtrl.style.opacity = '0'; 580 }.bind(this), 750); 581 582 return; 583 } 584 585 e.preventDefault(); 586 this.removePopups(); 587 588 if (this.wheel == true) { 589 return; 590 } 591 592 console.log('onWheel()'); 593 594 this.wheel = true; 595 596 this.mapRect = this.map.getBoundingClientRect(); 597 let mapWidth = this.mapRect.width; 598 let mapheight = this.mapRect.height; 599 600 let cursorX = e.clientX - this.mapRect.x; 601 let cursorY = e.clientY - this.mapRect.y; 602 603 let originX = (100 * cursorX) / mapWidth; 604 let originY = (100 * cursorY) / mapheight; 605 606 this.map.querySelector('.js_map_container_wrap').style.transformOrigin = originX + '% ' + originY + '%'; 607 608 let image = e.target; 609 if (image.tagName != 'IMG') { 610 image = null; 611 let items = document.elementsFromPoint(e.clientX, e.clientY); 612 for (let i = 0; i < items.length; i++) { 613 if (items[i].tagName == 'IMG' && items[i].classList.contains('js_map_tile')) { 614 image = items[i]; 615 break; 616 } 617 } 618 619 if (!image) { 620 this.wheel = false; 621 return; 622 } 623 } 624 625 let imageRect = image.getBoundingClientRect(); 626 let x = parseInt(image.getAttribute('x')); 627 let y = parseInt(image.getAttribute('y')); 628 629 // scroll up --> zoom in 630 if (e.deltaY < 0) { 631 if (this.zoom >= this.maxZoom) { 632 this.wheel = false; 633 return; 634 } 635 636 this.map.querySelector('.js_map_container_wrap').style.transform = 'scale(2)'; 637 638 setTimeout(function() { 639 640 x *= 2; 641 let dx = e.clientX - imageRect.x; 642 let startX = imageRect.x - this.mapRect.x - dx; 643 if (dx > 256 / 2) { 644 x += 1;
645 dx -= 256 / 2; 646 startX += 256; 647 } 648 x += (dx / (256 / 2)); 649 650 y *= 2; 651 let dy = e.clientY - imageRect.y; 652 let startY = imageRect.y - this.mapRect.y - dy; 653 if (dy > 256 / 2) { 654 y += 1; 655 dy -= 256 / 2; 656 startY += 256; 657 } 658 y += (dy / (256 / 2)); 659 660 this.loadMap({ 661 x: x, 662 y: y, 663 zoom: this.zoom + 1, 664 startX: startX, 665 startY: startY 666 }); 667 this.setMarkersToMap(); 668 this.wheel = false; 669 }.bind(this), 200); 670 return; 671 } 672 673 if (this.zoom <= this.minZoom) { 674 this.wheel = false; 675 return; 676 } 677 678 // scroll down --> zoom out 679 this.map.querySelector('.js_map_container_wrap').style.transform = 'scale(0.5)'; 680 681 setTimeout(function() { 682 683 x /= 2; 684 let dx = e.clientX - imageRect.x; 685 let startX = imageRect.x - this.mapRect.x + (dx / 2); 686 if ((x * 2) % 2 != 0) { 687 startX -= (256 / 2); 688 } 689 690 y /= 2; 691 let dy = e.clientY - imageRect.y; 692 let startY = imageRect.y - this.mapRect.y + (dy / 2); 693 if ((y * 2) % 2 != 0) { 694 startY -= (256 / 2); 695 } 696 697 this.loadMap({ 698 x: x, 699 y: y, 700 zoom: this.zoom - 1, 701 startX: startX, 702 startY: startY 703 }); 704 705 this.setMarkersToMap(); 706 this.wheel = false; 707 }.bind(this), 200); 708 } 709 710 onMouseUp(e) { 711 712 if (this.mapMoving == false && e.target.closest('.js_map_marker')) { 713 this.onMarkerClick(e); 714 } 715 716 if (!e.touches) { 717 this.map.style.cursor = 'grab'; 718 } 719 720 this.lastMouseMoveEvents = []; 721 722 this.mouseDown = false; 723 this.mapMoving = false; 724 } 725 726 onMouseDown(e) { 727 728 if (e.target.closest('.js_map_popup')) { 729 return; 730 } 731 732 e.preventDefault(); 733 734 this.mouseDown = true; 735 736 let eventCoordinates = this.getEventCoordinates(e); 737 this.mouseDownX = eventCoordinates.x; 738 this.mouseDownY = eventCoordinates.y; 739 740 741 if ((!e.touches || e.touches.length == 1) && (e.target.classList.contains('js_map_overlay') || e.target.classList.contains('js_map_tile'))) { 742 this.map.style.cursor = 'grabbing'; 743 } 744 745 this.mapContainerTop = this.#getTranslateY(this.map.querySelector('.js_map_container')); 746 this.mapContainerLeft = this.#getTranslateX(this.map.querySelector('.js_map_container')); 747 } 748 749 onMouseMove(e) { 750 751 if (this.mouseDown == false || !this.map) { 752 return; 753 } 754 755 if (this.wheel == true) { 756 return; 757 } 758 759 this.removePopups(); 760 761 this.centeredToMarker = null; 762 763 if ('touches' in e) { 764 765 let same = false; 766 for (let _e of this.lastMouseMoveEvents) { 767 768 let eventCoordinates1 = this.getEventCoordinates(e); 769 let eventCoordinates2 = this.getEventCoordinates(_e); 770 771 if (eventCoordinates1.x == eventCoordinates2.x && eventCoordinates1.y == eventCoordinates2.y) { 772 same = true; 773 } 774 } 775 776 if (!same) { 777 this.lastMouseMoveEvents.unshift(e); 778 this.lastMouseMoveEvents.length = this.lastMouseMoveEvents.length <= 3 ? this.lastMouseMoveEvents.length : 3; 779 } 780 781 if (this.lastMouseMoveEvents.length != 3) { 782 return; 783 } 784 785 let twoTouches = false; 786 for (let event of this.lastMouseMoveEvents) { 787 if (event.touches.length == 2) { 788 twoTouches = true; 789 break; 790 } 791 } 792 793 if (this.allowOneTouchScrollBool == false && twoTouches == false) { 794 this.overlayTouches.style.visibility = 'visible'; 795 this.overlayTouches.style.opacity = '0.5'; 796 this.overlayTouches.style.cursor = 'grab'; 797 if (this.timerToOverlay) { 798 clearTimeout(this.timerToOverlay); 799 } 800 this.timerToOverlay = setTimeout(function() { 801 this.overlayTouches.style.visibility = 'hidden'; 802 this.overlayTouches.style.opacity = '0'; 803 }.bind(this), 750); 804 805 return; 806 } 807 } 808 809 this.mapMoving = true; 810
811 let eventCoordinates = this.getEventCoordinates(e); 812 813 let x = eventCoordinates.x; 814 let y = eventCoordinates.y; 815 816 if (e.touches && e.touches.length == 2) { 817 818 if (this.lastMouseMoveEvents.length == 3) { 819 820 let dx = this.#getTwoPointDistance(this.lastMouseMoveEvents[0].touches[0].clientX, this.lastMouseMoveEvents[0].touches[1].clientX); 821 let dy = this.#getTwoPointDistance(this.lastMouseMoveEvents[0].touches[0].clientY, this.lastMouseMoveEvents[0].touches[1].clientY); 822 823 x = this.lastMouseMoveEvents[0].touches[0].clientX + dx / 2; 824 y = this.lastMouseMoveEvents[0].touches[1].clientY + dy / 2; 825 826 let target = null; 827 for (let image of this.container.querySelectorAll(':scope > img')) { 828 let rect = image.getBoundingClientRect(); 829 if (rect.x < x && rect.x + rect.width > x) { 830 if (rect.y < y && rect.y + rect.height > y) { 831 target = image; 832 break; 833 } 834 } 835 } 836 837 if (this.#getDistance(this.lastMouseMoveEvents[0]) - 3.5 > this.#getDistance(this.lastMouseMoveEvents[1])) { 838 if (this.#getDistance(this.lastMouseMoveEvents[1]) - 2 > this.#getDistance(this.lastMouseMoveEvents[2])) { 839 840 this.onWheel({ 841 ctrlKey: true, 842 clientX: x, 843 clientY: y, 844 deltaY: -1, 845 target: target, 846 preventDefault: function() {} 847 }); 848 849 return; 850 } 851 } 852 853 if (this.#getDistance(this.lastMouseMoveEvents[0]) + 3.5 < this.#getDistance(this.lastMouseMoveEvents[1])) { 854 if (this.#getDistance(this.lastMouseMoveEvents[1]) + 2 < this.#getDistance(this.lastMouseMoveEvents[2])) { 855 856 this.onWheel({ 857 ctrlKey: true, 858 clientX: x, 859 clientY: y, 860 deltaY: 1, 861 target: target, 862 preventDefault: function() {} 863 }); 864 865 return; 866 } 867 } 868 } 869 870 x = Math.min(e.touches[0].clientX, e.touches[1].clientX) + (Math.max(e.touches[0].clientX, e.touches[1].clientX) - Math.min(e.touches[0].clientX, e.touches[1].clientX)) / 2; 871 y = Math.min(e.touches[0].clientY, e.touches[1].clientY) + (Math.max(e.touches[0].clientY, e.touches[1].clientY) - Math.min(e.touches[0].clientY, e.touches[1].clientY)) / 2; 872 } 873 874 let dx = x - this.mouseDownX; 875 let dy = y - this.mouseDownY; 876 877 let left = Math.floor(this.mapContainerLeft + dx); 878 let top = Math.floor(this.mapContainerTop + dy); 879 880 let firstImageY = this.map.querySelector('img[y="' + (this.zoom >= 6 ? '1' : '0') + '"]'); 881 let lastImageY = this.map.querySelector('img[y="' + (Math.pow(2, this.zoom) - 1) + '"]'); 882 883 if (firstImageY && dy > 0) { 884 if (this.#getTranslateY(firstImageY) >= -this.#getTranslateY(this.map.querySelector('.js_map_container'))) { 885 top = -this.#getTranslateY(firstImageY); 886 } 887 } 888 889 if (lastImageY) { 890 891 if (this.#getTranslateY(lastImageY) + lastImageY.height + top < this.map.querySelector('.js_map_container').clientHeight - 1) { 892 top = this.#getTranslateY(this.map.querySelector('.js_map_container')); 893 } 894 } 895 896 this.map.querySelector('.js_map_container').style.transform = 'translate(' + left + 'px, ' + top + 'px)'; 897 this.map.querySelector('.js_map_container').setAttribute('translateX', left); 898 this.map.querySelector('.js_map_container').setAttribute('translateY', top); 899 this.fillViewWithImages(); 900 } 901 902 onMarkerClick(e) { 903 904 this.map.querySelectorAll('.js_map_popup').forEach(popup => popup.remove()); 905 906 let marker = this.getMarkerByID(e.target.closest('.js_map_marker').getAttribute('marker-id')); 907 if (!marker) { 908 return 909 } 910 911 document.dispatchEvent(new CustomEvent('mapMarkerClick', { detail: { 912 map: this.map, 913 marker: e.target.closest('.js_map_marker'), 914 markerInstance: marker, 915 state: 'start' 916 }})); 917 918 marker.onClickCallback(marker); 919 920 if (marker.centerOnClick == false) { 921 return; 922 } 923 924 if (marker.getAmount() > 1) { 925 926 let coords = this.getMiddleFit([marker].concat(marker.getChildren())); 927 this.loadMap({ 928 lat: coords.lat, 929 lon: coords.lon, 930 zoom: coords.zoom, 931 useOffset: true 932 }); 933 this.setMarkersToMap(); 934 935 return; 936 } 937 938 this.centerToMarker(this.getMarkerByID(e.target.closest('.js_map_marker').getAttribute('marker-id')), function() { 939 document.dispatchEvent(new CustomEvent('mapMarkerClick', { detail: { 940 map: this.map, 941 marker: e.target.closest('.js_map_marker'), 942 markerInstance: marker, 943 state: 'end' 944 }})); 945 }.bind(this)); 946 } 947 948 loadMap(options) { 949
950 if (!('x' in options) || !('y' in options)) { 951 952 let tiles = this.toTileCoords(options.lat, options.lon, options.zoom); 953 options.x = tiles.x; 954 options.y = tiles.y; 955 } 956 957 this.zoom = options.zoom; 958 959 if (this.renderedMapCenterX == options.x && this.renderedMapCenterY == options.y) { 960 if (this.#getTranslateX(this.map.querySelector('.js_map_container') == 0 && this.#getTranslateY(this.map.querySelector('.js_map_container') == 0))) { 961 if (this.container != null) { 962 return; 963 } 964 } 965 } 966 967 let x = Math.floor(options.x); 968 let y = Math.floor(options.y); 969 970 let dx = (options.x - x) * 256; 971 let dy = (options.y - y) * 256; 972 973 let startX = x - Math.floor(this.numberOfCols / 2); 974 let endX = x + Math.floor(this.numberOfCols / 2); 975 976 let startY = y - Math.floor(this.numberOfRows / 2); 977 let endY = y + Math.floor(this.numberOfRows / 2); 978 979 let _startX = -Math.floor(this.numberOfCols / 2); 980 let _startY = -Math.floor(this.numberOfRows / 2); 981 982 let viewMiddleX = this.mapRect.width / 2; 983 let viewMiddleY = this.mapRect.height / 2; 984 985 this.containerWrap = document.createElement('div'); 986 this.containerWrap.classList.add('js_map_container_wrap'); 987 this.containerWrap.style.width = '100%'; 988 this.containerWrap.style.height = '100%'; 989 this.containerWrap.style.transform = null; 990 this.containerWrap.style.transition = 'transform 0.4s ease'; 991 this.containerWrap.style.userSelect = 'none'; 992 this.containerWrap.style.position = 'absolute'; 993 this.map.append(this.containerWrap); 994 995 this.container = document.createElement('div'); 996 this.container.classList.add('js_map_container'); 997 this.container.style.position = 'absolute'; 998 this.container.style.width = '100%'; 999 this.container.style.height = '100%'; 1000 this.containerWrap.append(this.container); 1001 1002 if (!('startX' in options) || !('startY' in options)) { 1003 options.startX = (this.mapRect.width / 2) - dx; 1004 options.startY = (this.mapRect.height / 2) - dy; 1005 } 1006 1007 if ('useOffset' in options) { 1008 options.startX += this.offsetX / 2; 1009 options.startY += this.offsetY / 2; 1010 } 1011 1012 let offsetFromMiddleX = Math.round(((options.startX + (256 / 2)) - viewMiddleX) / 256); 1013 let offsetFromMiddleY = Math.round(((options.startY + (256 / 2)) - viewMiddleY) / 256); 1014 1015 startX -= offsetFromMiddleX; 1016 endX -= offsetFromMiddleX; 1017 1018 startY -= offsetFromMiddleY; 1019 endY -= offsetFromMiddleY; 1020 1021 _startX -= 2 * offsetFromMiddleX; 1022 _startY -= 2 * offsetFromMiddleY; 1023 1024 options.startX += offsetFromMiddleX * 256; 1025 options.startY += offsetFromMiddleY * 256; 1026 1027 let _x = startX; 1028 let __x = _startX; 1029 1030 let Xs = []; 1031 let c = startX; 1032 while (c != endX + 1) { 1033 1034 if (c < 0) { 1035 Xs.push(Math.pow(2, this.zoom) + c); 1036 c++; 1037 continue; 1038 } 1039 1040 if (c >= Math.pow(2, this.zoom)) { 1041 Xs.push(c - Math.pow(2, this.zoom)); 1042 c++; 1043 continue; 1044 } 1045 1046 Xs.push(c); 1047 c++; 1048 } 1049 1050 if (startY < 0) { 1051 startY = 0; 1052 } 1053 1054 let _Xs = Xs.filter((value, i) => { 1055 if (i != 0 && value < Xs[i-1]) { 1056 return false; 1057 } 1058 1059 return true; 1060 }); 1061 1062 this.renderedMapStartX = Xs[0]; 1063 this.renderedMapStartY = startY; 1064 1065 this.renderedMapEndX = _Xs[Xs.length - 1]; 1066 this.renderedMapEndY = endY; 1067 1068 for (let __y = _startY, _y = startY; _y <= endY; _y++, __y++) { 1069 1070 for (__x = _startX, _x = 0; _x < Xs.length; _x++, __x++) { 1071 1072 1073 let translateX = Math.floor(options.startX + (__x * 256)); 1074 let translateY = Math.floor(options.startY + (__y * 256)); 1075 1076 let img = document.createElement('img'); 1077 img.src = this.APIMap.replace('{z}', this.zoom).replace('{x}', Xs[_x]).replace('{y}', _y); 1078 img.width = 256; 1079 img.height = 256; 1080 img.setAttribute('z', this.zoom); 1081 img.setAttribute('x', Xs[_x]); 1082 img.setAttribute('y', _y); 1083 img.setAttribute('translateX', translateX); 1084 img.setAttribute('translateY', translateY); 1085 img.classList.add('js_map_tile'); 1086 img.alt = ''; 1087 img.draggable = false; 1088 img.role = 'presentation'; 1089 img.style.cursor = 'inherit'; 1090 img.style.userSelect = 'none'; 1091 img.style.position = 'absolute'; 1092 img.style.transform = 'translate(' + translateX + 'px, ' + translateY + 'px)'; 1093 img.style.transition = 'opacity 0.2s ease'; 1094 img.addEventListener('mousedown', function(e) { 1095 e.preventDefault(); 1096 }); 1097 img.style.opacity = '0'; 1098 img.onload = function() { 1099 img.style.opacity = ''; 1100 } 1101 this.container.append(img); 1102 } 1103 _x = 0; 1104 __x = _startX; 1105 } 1106 1107 if (this.map.querySelectorAll('.js_map_container_wrap').length > 1) { 1108 this.map.querySelector('.js_map_container_wrap').remove(); 1109 } 1110 1111 let firstImageY = this.map.querySelector('img[y="' + (this.zoom >= 6 ? '1' : '0') + '"]'); 1112 if (firstImageY) { 1113 1114 let mapRect = this.map.getBoundingClientRect(); 1115 let imageRect = firstImageY.getBoundingClientRect(); 1116 if (imageRect.y > mapRect.y) { 1117 1118 let dx = imageRect.y - mapRect.y; 1119 this.map.querySelector('.js_map_container').style.top = (this.map.querySelector('.js_map_container').offsetTop - dx) + 'px'; 1120 } 1121 } 1122 1123 let lastImageY = this.map.querySelector('img[y="' + (Math.pow(2, this.zoom) - 1) + '"]'); 1124 if (lastImageY) { 1125 1126 let mapRect = this.map.getBoundingClientRect(); 1127 let imageRect = lastImageY.getBoundingClientRect(); 1128 if (imageRect.y + imageRect.height < mapRect.y + mapRect.height) { 1129 1130 let dx = (mapRect.y + mapRect.height) - (imageRect.y + imageRect.height); 1131 this.map.querySelector('.js_map_container').style.top = (this.map.querySelector('.js_map_container').offsetTop + dx) + 'px'; 1132 } 1133 } 1134 1135 let mapRect = this.map.getBoundingClientRect(); 1136 let pointX = this.offsetX / 2 + (mapRect.x + mapRect.width / 2); 1137 let pointY = this.offsetY / 2 + (mapRect.y + mapRect.height / 2); 1138 1139 let elements = document.elementsFromPoint(pointX, pointY); 1140 for (let el of elements) { 1141 if (el.classList.contains('js_map_tile')) { 1142 let tileRect = el.getBoundingClientRect(); 1143 this.renderedMapCenterX = parseInt(el.getAttribute('x')) + (pointX - tileRect.x) / 256; 1144 this.renderedMapCenterY = parseInt(el.getAttribute('y')) + (pointY - tileRect.y) / 256; 1145 } 1146 } 1147 } 1148
1149 getEventCoordinates(event) { 1150 if (!event.touches || event.touches.length == 1) { 1151 return { 1152 x: event?.clientX ? event?.clientX : event?.touches[0].clientX, 1153 y: event?.clientY ? event?.clientY : event?.touches[0].clientY 1154 }; 1155 } 1156 1157 let maxX = Math.max(event.touches[0].clientX, event.touches[1].clientX); 1158 let minX = Math.min(event.touches[0].clientX, event.touches[1].clientX) 1159 1160 let maxY = Math.max(event.touches[0].clientY, event.touches[1].clientY); 1161 let minY = Math.min(event.touches[0].clientY, event.touches[1].clientY); 1162 1163 return { 1164 x: minX + (maxX - minX) / 2, 1165 y: minY + (maxY - minY) / 2 1166 }; 1167 } 1168 1169 toTileCoords(lat, lon, _zoom = null) { 1170 1171 if (!_zoom) { 1172 _zoom = this.zoom; 1173 } 1174 1175 let n = Math.pow(2, _zoom); 1176 let x = n * ((lon + 180) / 360); 1177 let y = n * (1 - (Math.log(Math.tan(lat * (Math.PI / 180)) + (1 / Math.cos(lat * (Math.PI / 180)))) / Math.PI)) / 2; 1178 1179 return { 1180 x: x, 1181 y: y 1182 }; 1183 } 1184 1185 toLatLon(x, y, z = null) { 1186 1187 if (z == null) { 1188 z = this.zoom; 1189 } 1190 1191 let n=Math.PI-2*Math.PI*y/Math.pow(2,z); 1192 return { 1193 lat: 180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))), 1194 lon: x/Math.pow(2,z)*360-180 1195 }; 1196 } 1197 1198 getMiddle(markers) { 1199 function _getMiddle(prop, markers) { 1200 let values = markers.map(m => m[prop]); 1201 let min = Math.min(...values); 1202 let max = Math.max(...values); 1203 if (prop === 'lon' && (max - min > 180)) { 1204 values = values.map(val => val < max - 180 ? val + 360 : val); 1205 min = Math.min(...values); 1206 max = Math.max(...values); 1207 } 1208 let result = (min + max) / 2; 1209 if (prop === 'lon' && result > 180) { 1210 result -= 360 1211 } 1212 return result; 1213 } 1214 1215 return { 1216 lat: _getMiddle('lat', markers), 1217 lon: _getMiddle('lon', markers) 1218 } 1219 } 1220 1221 getMiddleFit(markers, _zoom = null) { 1222 1223 if (_zoom == null) { 1224 _zoom = 18; 1225 } 1226 1227 if (markers.length == 1) { 1228 return { 1229 zoom: _zoom, 1230 lat: markers[0].getLat(), 1231 lon: markers[0].getLon() 1232 }; 1233 } 1234 1235 if (_zoom <= this.minZoom) { 1236 let mid = this.getMiddle(markers); 1237 return { 1238 zoom: _zoom, 1239 lat: mid.lat, 1240 lon: mid.lon 1241 }; 1242 } 1243 1244 let zoomOut = false; 1245 1246 for (let i = 0; i < markers.length; i++) { 1247 let _coords = this.toTileCoords(markers[i].lat, markers[i].lon, _zoom); 1248 1249 for (let x = i + 1; x < markers.length; x++) { 1250 let __coords = this.toTileCoords(markers[x].lat, markers[x].lon, _zoom); 1251 1252 if ((Math.max(_coords.x, __coords.x) - Math.min(_coords.x, __coords.x)) * 256 > this.mapRect.width - 50 - this.offsetX) { 1253 zoomOut = true; 1254 break; 1255 } 1256 if ((Math.max(_coords.y, __coords.y) - Math.min(_coords.y, __coords.y)) * 256 > this.mapRect.height - 50 - this.offsetY) { 1257 zoomOut = true; 1258 break; 1259 } 1260 } 1261 if (zoomOut) { 1262 break; 1263 } 1264 } 1265 1266 if (zoomOut) { 1267 1268 _zoom -= 1; 1269 for (let marker of markers) { 1270 let cache = this.toTileCoords(marker.getLat(), marker.getLon(), _zoom); 1271 marker.setX(cache.x); 1272 marker.setY(cache.y); 1273 } 1274 1275 return this.getMiddleFit(markers, _zoom); 1276 } 1277 1278 let mid = this.getMiddle(markers); 1279 1280 return { 1281 zoom: _zoom, 1282 lat: mid.lat, 1283 lon: mid.lon 1284 }; 1285 } 1286 1287 getLatLon(address, func) { 1288 1289 console.error('Adresa "' + address + '" nemá zadané GPS souÅadnice. ProsÃm zvažte zadánà souÅadnic v K2. Gekodovánà adres zpomaluje naÄÃtánà mapy a rychleji vyÄerpává kredity, které jsou potÅebné k vygenerovánà mapy.'); 1290 1291 func = func.bind(this); 1292 1293 let url = this.APIGecode.replace('{query}', address); 1294 let result = {}; 1295 let xhr = new XMLHttpRequest(); 1296 1297 xhr.onreadystatechange = () => { 1298 1299 if (xhr.readyState == 4 && xhr.status == 403) { 1300 console.error('Požadavek ke gekódovánà byl zamÃtnut. Zkontrolujte správnost API klÃÄe, pÅÃpadnÄ zda má opravánÄnà ke gekódovánÃ.'); 1301 } 1302 1303 if (xhr.readyState != 4 || xhr.status != 200) { 1304 func(null, null); 1305 return; 1306 } 1307 1308 let lat = null; 1309 let lon = null; 1310 let data = JSON.parse(xhr.responseText); 1311 1312 if (this._useGoogleMaps == true && data.status == 'REQUEST_DENIED') { 1313 console.error('Požadavek ke gekódovánà byl zamÃtnut. Zkontrolujte správnost API klÃÄe, pÅÃpadnÄ zda má opravánÄnà ke gekódovánÃ.'); 1314 } 1315 1316 if (this._useGoogleMaps) { 1317 lat = data?.results[0]?.geometry?.location?.lat; 1318 lon = data?.results[0]?.geometry?.location?.lng; 1319 } 1320 1321 if (this._useOpenStreetMap) { 1322 lat = data[0]?.lat; 1323 lon = data[0]?.lon; 1324 } 1325 1326 if (this._useMapyCz) { 1327 lat = data?.items[0]?.position?.lat; 1328 lon = data?.items[0]?.position?.lon; 1329 } 1330 1331 func(parseFloat(lat), parseFloat(lon)); 1332 }; 1333 1334 xhr.open('GET', url, true); 1335 xhr.send(); 1336 1337 return result; 1338 } 1339 1340 getImagesInView() { 1341 1342 let mapRect = this.map.getBoundingClientRect(); 1343 let allImages = this.map.querySelectorAll('.js_map_tile'); 1344 let imagesInView = []; 1345 for (let img of allImages) { 1346 let imgRect = img.getBoundingClientRect(); 1347 if (imgRect.x + imgRect.width < mapRect.x) { 1348 continue; 1349 } 1350 1351 if (imgRect.x > mapRect.x + mapRect.width) { 1352 continue; 1353 } 1354 1355 if (imgRect.y + imgRect.height < mapRect.y) { 1356 continue; 1357 } 1358 1359 if (imgRect.y > mapRect.y + mapRect.height) { 1360 continue; 1361 } 1362 1363 imagesInView.push(img); 1364 } 1365 1366 return imagesInView; 1367 } 1368 1369 getContentOfImagesInView() { 1370 1371 let mapRect = this.map.getBoundingClientRect(); 1372
1373 let content = 0; 1374 let allImages = this.map.querySelectorAll('.js_map_tile'); 1375 for (let img of allImages) { 1376 let imgRect = img.getBoundingClientRect(); 1377 1378 if (imgRect.x + imgRect.width < mapRect.x) { 1379 continue; 1380 } 1381 1382 if (imgRect.x > mapRect.x + mapRect.width) { 1383 continue; 1384 } 1385 1386 if (imgRect.y + imgRect.height < mapRect.y) { 1387 continue; 1388 } 1389 1390 if (imgRect.y > mapRect.y + mapRect.height) { 1391 continue; 1392 } 1393 1394 let width = 256; 1395 let height = 256; 1396 1397 if (imgRect.x < mapRect.x) { 1398 width = width - (mapRect.x - imgRect.x); 1399 } 1400 1401 if (imgRect.x + imgRect.width > mapRect.x + mapRect.width) { 1402 width = width - ((imgRect.x + imgRect.width) - (mapRect.x + mapRect.width)); 1403 } 1404 1405 if (imgRect.y < mapRect.y) { 1406 height = height - (mapRect.y - imgRect.y); 1407 } 1408 1409 if (imgRect.y + imgRect.height > mapRect.y + mapRect.height) { 1410 height = height - ((imgRect.y + imgRect.height) - (mapRect.y + mapRect.height)); 1411 } 1412 1413 content += width * height; 1414 } 1415 1416 return content; 1417 } 1418 1419 getEdgeImages() { 1420 let list = []; 1421 let imagesInView = this.getImagesInView(); 1422 for (let img of imagesInView) { 1423 let imgRect = img.getBoundingClientRect(); 1424 1425 if (imgRect.x < this.mapRect.x && imgRect.x + imgRect.width > this.mapRect.x) { 1426 list.push(img); 1427 continue; 1428 } 1429 1430 if (imgRect.x < this.mapRect.x + this.mapRect.width && imgRect.x + imgRect.width > this.mapRect.x + this.mapRect.width) { 1431 list.push(img); 1432 continue; 1433 } 1434 1435 if (imgRect.y < this.mapRect.y && imgRect.y + imgRect.height > this.mapRect.y) { 1436 list.push(img); 1437 continue; 1438 } 1439 1440 if (imgRect.y < this.mapRect.y + this.mapRect.height && imgRect.y + imgRect.height > this.mapRect.y + this.mapRect.height) { 1441 list.push(img); 1442 continue; 1443 } 1444 } 1445 1446 return list; 1447 } 1448 1449 getPixelDensityRatio() { 1450 return window.devicePixelRatio; 1451 } 1452 1453 getLang() { 1454 1455 let lang = document.documentElement.lang; 1456 if (typeof defaultLang != 'undefined') { 1457 lang = defaultLang; 1458 } 1459 1460 return lang; 1461 } 1462 1463 expandMap(img) { 1464 1465 let x = parseInt(img.getAttribute('x')); 1466 let y = parseInt(img.getAttribute('y')); 1467 1468 let startX = x - 1; 1469 let endX = x + 1; 1470 1471 let startY = y - 1; 1472 let endY = y + 1; 1473 1474 let imgLeft = this.#getTranslateX(img); 1475 let imgTop = this.#getTranslateY(img); 1476 1477 let _x = startX; 1478 let __x = -1; 1479 1480 let Xs = []; 1481 let c = startX; 1482 while (c != endX + 1) { 1483 if (c < 0) { 1484 Xs.push(Math.pow(2, this.zoom) + c); 1485 c++; 1486 continue; 1487 } 1488 1489 if (c >= Math.pow(2, this.zoom)) { 1490 Xs.push(c - Math.pow(2, this.zoom)); 1491 c++; 1492 continue; 1493 } 1494 1495 Xs.push(c); 1496 c++; 1497 } 1498 1499 for (let __y = -1, _y = startY; _y <= endY; _y++, __y++) { 1500 for (__x = -1, _x = 0; _x < Xs.length; _x++, __x++) { 1501 1502 if (_y < 0) { 1503 continue; 1504 } 1505 1506 let translateX = Math.floor(imgLeft + (__x * 256)); 1507 let translateY = Math.floor(imgTop + (__y * 256)); 1508 1509 if (this.container.querySelector(':scope > img[translateX="' + translateX + '"][translateY="' + translateY + '"]')) { 1510 continue; 1511 } 1512 1513 let img = document.createElement('img'); 1514 img.src = this.APIMap.replace('{z}', this.zoom).replace('{x}', Xs[_x]).replace('{y}', _y); 1515 img.width = 256; 1516 img.height = 256; 1517 img.setAttribute('z', this.zoom); 1518 img.setAttribute('x', Xs[_x]); 1519 img.setAttribute('y', _y); 1520 img.setAttribute('translateX', translateX); 1521 img.setAttribute('translateY', translateY); 1522 img.classList.add('js_map_tile'); 1523 img.style.cursor = 'inherit'; 1524 img.style.userSelect = 'none'; 1525 img.style.position = 'absolute'; 1526 img.style.transform = 'translate(' + translateX + 'px, ' + translateY + 'px)'; 1527 img.style.transition = 'opacity 0.2s ease'; 1528 img.addEventListener('mousedown', function(e) { 1529 e.preventDefault(); 1530 }); 1531 img.style.opacity = '0'; 1532 img.onload = function() { 1533 img.style.opacity = ''; 1534 } 1535 1536 this.container.append(img); 1537 } 1538 _x = 0; 1539 __x = -2; 1540 } 1541 } 1542 1543 setMarkerToMap(marker) { 1544 1545 let markerX = Math.floor(marker.getX()); 1546 let markerY = Math.floor(marker.getY()); 1547 1548 let images = this.map.querySelectorAll('img[x="' + markerX + '"][y="' + markerY + '"]'); 1549 if (images.length == 0) { 1550 return; 1551 } 1552
1553 let tileOffsetX = (marker.getX() - markerX) * 256; 1554 let tileOffsetY = (marker.getY() - markerY) * 256; 1555 1556 for (let image of images) { 1557 if (this.map.querySelector('.js_map_marker[translateX="' + Math.floor(this.#getTranslateX(image) + tileOffsetX) + '"][translateY="' + Math.floor(this.#getTranslateY(image) + tileOffsetY) + '"]')) { 1558 continue; 1559 } 1560 1561 let markerEl = marker.build(this.#getTranslateX(image) + tileOffsetX, this.#getTranslateY(image) + tileOffsetY, markerX, markerY); 1562 this.container.append(markerEl); 1563 } 1564 } 1565 1566 setMarkersToMap() 1567 { 1568 let listOfCurrentMarkers = []; 1569 for (let marker of this.listOfAllMarkers) { 1570 marker.removeChildren(); 1571 listOfCurrentMarkers.push(Object.assign(Object.create(Object.getPrototypeOf(marker)), marker)); 1572 } 1573 1574 1575 let renderedMapEndX = this.renderedMapEndX + 1; 1576 let renderedMapEndY = this.renderedMapEndY + 1; 1577 1578 let startX = Math.min(this.renderedMapStartX, renderedMapEndX); 1579 let endX = Math.max(this.renderedMapStartX, renderedMapEndX); 1580 1581 let startY = Math.min(this.renderedMapStartY, renderedMapEndY); 1582 let endY = Math.max(this.renderedMapStartY, renderedMapEndY); 1583 1584 let start = this.toLatLon(startX, startY); 1585 let end = this.toLatLon(endX, endY); 1586 let startLat = Math.min(start.lat, end.lat); 1587 let endLat = Math.max(start.lat, end.lat); 1588 let startLon = Math.min(start.lon, end.lon); 1589 let endLon = Math.max(start.lon, end.lon); 1590 1591 listOfCurrentMarkers = listOfCurrentMarkers.filter(function(marker) { 1592 if (marker.isVisible() == false) { 1593 return false; 1594 } 1595 if (marker.getLat() < startLat || marker.getLat() > endLat) { 1596 return false; 1597 } 1598 if (marker.getLon() < startLon || marker.getLon() > endLon) { 1599 return false; 1600 } 1601 1602 let coords = this.toTileCoords(marker.getLat(), marker.getLon()); 1603 marker.setX(coords.x); 1604 marker.setY(coords.y); 1605 1606 return true; 1607 }.bind(this)); 1608 1609 function assignMarkersToClusters(markers) { 1610 1611 for (let markerOuter of markers) { 1612 1613 if (markerOuter.parent) { 1614 continue; 1615 } 1616 1617 for (let markerInner of markers) { 1618 1619 if (markerInner == markerOuter) { 1620 continue; 1621 } 1622 1623 let distance = Math.sqrt(Math.pow(Math.max(markerOuter.getX(), markerInner.getX()) - Math.min(markerOuter.getX(), markerInner.getX()), 2) + Math.pow(Math.max(markerOuter.getY(), markerInner.getY()) - Math.min(markerOuter.getY(), markerInner.getY()), 2));
1624 if (distance > this.clusteringDistance) { 1625 continue; 1626 } 1627 1628 if (markerInner.parentDistance && markerInner.parentDistance < distance) { 1629 continue; 1630 } 1631 1632 if (markerInner.parent) { 1633 markerInner.parent.removeChild(markerInner); 1634 } 1635 1636 markerInner.parent = markerOuter; 1637 markerInner.parentDistance = distance; 1638 1639 markerOuter.addChild(markerInner); 1640 } 1641 } 1642 1643 return markers; 1644 } 1645 1646 function clusterMarkers(markers) { 1647 for (let i = 0; i < markers.length; i++) { 1648 if (!markers[i].parent) { 1649 continue; 1650 } 1651 markers.splice(i, 1); 1652 i--; 1653 } 1654 1655 return markers; 1656 } 1657 1658 if (this.isClusteringDisabled == false && this.zoom != 18) { 1659 assignMarkersToClusters.bind(this)(listOfCurrentMarkers); 1660 clusterMarkers(listOfCurrentMarkers); 1661 } 1662 1663 for (let marker of listOfCurrentMarkers) { 1664 this.setMarkerToMap(marker); 1665 } 1666 } 1667 1668 centerToMarker(marker, callback = function() {}) { 1669 1670 if (!marker) { 1671 return; 1672 } 1673 1674 if (this.centeredToMarker == marker) { 1675 callback(); 1676 return; 1677 } 1678 1679 this.centeredToMarker = marker; 1680 1681 1682 for (let marker of this.listOfAllMarkers) { 1683 marker.removeActive(); 1684 } 1685 1686 marker.setActive(); 1687 1688 let markerEl = this.map.querySelector('.js_map_marker[marker-id="' + marker.getID() + '"]'); 1689 if (this.zoom >= 14 && markerEl && marker.getAmount() == 1) { 1690 1691 let dx = -(parseFloat(markerEl.getAttribute('x')) - (this.renderedMapCenterX)) * 256; 1692 let dy = -(parseFloat(markerEl.getAttribute('y')) - (this.renderedMapCenterY)) * 256; 1693 1694 let func = function onTransitionEnd() { 1695 this.map.querySelector('.js_map_container').style.transition = null; 1696 1697 this.map.querySelector('.js_map_container').removeEventListener('transitionend', func); 1698 1699 this.fillViewWithImages(); 1700 1701 callback(); 1702 } 1703 func = func.bind(this); 1704 this.map.querySelector('.js_map_container').addEventListener('transitionend', func); 1705 this.map.querySelector('.js_map_container').style.transition = 'transform 0.3s ease'; 1706 this.map.querySelector('.js_map_container').style.transform = 'translate(' + Math.round(dx) + 'px, ' + Math.round(dy) + 'px)'; 1707 this.map.querySelector('.js_map_container').setAttribute('translateX', Math.round(dx)); 1708 this.map.querySelector('.js_map_container').setAttribute('translateY', Math.round(dy)); 1709 1710 1711 let tileCoords = this.toTileCoords(marker.getLat(), marker.getLon()); 1712 let markerTile = this.map.querySelector('img[x="' + Math.floor(tileCoords.x) + '"][y="' + Math.floor(tileCoords.y) + '"]'); 1713 if (markerTile) { 1714 this.expandMap(markerTile); 1715 } 1716 return; 1717 } 1718 1719 this.requiredCenter = { 1720 zoom: 18, 1721 lat: marker.getLat(), 1722 lon: marker.getLon(), 1723 useOffset: true 1724 }; 1725 1726 if (!this.isMapRendered) { 1727 return; 1728 } 1729 1730 this.loadMap(JSON.parse(JSON.stringify(this.requiredCenter))); 1731 this.setMarkersToMap(); 1732 1733 callback(); 1734 } 1735 1736 getMarkerByID(ID) { 1737 for (let i = 0; i < this.listOfAllMarkers.length; i++) { 1738 if (this.listOfAllMarkers[i].getID() == ID) { 1739 return this.listOfAllMarkers[i]; 1740 } 1741 } 1742 } 1743 1744 getMarkers() { 1745 return this.listOfAllMarkers; 1746 } 1747 1748 fillViewWithImages() { 1749 1750 let percentage = (100 * this.getContentOfImagesInView()) / (this.mapRect.width * this.mapRect.height); 1751 if (percentage >= 99) { 1752 return false; 1753 } 1754 let i = 0; 1755 let imagesInView = []; 1756 while (percentage < 99) { 1757 1758 imagesInView = this.getImagesInView(); 1759 for (let image of imagesInView) { 1760 this.expandMap(image); 1761 } 1762 1763 imagesInView = this.getImagesInView(); 1764 let allTiles = this.map.querySelectorAll('.js_map_tile'); 1765 let result = Array.from(allTiles).filter(tile => !Array.from(imagesInView).find(_tile => _tile == tile)); 1766 for (let item of result) { 1767 item.remove(); 1768 } 1769 1770 this.map.querySelectorAll('.js_map_marker').forEach(marker => { 1771 if (!this.map.querySelector('.js_map_tile[x="' + Math.floor(parseInt(marker.getAttribute('x'))) + '"][y="' + Math.floor(parseInt(marker.getAttribute('y'))) + '"]')) { 1772 marker.remove(); 1773 } 1774 }); 1775 1776 1777 percentage = (100 * this.getContentOfImagesInView()) / (this.mapRect.width * this.mapRect.height); 1778 1779 if (i == 9) { 1780 break; 1781 } 1782 1783 i++; 1784 } 1785 1786 1787 this.renderedMapStartX = Infinity; 1788 this.renderedMapEndX = 0; 1789 this.renderedMapStartY = Infinity; 1790 this.renderedMapEndY = 0; 1791 imagesInView = this.getImagesInView(); 1792 for (let image of imagesInView) { 1793 if (image.getAttribute('x') > this.renderedMapEndX) { 1794 this.renderedMapEndX = image.getAttribute('x'); 1795 } 1796 1797 if (image.getAttribute('x') < this.renderedMapStartX) { 1798 this.renderedMapStartX = image.getAttribute('x'); 1799 } 1800 1801 if (image.getAttribute('y') > this.renderedMapEndY) { 1802 this.renderedMapEndY = image.getAttribute('y'); 1803 } 1804 1805 if (image.getAttribute('y') < this.renderedMapStartY) { 1806 this.renderedMapStartY = image.getAttribute('y'); 1807 } 1808 } 1809 1810 this.setMarkersToMap(); 1811 } 1812 1813 setOffsetY( offsetY ) { 1814 this.offsetY = offsetY; 1815 } 1816 1817 setOffsetX( offsetX ) { 1818 this.offsetX = offsetX; 1819 } 1820 1821 setZoom( zoom ) { 1822 this.requiredCenter.zoom = parseInt(zoom); 1823 } 1824 1825 setCurrentPosition() { 1826 1827 if (!('geolocation' in navigator)) { 1828 console.error('Geolocation je nedostupné!'); 1829 return; 1830 } 1831 1832 for (let i = 0; i < this.listOfAllMarkers.length; i++) { 1833 if (this.listOfAllMarkers[i].isCurrentPosition()) { 1834 this.centerToMarker(this.listOfAllMarkers[i]); 1835 return; 1836 } 1837 } 1838 1839 navigator.geolocation.getCurrentPosition(function(position) { 1840 let marker = this.addMarker(); 1841 marker.setLat(position.coords.latitude); 1842 marker.setLon(position.coords.longitude); 1843 marker.setCurrentPosition(); 1844 1845 if (typeof map_current_img == 'undefined') { 1846 console.error('PromÄnná "map_current_img" nebyla nelezena!'); 1847 this.centerToMarker(marker); 1848 } else { 1849 if (MapMarker.currentPositionMarkerHeight == null || MapMarker.currentPositionMarkerWidth == null) { 1850 let currentPositionMarker = document.createElement('img'); 1851 currentPositionMarker.onload = function() { 1852 MapMarker.currentPositionMarkerWidth = currentPositionMarker.naturalWi
1852dth; 1853 MapMarker.currentPositionMarkerHeight = currentPositionMarker.naturalHeight; 1854 this.centerToMarker(marker); 1855 }.bind(this); 1856 currentPositionMarker.onerror = function() { 1857 MapMarker.currentPositionMarkerHeight = 0; 1858 MapMarker.currentPositionMarkerWidth = 0; 1859 console.error('Map pin pro zobrazenà aktuálnà pozice se nepodaÅilo naÄÃst!'); 1860 this.centerToMarker(marker); 1861 }.bind(this) 1862 currentPositionMarker.src = map_current_img; 1863 } 1864 } 1865 }.bind(this)); 1866 } 1867 1868 disableCtrl() { 1869 this.disableCtrlBool = true; 1870 } 1871 1872 allowOneTouchScroll() { 1873 this.allowOneTouchScrollBool = true; 1874 } 1875 1876 showAllMarkers() { 1877 for (let marker of this.listOfAllMarkers) { 1878 marker.show(); 1879 } 1880 } 1881 1882 disableClustering() { 1883 this.isClusteringDisabled = true; 1884 } 1885 1886 showPopup(marker, content) { 1887 1888 let markerEl = this.map.querySelector('.js_map_marker[marker-id="' + marker.getID() + '"]'); 1889 if (!markerEl) { 1890 return; 1891 } 1892 1893 this.removePopups(); 1894 1895 let markerRect = markerEl.getBoundingClientRect(); 1896 let mapRect = this.map.getBoundingClientRect(); 1897 if (!markerRect.x || !markerRect.y) { 1898 return; 1899 } 1900 1901 let popup = document.createElement('div'); 1902 popup.classList.add('js_map_popup'); 1903 popup.style.left = (markerRect.x + (markerRect.width / 2) - (250 / 2)) + 'px'; 1904 popup.style.width = '250px'; 1905 popup.style.position = 'absolute'; 1906 popup.style.backgroundColor = 'white'; 1907 popup.style.boxShadow = '0 0 10px 0 rgba(0,0,0,0.25)'; 1908 popup.style.borderRadius = '6px'; 1909 popup.style.padding = '15px'; 1910 popup.style.zIndex = 100; 1911 popup.style.visibility = 'hidden'; 1912 popup.style.cursor = 'auto'; 1913 popup.innerHTML = content.innerHTML; 1914 1915 let caret = document.createElement('div'); 1916 caret.classList.add('js_map_popup_caret'); 1917 caret.style.margin = '0 auto'; 1918 caret.style.position = 'absolute'; 1919 caret.style.width = '15px'; 1920 caret.style.height = '15px'; 1921 caret.style.backgroundColor = '#ffffff'; 1922 caret.style.left = 0; 1923 caret.style.right = 0; 1924 caret.style.margin = '0 auto'; 1925 caret.style.bottom = '-7px'; 1926 caret.style.transform = 'rotate(45deg)'; 1927 popup.prepend(caret); 1928 this.map.append(popup); 1929 1930 let popupRect = popup.getBoundingClientRect(); 1931 1932 popup.style.top = (markerRect.y - mapRect.y - popupRect.height - 15) + 'px'; 1933 popup.style.visibility = 'visible'; 1934 } 1935 1936 removePopups() { 1937 this.map.querySelectorAll('.js_map_popup').forEach(popup => popup.remove()); 1938 } 1939} 1940 1941class MapMarker { 1942 1943 ID = 0; 1944 name = ''; 1945 lat = null; 1946 lon = null; 1947 x = null; 1948 y = null; 1949 1950 active = false; 1951 1952 centerOnClick = true; 1953 1954 image = ''; 1955 address = null; 1956 children = []; 1957 1958 visible = true; 1959 cluster = false; 1960 currentPosition = false; 1961 1962 parent = null; 1963 parentDistance = null; 1964 1965 onClickCallback = function() {}; 1966 1967 static markerWidth = 40; 1968 static markerHeight = 40; 1969 1970 static currentPositionMarkerWidth = null; 1971 static currentPositionMarkerHeight = null; 1972 1973 static clusterBorderColor = '#0a4b9d'; 1974 static clusterBackgroundColor = '#046aeb'; 1975 1976 constructor(name = '') { 1977 this.name = name; 1978 if (typeof mapPin == 'undefined') { 1979 console.error('Výchozà obrázek pro pin na mapÄ je uložen v promÄnné "mapPin", která ale nabyla nalezena!'); 1980 return; 1981 } 1982 this.image = mapPin; 1983 } 1984 1985 hide() { 1986 this.visible = false; 1987 } 1988 1989 show() { 1990 this.visible = true; 1991 } 1992 1993 isVisible() { 1994 return this.visible; 1995 } 1996 1997 isCluster() { 1998 return this.cluster; 1999 } 2000 2001 isCurrentPosition() { 2002 return this.currentPosition; 2003 } 2004 2005 getName() { 2006 return this.name; 2007 } 2008 2009 getLat() { 2010 return this.lat; 2011 } 2012 2013 getLon() { 2014 return this.lon; 2015 } 2016 2017 getColor() { 2018 return this.color; 2019 } 2020 2021 geticon() { 2022 return this.icon; 2023 } 2024 2025 setImage( image ) { 2026 this.image = image; 2027 } 2028 2029 setX( x ) { 2030 this.x = x; 2031 } 2032 2033 getX() { 2034 return this.x; 2035 } 2036 2037 getY() { 2038 return this.y; 2039 } 2040 2041 setY( y ) { 2042 this.y = y; 2043 } 2044 2045 setLat( lat ) { 2046 2047 if (lat == 0) { 2048 this.lat = null; 2049 return; 2050 } 2051 2052 this.lat = isNaN(parseFloat(lat)) ? null : parseFloat(lat); 2053 } 2054 2055 setLon( lon ) { 2056
2057 if (lon == 0) { 2058 this.lon = null; 2059 return; 2060 } 2061 2062 this.lon = isNaN(parseFloat(lon)) ? null : parseFloat(lon); 2063 } 2064 2065 setAddress( address ) { 2066 this.address = address; 2067 } 2068 2069 getIcon() { 2070 return this.icon; 2071 } 2072 2073 getAddress() { 2074 return this.address; 2075 } 2076 2077 getAmount() { 2078 return this.children.length + 1; 2079 } 2080 2081 setID( ID ) { 2082 this.ID = parseInt(ID); 2083 } 2084 2085 getID() { 2086 return this.ID; 2087 } 2088 2089 addChild( marker ) { 2090 this.children.push(Object.assign(Object.create(Object.getPrototypeOf(marker)), marker)); 2091 } 2092 2093 removeChild( marker ) { 2094 for (let [i, child] of this.children.entries()) { 2095 if (child.getID() == marker.getID()) { 2096 this.children.splice(i, 1); 2097 } 2098 } 2099 } 2100 2101 getChildren() { 2102 return this.children; 2103 } 2104 2105 getImage() { 2106 return this.image; 2107 } 2108 2109 removeChildren() { 2110 this.children = []; 2111 } 2112 2113 setActive() { 2114 this.active = true; 2115 } 2116 2117 removeActive() { 2118 this.active = false; 2119 } 2120 2121 disableCenterOnClick() { 2122 this.centerOnClick = false; 2123 } 2124 2125 setCurrentPosition() { 2126 this.currentPosition = true; 2127 if (typeof map_current_img == 'undefined') { 2128 return; 2129 } 2130 this.image = map_current_img; 2131 } 2132 2133 onClick(callback) { 2134 this.onClickCallback = callback; 2135 } 2136 2137 build(x, y, tileX, tileY) { 2138 let marker = document.createElement('div'); 2139 marker.classList.add('js_map_marker'); 2140 marker.style.position = 'absolute'; 2141 marker.style.transform = 'translate(' + Math.floor(x - MapMarker.markerWidth / 2) + 'px, ' + Math.floor(y - MapMarker.markerHeight) + 'px)'; 2142 marker.style.zIndex = 10; 2143 marker.style.cursor = 'pointer'; 2144 marker.style.display = 'flex'; 2145 marker.style.alignItems = 'center'; 2146 marker.style.justifyContent = 'center'; 2147 marker.setAttribute('marker-id', this.getID()); 2148 marker.setAttribute('translateX', Math.floor(x)); 2149 marker.setAttribute('translateY', Math.floor(y)); 2150 marker.setAttribute('x', this.getX()); 2151 marker.setAttribute('y', this.getY()); 2152 2153 if (this.active) { 2154 marker.classList.add('js_map_marker_active'); 2155 } 2156 2157 if (this.getAmount() == 1) { 2158 let img = document.createElement('img'); 2159 img.src = this.getImage(); 2160 img.width = MapMarker.markerWidth; 2161 img.height = MapMarker.markerHeight; 2162 img.style.pointerEvents = 'none'; 2163 img.role = 'presentation'; 2164 marker.append(img); 2165 2166 if (this.isCurrentPosition()) { 2167 marker.style.transform = 'translate(' + Math.floor(x - MapMarker.currentPositionMarkerWidth / 2) + 'px, ' + Math.floor(y - MapMarker.currentPositionMarkerHeight / 2) + 'px)'; 2168 img.width = MapMarker.currentPositionMarkerWidth; 2169 img.height = MapMarker.currentPositionMarkerHeight; 2170 } 2171 } 2172 2173 if (this.getAmount() > 1) { 2174 let amouth = document.createElement('span'); 2175 amouth.textContent = this.getAmount(); 2176 amouth.style.color = 'white'; 2177 amouth.style.fontSize = '18px'; 2178 amouth.style.fontWeight = 'bold'; 2179 2180 let size = 33; 2181 if (this.getAmount() > 9 && this.getAmount() < 100) { 2182 size = 40; 2183 } else if (this.getAmount() > 99) { 2184 size = 45; 2185 } 2186 2187 marker.classList.add('js_map_marker_cluster'); 2188 marker.style.backgroundColor = MapMarker.clusterBackgroundColor; 2189 marker.style.border = '3px solid ' + MapMarker.clusterBorderColor; 2190 marker.style.width = size + 'px'; 2191 marker.style.height = size + 'px'; 2192 marker.style.borderRadius = '100%'; 2193 marker.style.paddingTop = '3px'; 2194 marker.style.transform = 'translate(' + Math.floor(x - size / 2) + 'px, ' + Math.floor(y - size / 2) + 'px)'; 2195 marker.setAttribute('translateX', Math.floor(x)); 2196 marker.setAttribute('translateY', Math.floor(y)); 2197 marker.append(amouth); 2198 } 2199 2200 2201 return marker; 2202 } 2203}
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.