PageSourceSearch

https://eshop.tecampcv.cz/template/eshop1/js/maps.js?v=1778088688

js tecampcv.cz collected 2026-10-07 23:30:12 UTC 61,743 bytes, 2,203 lines download raw bytes

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.