adt-js-components 1.9.6 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. package/src/Map/index.js +256 -221
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adt-js-components",
3
- "version": "1.9.6",
3
+ "version": "1.10.0",
4
4
  "description": "JavaScript components for Nette framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -14,6 +14,7 @@
14
14
  "select2": "^4.0.13",
15
15
  "select2-bootstrap-5-theme": "^1.1.1",
16
16
  "autonumeric": "^4.6.0",
17
+ "@here/flexpolyline": "^0.1.0",
17
18
  "flatpickr": "^4.6.3",
18
19
  "script-loader": "^0.7.2",
19
20
  "timepicker": "^1.13.4",
package/src/Map/index.js CHANGED
@@ -3,10 +3,12 @@ import 'leaflet/dist/leaflet.css';
3
3
  import "leaflet.markercluster";
4
4
  import "leaflet.markercluster/dist/MarkerCluster.css";
5
5
  import "leaflet.markercluster/dist/MarkerCluster.Default.css";
6
+ import { decode as decodeFlexPolyline } from '@here/flexpolyline';
6
7
  import {RouteSetting, defaultRouteSettings} from './route.types.js';
7
8
 
8
9
  let siteKey;
9
10
  let markerImg;
11
+ let mapProvider;
10
12
  const svgCache = new Map();
11
13
  const mapInstances = new WeakMap();
12
14
  const selectedMarkers = new WeakMap();
@@ -25,9 +27,15 @@ const DEPOT_TYPE = {
25
27
  END: 'depot-end'
26
28
  };
27
29
 
30
+ const MAP_PROVIDER = {
31
+ MAPY_CZ: 'mapy.cz',
32
+ HERE: 'here',
33
+ };
34
+
28
35
  async function run(options) {
29
36
  siteKey = options.siteKey;
30
37
  markerImg = options.markerImg;
38
+ mapProvider = options.mapProvider ?? MAP_PROVIDER.MAPY_CZ;
31
39
 
32
40
  function applyEventHandlers(el) {
33
41
  const {
@@ -47,11 +55,11 @@ async function run(options) {
47
55
  onAfterRouteCalculation = null,
48
56
  } = JSON.parse(el.dataset.adtMap);
49
57
 
50
- /** @type {RouteSetting} */
51
58
  const routeSettings = {
52
59
  ...defaultRouteSettings,
53
60
  ...route,
54
- customMarkers: customMarkers,
61
+ customMarkers,
62
+ mapProvider,
55
63
  };
56
64
 
57
65
  if (mapInstances.has(el)) {
@@ -71,28 +79,39 @@ async function run(options) {
71
79
  onBeforeRouteCalculationMap.set(map, onBeforeRouteCalculation);
72
80
  onAfterRouteCalculationMap.set(map, onAfterRouteCalculation);
73
81
 
74
- L.tileLayer("https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=" + siteKey, {
75
- minZoom: 0,
76
- maxZoom: 19,
77
- attribution: '<a href="https://api.mapy.cz/copyright" target="_blank">&copy; Seznam.cz a.s. a další</a>',
78
- }).addTo(map);
79
-
80
- const LogoControl = L.Control.extend({
81
- options: {
82
- position: 'bottomleft',
83
- },
84
- onAdd: function (map) {
85
- const container = L.DomUtil.create('div');
86
- const link = L.DomUtil.create('a', '', container);
87
- link.setAttribute('href', 'http://mapy.cz/');
88
- link.setAttribute('target', '_blank');
89
- link.innerHTML = '<img src="https://api.mapy.cz/img/api/logo.svg" />';
90
- L.DomEvent.disableClickPropagation(link);
91
-
92
- return container;
93
- },
94
- });
95
- new LogoControl().addTo(map);
82
+ if (mapProvider === MAP_PROVIDER.HERE) {
83
+ L.tileLayer(
84
+ `https://maps.hereapi.com/v3/base/mc/{z}/{x}/{y}/png8?style=explore.day&apiKey=${siteKey}`,
85
+ {
86
+ minZoom: 0,
87
+ maxZoom: 20,
88
+ attribution: '&copy; <a href="https://www.here.com" target="_blank">HERE</a>',
89
+ }
90
+ ).addTo(map);
91
+ } else {
92
+ L.tileLayer(
93
+ `https://api.mapy.cz/v1/maptiles/basic/256/{z}/{x}/{y}?apikey=${siteKey}`,
94
+ {
95
+ minZoom: 0,
96
+ maxZoom: 19,
97
+ attribution: '<a href="https://api.mapy.cz/copyright" target="_blank">&copy; Seznam.cz a.s. a další</a>',
98
+ }
99
+ ).addTo(map);
100
+
101
+ const LogoControl = L.Control.extend({
102
+ options: { position: 'bottomleft' },
103
+ onAdd: function () {
104
+ const container = L.DomUtil.create('div');
105
+ const link = L.DomUtil.create('a', '', container);
106
+ link.setAttribute('href', 'http://mapy.cz/');
107
+ link.setAttribute('target', '_blank');
108
+ link.innerHTML = '<img src="https://api.mapy.cz/img/api/logo.svg" />';
109
+ L.DomEvent.disableClickPropagation(link);
110
+ return container;
111
+ },
112
+ });
113
+ new LogoControl().addTo(map);
114
+ }
96
115
 
97
116
  if (position.length) {
98
117
  if (zoom) {
@@ -100,6 +119,13 @@ async function run(options) {
100
119
  } else {
101
120
  map.fitBounds([position]);
102
121
  }
122
+ } else if (!markers.length) {
123
+ const depot = routeSettings.startPoint ?? routeSettings.endPoint;
124
+ if (depot) {
125
+ map.setView([depot.lat, depot.lon], zoom ?? 12);
126
+ } else {
127
+ map.setView([50.0755, 14.4378], 7);
128
+ }
103
129
  }
104
130
 
105
131
  map.scrollWheelZoom.disable();
@@ -109,7 +135,6 @@ async function run(options) {
109
135
  map.dragging.disable();
110
136
  }
111
137
 
112
-
113
138
  if (selectable) {
114
139
  enableRectangleSelection(map, onSelectionChange, showSelectionOrder);
115
140
  }
@@ -142,10 +167,7 @@ async function run(options) {
142
167
  if (markers.length) {
143
168
  addMarkers(map, markers, markerOptions, customMarkerOptions, position, selectable, onSelectionChange, showSelectionOrder, markerInfoCallback);
144
169
  } else if (showDefaultMarker) {
145
- createMarker({
146
- id: 0,
147
- position: position
148
- }, markerOptions, customMarkerOptions, null, selectable, onSelectionChange, map, showSelectionOrder, markerInfoCallback).addTo(map);
170
+ createMarker({ id: 0, position }, markerOptions, customMarkerOptions, null, selectable, onSelectionChange, map, showSelectionOrder, markerInfoCallback).addTo(map);
149
171
  }
150
172
  };
151
173
  } else {
@@ -154,10 +176,7 @@ async function run(options) {
154
176
  if (markers.length) {
155
177
  addMarkers(map, markers, markerOptions, customMarkerOptions, position, selectable, onSelectionChange, showSelectionOrder, markerInfoCallback);
156
178
  } else if (showDefaultMarker) {
157
- createMarker({
158
- id: 0,
159
- position: position
160
- }, markerOptions, customMarkerOptions, null, selectable, onSelectionChange, map, showSelectionOrder, markerInfoCallback).addTo(map);
179
+ createMarker({ id: 0, position }, markerOptions, customMarkerOptions, null, selectable, onSelectionChange, map, showSelectionOrder, markerInfoCallback).addTo(map);
161
180
  }
162
181
  }
163
182
  };
@@ -183,14 +202,10 @@ async function run(options) {
183
202
  if (node.hasAttribute("data-adt-map")) {
184
203
  applyEventHandlers(node);
185
204
  }
186
-
187
- node.querySelectorAll?.('[data-adt-map]').forEach(el => {
188
- applyEventHandlers(el);
189
- });
205
+ node.querySelectorAll?.('[data-adt-map]').forEach(el => applyEventHandlers(el));
190
206
  }
191
207
  });
192
208
  }
193
-
194
209
  if (mutation.type === "attributes" && mutation.attributeName === "data-adt-map") {
195
210
  applyEventHandlers(mutation.target);
196
211
  }
@@ -204,9 +219,7 @@ async function run(options) {
204
219
  attributeFilter: ["data-adt-map"]
205
220
  });
206
221
 
207
- document.querySelectorAll('[data-adt-map]').forEach(function (el) {
208
- applyEventHandlers(el);
209
- });
222
+ document.querySelectorAll('[data-adt-map]').forEach(el => applyEventHandlers(el));
210
223
  }
211
224
 
212
225
  function enableRectangleSelection(map, onSelectionChange, showSelectionOrder) {
@@ -218,13 +231,7 @@ function enableRectangleSelection(map, onSelectionChange, showSelectionOrder) {
218
231
  if (e.originalEvent.ctrlKey && e.originalEvent.shiftKey) {
219
232
  isDrawing = true;
220
233
  startPoint = e.latlng;
221
-
222
- rectangle = L.rectangle([startPoint, startPoint], {
223
- color: '#3388ff',
224
- weight: 2,
225
- fillOpacity: 0.1
226
- }).addTo(map);
227
-
234
+ rectangle = L.rectangle([startPoint, startPoint], { color: '#3388ff', weight: 2, fillOpacity: 0.1 }).addTo(map);
228
235
  map.dragging.disable();
229
236
  e.originalEvent.preventDefault();
230
237
  }
@@ -246,10 +253,8 @@ function enableRectangleSelection(map, onSelectionChange, showSelectionOrder) {
246
253
  const markerMap = markerInstances.get(map);
247
254
  const selectedInArea = [];
248
255
 
249
- markerMap.forEach((markerInstance, markerId) => {
250
- const latLng = markerInstance.getLatLng();
251
-
252
- if (bounds.contains(latLng)) {
256
+ markerMap.forEach((markerInstance) => {
257
+ if (bounds.contains(markerInstance.getLatLng())) {
253
258
  selectedInArea.push(markerInstance);
254
259
  }
255
260
  });
@@ -268,9 +273,7 @@ function enableRectangleSelection(map, onSelectionChange, showSelectionOrder) {
268
273
 
269
274
  if (onSelectionChange && window[onSelectionChange]) {
270
275
  const order = selectionOrder.get(map);
271
- const orderedIds = Array.from(order.entries())
272
- .sort((a, b) => a[1] - b[1])
273
- .map(entry => entry[0]);
276
+ const orderedIds = Array.from(order.entries()).sort((a, b) => a[1] - b[1]).map(e => e[0]);
274
277
  window[onSelectionChange](orderedIds);
275
278
  }
276
279
 
@@ -290,21 +293,12 @@ function addMarkers(map, markers, options, selectedOptions, position, selectable
290
293
  spiderfyOnMaxZoom: true,
291
294
  showCoverageOnHover: false,
292
295
  zoomToBoundsOnClick: true,
293
-
294
296
  iconCreateFunction: function (cluster) {
295
297
  const childCount = cluster.getChildCount();
296
- let c = ' marker-cluster-';
297
- if (childCount < 10) {
298
- c += 'small';
299
- } else if (childCount < 100) {
300
- c += 'medium';
301
- } else {
302
- c += 'large';
303
- }
304
-
298
+ const c = childCount < 10 ? 'small' : childCount < 100 ? 'medium' : 'large';
305
299
  return new L.DivIcon({
306
300
  html: '<div><span>' + childCount + '</span></div>',
307
- className: 'marker-cluster' + c,
301
+ className: 'marker-cluster marker-cluster-' + c,
308
302
  iconSize: new L.Point(40, 40)
309
303
  });
310
304
  }
@@ -326,7 +320,6 @@ function addMarkers(map, markers, options, selectedOptions, position, selectable
326
320
  iconAnchor: [markerImage.width / 2, markerImage.height]
327
321
  });
328
322
  createMarker(marker, markerOptions, selectedOptions, cluster, selectable, onSelectionChange, map, showSelectionOrder, markerInfoCallback);
329
-
330
323
  loadedCount++;
331
324
  if (loadedCount === totalMarkers) {
332
325
  checkAndApplyPreselection(map, markers, showSelectionOrder, onSelectionChange, selectable);
@@ -364,11 +357,6 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
364
357
  mapMarker._selectedIcon = selectedOptions.icon;
365
358
  mapMarker._markerData = marker;
366
359
 
367
- const markerElement = mapMarker.getElement();
368
- if (markerElement && markerElement.parentElement) {
369
- markerElement.parentElement.style.pointerEvents = 'visible';
370
- }
371
-
372
360
  if (map) {
373
361
  const markers = markerInstances.get(map);
374
362
  markers.set(marker.id, mapMarker);
@@ -377,7 +365,6 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
377
365
  const el = mapMarker.getElement();
378
366
  if (el) {
379
367
  el.style.cursor = 'pointer';
380
-
381
368
  const parent = el.parentElement;
382
369
  if (parent) {
383
370
  parent.style.pointerEvents = 'all';
@@ -390,14 +377,14 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
390
377
 
391
378
  if (selectable && map) {
392
379
  mapMarker.on('click', function (e) {
393
- if (e.originalEvent.shiftKey && markerInfoCallback && window[markerInfoCallback]) {
380
+ if (e.originalEvent.shiftKey && !e.originalEvent.ctrlKey && markerInfoCallback && window[markerInfoCallback]) {
394
381
  window[markerInfoCallback](marker);
395
382
  L.DomEvent.stopPropagation(e);
383
+ L.DomEvent.preventDefault(e);
396
384
  return;
397
385
  }
398
386
 
399
387
  const selected = selectedMarkers.get(map);
400
-
401
388
  if (selected.has(marker.id)) {
402
389
  deselectMarker(mapMarker, map, showSelectionOrder);
403
390
  } else {
@@ -406,9 +393,7 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
406
393
 
407
394
  if (onSelectionChange && window[onSelectionChange]) {
408
395
  const order = selectionOrder.get(map);
409
- const orderedIds = Array.from(order.entries())
410
- .sort((a, b) => a[1] - b[1])
411
- .map(entry => entry[0]);
396
+ const orderedIds = Array.from(order.entries()).sort((a, b) => a[1] - b[1]).map(e => e[0]);
412
397
  window[onSelectionChange](orderedIds);
413
398
  }
414
399
 
@@ -416,11 +401,9 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
416
401
  if (settings && settings.enabled) {
417
402
  calculateRoute(map);
418
403
  }
419
-
420
404
  if (originalCallback && window[originalCallback]) {
421
405
  window[originalCallback](e);
422
406
  }
423
-
424
407
  L.DomEvent.stopPropagation(e);
425
408
  });
426
409
  } else if (originalCallback) {
@@ -443,9 +426,7 @@ function createMarker(marker, options, selectedOptions, cluster = null, selectab
443
426
  async function selectMarker(marker, map, showSelectionOrder) {
444
427
  const selected = selectedMarkers.get(map);
445
428
  const order = selectionOrder.get(map);
446
-
447
429
  selected.add(marker.options.id);
448
-
449
430
  const maxOrder = order.size > 0 ? Math.max(...order.values()) : 0;
450
431
  const newOrder = maxOrder + 1;
451
432
  order.set(marker.options.id, newOrder);
@@ -453,14 +434,13 @@ async function selectMarker(marker, map, showSelectionOrder) {
453
434
  if (marker._selectedIcon) {
454
435
  marker.setIcon(marker._selectedIcon);
455
436
  }
456
-
457
437
  const icon = marker.getElement();
458
438
  if (icon) {
459
439
  icon.classList.add('marker-selected');
460
440
  }
461
441
 
462
442
  if (showSelectionOrder) {
463
- const newIcon = await createMarkerIcon(map, marker.options.icon.options.iconUrl, newOrder)
443
+ const newIcon = await createMarkerIcon(map, marker.options.icon.options.iconUrl, newOrder);
464
444
  marker.setIcon(newIcon);
465
445
  }
466
446
  }
@@ -472,11 +452,7 @@ function deselectMarker(marker, map, showSelectionOrder) {
472
452
 
473
453
  selected.delete(marker.options.id);
474
454
  order.delete(marker.options.id);
475
- order.forEach((value, key) => {
476
- if (value > removedOrder) {
477
- order.set(key, value - 1);
478
- }
479
- });
455
+ order.forEach((value, key) => { if (value > removedOrder) order.set(key, value - 1); });
480
456
 
481
457
  if (showSelectionOrder) {
482
458
  updateMarkerOrderDisplay(map, marker, null, false);
@@ -500,22 +476,16 @@ function deselectMarker(marker, map, showSelectionOrder) {
500
476
  }
501
477
  }
502
478
 
503
-
504
479
  async function updateMarkerOrderDisplay(map, marker, orderNumber, isSelected, color = null) {
505
480
  if (!marker._normalIcon || !marker._normalIcon.options) return;
506
-
507
- const iconUrl = isSelected && marker._selectedIcon ?
508
- marker._selectedIcon.options.iconUrl :
509
- marker._normalIcon.options.iconUrl;
481
+ const iconUrl = isSelected && marker._selectedIcon ? marker._selectedIcon.options.iconUrl : marker._normalIcon.options.iconUrl;
510
482
  const newIcon = await createMarkerIcon(map, iconUrl, orderNumber, color);
511
-
512
483
  marker.setIcon(newIcon);
513
484
  }
514
485
 
515
486
  async function createMarkerIcon(map, iconUrl, orderNumber, color = null) {
516
487
  let inlineStyle = null;
517
488
  const settings = routeSettingsMap.get(map);
518
-
519
489
  if ((settings && settings.enabled) || color) {
520
490
  inlineStyle = `--marker-fill: ${color ?? settings.color};`;
521
491
  }
@@ -546,12 +516,10 @@ function applyPreselectedMarkers(map, markersData, showSelectionOrder, onSelecti
546
516
  const markerMap = markerInstances.get(map);
547
517
  const order = selectionOrder.get(map);
548
518
  const preselected = markersData
549
- .filter(m => {
550
- return m.selected === true;
551
- })
519
+ .filter(m => m.selected === true)
552
520
  .sort((a, b) => (a.selectionOrder || 0) - (b.selectionOrder || 0));
553
521
 
554
- preselected.forEach((markerData, index) => {
522
+ preselected.forEach((markerData) => {
555
523
  const markerInstance = markerMap.get(markerData.id);
556
524
  if (markerInstance) {
557
525
  selectMarker(markerInstance, map, showSelectionOrder);
@@ -561,10 +529,7 @@ function applyPreselectedMarkers(map, markersData, showSelectionOrder, onSelecti
561
529
  });
562
530
 
563
531
  if (onSelectionChange && window[onSelectionChange] && order.size > 0) {
564
- const orderedIds = Array.from(order.entries())
565
- .sort((a, b) => a[1] - b[1])
566
- .map(entry => entry[0]);
567
-
532
+ const orderedIds = Array.from(order.entries()).sort((a, b) => a[1] - b[1]).map(e => e[0]);
568
533
  window[onSelectionChange](orderedIds);
569
534
  }
570
535
 
@@ -586,24 +551,22 @@ async function calculateRoute(map) {
586
551
  routePolylines.set(map, null);
587
552
  }
588
553
 
589
- if (!routeSettings || !routeSettings.enabled) {
590
- return;
591
- }
554
+ if (!routeSettings || !routeSettings.enabled) return;
592
555
 
593
556
  const hasCustomStart = routeSettings.startPoint !== null && routeSettings.startPoint !== undefined;
594
557
  const hasCustomEnd = routeSettings.endPoint !== null && routeSettings.endPoint !== undefined;
595
558
 
596
- if (hasCustomStart) addDepotMarker(map, routeSettings.startPoint, DEPOT_TYPE.START);
597
- if (hasCustomEnd) addDepotMarker(map, routeSettings.endPoint, DEPOT_TYPE.END);
559
+ if (hasCustomStart) {
560
+ addDepotMarker(map, routeSettings.startPoint, DEPOT_TYPE.START);
561
+ }
562
+ if (hasCustomEnd) {
563
+ addDepotMarker(map, routeSettings.endPoint, DEPOT_TYPE.END);
564
+ }
598
565
 
599
566
  if (!selectedSet || selectedSet.size === 0) return;
600
567
 
601
- const orderedIds = Array.from(order.entries())
602
- .sort((a, b) => a[1] - b[1])
603
- .map(entry => entry[0]);
604
- const routeMarkers = orderedIds
605
- .map(id => markers.find(m => m.id === id))
606
- .filter(m => m !== undefined);
568
+ const orderedIds = Array.from(order.entries()).sort((a, b) => a[1] - b[1]).map(e => e[0]);
569
+ const routeMarkers = orderedIds.map(id => markers.find(m => m.id === id)).filter(m => m !== undefined);
607
570
 
608
571
  if (routeMarkers.length < 2) return;
609
572
 
@@ -612,86 +575,25 @@ async function calculateRoute(map) {
612
575
  window[beforeCallback]();
613
576
  }
614
577
 
615
- let startPoint = hasCustomStart ? routeSettings.startPoint : routeMarkers[0].position;
616
- let endPoint = hasCustomEnd ? routeSettings.endPoint : routeMarkers[routeMarkers.length - 1].position;
578
+ const isHere = routeSettings.mapProvider === MAP_PROVIDER.HERE;
579
+ const startPoint = hasCustomStart ? routeSettings.startPoint : routeMarkers[0].position;
580
+ const endPoint = hasCustomEnd ? routeSettings.endPoint : routeMarkers[routeMarkers.length - 1].position;
617
581
 
618
- const WAYPOINTS_LIMIT = 15;
619
582
  const allCoords = [];
620
583
  const allParts = [];
621
- let totalDuration = 0;
622
- let totalLength = 0;
623
584
 
624
585
  try {
625
- if (routeMarkers.length <= WAYPOINTS_LIMIT) {
626
- const params = new URLSearchParams({
627
- start: `${startPoint.lon},${startPoint.lat}`,
628
- end: `${endPoint.lon},${endPoint.lat}`,
629
- routeType: routeSettings.routeType,
630
- apikey: siteKey
631
- });
632
- routeMarkers.forEach(m => params.append('waypoints', `${m.position.lon},${m.position.lat}`));
633
-
634
- const response = await fetch(`https://api.mapy.cz/v1/routing/route?${params.toString()}`);
635
- const data = await response.json();
636
-
637
- if (data.geometry?.geometry?.coordinates) {
638
- allCoords.push(...data.geometry.geometry.coordinates.map(c => [c[1], c[0]]));
639
- }
640
- if (data.parts) {
641
- allParts.push(...data.parts);
642
- }
643
- if (data.duration) {
644
- totalDuration += data.duration;
645
- }
646
- if (data.length) {
647
- totalLength += data.length;
648
- }
586
+ if (isHere) {
587
+ await calculateRouteHere({ routeMarkers, startPoint, endPoint, routeSettings, allCoords, allParts });
649
588
  } else {
650
- const chunks = [];
651
- for (let i = 0; i < routeMarkers.length; i += WAYPOINTS_LIMIT) {
652
- chunks.push(routeMarkers.slice(i, i + WAYPOINTS_LIMIT));
653
- }
654
-
655
- for (let i = 0; i < chunks.length; i++) {
656
- const chunk = chunks[i];
657
- const isFirstChunk = i === 0;
658
- const isLastChunk = i === chunks.length - 1;
659
-
660
- const chunkStart = isFirstChunk ? startPoint : chunks[i - 1][chunks[i - 1].length - 1].position;
661
- const chunkEnd = isLastChunk ? endPoint : chunk[chunk.length - 1].position;
662
-
663
- const params = new URLSearchParams({
664
- start: `${chunkStart.lon},${chunkStart.lat}`,
665
- end: `${chunkEnd.lon},${chunkEnd.lat}`,
666
- routeType: routeSettings.routeType,
667
- apikey: siteKey
668
- });
669
-
670
- const markersToAdd = isFirstChunk ? chunk : chunk.slice(1);
671
- markersToAdd.forEach(m => params.append('waypoints', `${m.position.lon},${m.position.lat}`));
672
-
673
- const response = await fetch(`https://api.mapy.cz/v1/routing/route?${params.toString()}`);
674
- const data = await response.json();
675
-
676
- if (data.geometry?.geometry?.coordinates) {
677
- allCoords.push(...data.geometry.geometry.coordinates.map(c => [c[1], c[0]]));
678
- }
679
- if (data.parts) {
680
- allParts.push(...data.parts);
681
- }
682
- if (data.duration) {
683
- totalDuration += data.duration;
684
- }
685
- if (data.length) {
686
- totalLength += data.length;
687
- }
688
- }
589
+ await calculateRouteMapy({ routeMarkers, startPoint, endPoint, routeSettings, allCoords, allParts });
689
590
  }
690
591
  } catch (error) {
691
592
  console.error('Error calculating route:', error);
692
593
  const afterCallback = onAfterRouteCalculationMap.get(map);
693
- if (afterCallback && window[afterCallback]) window[afterCallback]({}, 0, 0);
694
-
594
+ if (afterCallback && window[afterCallback]) {
595
+ window[afterCallback]({}, 0, 0);
596
+ }
695
597
  return;
696
598
  }
697
599
 
@@ -704,20 +606,103 @@ async function calculateRoute(map) {
704
606
  routePolylines.set(map, polyline);
705
607
  }
706
608
 
707
- // @type {TRouteParts}
609
+ const totalDuration = allParts.reduce((s, p) => s + (p.duration || 0), 0);
610
+ const totalLength = allParts.reduce((s, p) => s + (p.length || 0), 0);
611
+
708
612
  const routeParts = {};
709
613
  allParts.forEach((part, index) => {
710
614
  if (index < orderedIds.length) {
711
- const markerId = orderedIds[index];
712
- routeParts[markerId] = {
713
- duration: part.duration,
714
- length: part.length,
715
- };
615
+ routeParts[orderedIds[index]] = { duration: part.duration, length: part.length };
716
616
  }
717
617
  });
718
618
 
719
619
  const afterCallback = onAfterRouteCalculationMap.get(map);
720
- if (afterCallback && window[afterCallback]) window[afterCallback](routeParts, totalLength, totalDuration);
620
+ if (afterCallback && window[afterCallback]) {
621
+ window[afterCallback](routeParts, totalLength, totalDuration);
622
+ }
623
+ }
624
+
625
+ async function calculateRouteMapy({ routeMarkers, startPoint, endPoint, routeSettings, allCoords, allParts }) {
626
+ const WAYPOINTS_LIMIT = 15;
627
+
628
+ const chunks = [];
629
+ for (let i = 0; i < routeMarkers.length; i += WAYPOINTS_LIMIT) chunks.push(routeMarkers.slice(i, i + WAYPOINTS_LIMIT));
630
+
631
+ for (let i = 0; i < chunks.length; i++) {
632
+ const chunk = chunks[i];
633
+ const isFirstChunk = i === 0;
634
+ const isLastChunk = i === chunks.length - 1;
635
+ const chunkStart = isFirstChunk ? startPoint : chunks[i - 1][chunks[i - 1].length - 1].position;
636
+ const chunkEnd = isLastChunk ? endPoint : chunk[chunk.length - 1].position;
637
+
638
+ const params = new URLSearchParams({
639
+ start: `${chunkStart.lon},${chunkStart.lat}`,
640
+ end: `${chunkEnd.lon},${chunkEnd.lat}`,
641
+ routeType: routeSettings.routeType,
642
+ apikey: siteKey
643
+ });
644
+
645
+ const markersToAdd = isFirstChunk ? chunk : chunk.slice(1);
646
+ markersToAdd.forEach(m => params.append('waypoints', `${m.position.lon},${m.position.lat}`));
647
+
648
+ const response = await fetch(`https://api.mapy.cz/v1/routing/route?${params.toString()}`);
649
+ const data = await response.json();
650
+
651
+ if (data.geometry?.geometry?.coordinates) {
652
+ allCoords.push(...data.geometry.geometry.coordinates.map(c => [c[1], c[0]]));
653
+ }
654
+ if (data.parts) {
655
+ allParts.push(...data.parts);
656
+ }
657
+ }
658
+ }
659
+
660
+ async function calculateRouteHere({ routeMarkers, startPoint, endPoint, routeSettings, allCoords, allParts }) {
661
+ const WAYPOINTS_LIMIT = 98;
662
+
663
+ const chunks = [];
664
+ for (let i = 0; i < routeMarkers.length; i += WAYPOINTS_LIMIT) chunks.push(routeMarkers.slice(i, i + WAYPOINTS_LIMIT));
665
+
666
+ for (let i = 0; i < chunks.length; i++) {
667
+ const chunk = chunks[i];
668
+ const isFirstChunk = i === 0;
669
+ const isLastChunk = i === chunks.length - 1;
670
+ const chunkStart = isFirstChunk ? startPoint : chunks[i - 1][chunks[i - 1].length - 1].position;
671
+ const chunkEnd = isLastChunk ? endPoint : chunk[chunk.length - 1].position;
672
+
673
+ const params = new URLSearchParams({
674
+ origin: `${chunkStart.lat},${chunkStart.lon}`,
675
+ destination: `${chunkEnd.lat},${chunkEnd.lon}`,
676
+ transportMode: routeSettings.routeType,
677
+ return: 'polyline,summary',
678
+ apiKey: siteKey,
679
+ });
680
+
681
+ if (routeSettings.departureTime) {
682
+ params.set('departureTime', routeSettings.departureTime);
683
+ }
684
+
685
+ const viaMarkers = isFirstChunk ? chunk : chunk.slice(1);
686
+ viaMarkers.forEach(m => params.append('via', `${m.position.lat},${m.position.lon}`));
687
+
688
+ const response = await fetch(`https://router.hereapi.com/v8/routes?${params.toString()}`);
689
+ const data = await response.json();
690
+
691
+ if (!data.routes || data.routes.length === 0) {
692
+ console.warn('HERE routing: žádná trasa nenalezena', data);
693
+ continue;
694
+ }
695
+
696
+ for (const section of data.routes[0].sections) {
697
+ if (section.polyline) {
698
+ const decoded = decodeFlexPolyline(section.polyline);
699
+ allCoords.push(...decoded.polyline);
700
+ }
701
+ if (section.summary) {
702
+ allParts.push({ duration: section.summary.duration, length: section.summary.length });
703
+ }
704
+ }
705
+ }
721
706
  }
722
707
 
723
708
  function addDepotMarker(map, position, depotType) {
@@ -728,53 +713,35 @@ function addDepotMarker(map, position, depotType) {
728
713
  const img = new Image();
729
714
  img.src = iconUrl;
730
715
  img.onload = function () {
731
- const icon = L.icon({
732
- iconUrl: iconUrl,
733
- iconSize: [img.width, img.height],
734
- iconAnchor: [img.width / 2, img.height]
735
- });
736
-
737
- L.marker(position, {icon: icon}).addTo(map);
716
+ const icon = L.icon({ iconUrl, iconSize: [img.width, img.height], iconAnchor: [img.width / 2, img.height] });
717
+ L.marker(position, { icon }).addTo(map);
738
718
  };
739
719
  }
740
720
 
741
721
  function getSelectedMarkers(mapElement) {
742
722
  const map = mapInstances.get(mapElement);
743
723
  if (!map) return [];
744
-
745
724
  const order = selectionOrder.get(map);
746
- return Array.from(order.entries())
747
- .sort((a, b) => a[1] - b[1])
748
- .map(entry => entry[0]);
725
+ return Array.from(order.entries()).sort((a, b) => a[1] - b[1]).map(e => e[0]);
749
726
  }
750
727
 
751
728
  function clearSelection(mapElement) {
752
729
  const map = mapInstances.get(mapElement);
753
730
  if (!map) return;
754
-
755
731
  const selected = selectedMarkers.get(map);
756
732
  const order = selectionOrder.get(map);
757
733
  const markers = markerInstances.get(map);
758
-
759
- markers.forEach((markerInstance) => {
760
- if (selected.has(markerInstance.options.id)) {
761
- deselectMarker(markerInstance, map, true);
762
- }
763
- });
764
-
734
+ markers.forEach((markerInstance) => { if (selected.has(markerInstance.options.id)) {
735
+ deselectMarker(markerInstance, map, true);
736
+ } });
765
737
  selected.clear();
766
738
  order.clear();
767
739
  }
768
740
 
769
- function getOnSelectionChange(map) {
770
- return onSelectionChangeMap.get(map);
771
- }
772
-
773
741
  function toggleMarker(mapElement, markerId, selected) {
774
742
  const map = mapInstances.get(mapElement);
775
743
  const markers = markerInstances.get(map);
776
744
  const marker = markers?.get(markerId);
777
-
778
745
  if (!marker) return;
779
746
 
780
747
  const selectedSet = selectedMarkers.get(map);
@@ -802,16 +769,13 @@ function toggleMarker(mapElement, markerId, selected) {
802
769
  function setOrder(mapElement, markerId, orderNumber) {
803
770
  let color = null;
804
771
  const map = mapInstances.get(mapElement);
805
- const settings = $(mapElement).data('adt-map')
772
+ const settings = $(mapElement).data('adt-map');
806
773
  const markers = markerInstances.get(map);
807
774
  const marker = markers?.get(markerId);
808
-
809
775
  if (settings && settings.route && settings.route.enabled) {
810
776
  color = settings.route.color;
811
777
  }
812
-
813
778
  if (!marker) return;
814
-
815
779
  updateMarkerOrderDisplay(mapElement, marker, orderNumber, true, color);
816
780
  }
817
781
 
@@ -833,6 +797,76 @@ function recalculateRoute(mapElement) {
833
797
  calculateRoute(map);
834
798
  }
835
799
 
800
+
801
+ function addMarkerAndSelect(mapElement, markerData) {
802
+ const map = mapInstances.get(mapElement);
803
+ if (!map) return;
804
+
805
+ const markers = markerInstances.get(map);
806
+ const routeSettings = routeSettingsMap.get(map);
807
+ const markersData = markersDataMap.get(map);
808
+ const cluster = markerClusters.get(map);
809
+
810
+ if (markers.has(markerData.id)) {
811
+ const existing = markers.get(markerData.id);
812
+ selectMarker(existing, map, true);
813
+ return;
814
+ }
815
+
816
+ const normalIconUrl = routeSettings?.customMarkers?.normal || markerImg;
817
+ const selectedIconUrl = routeSettings?.customMarkers?.selected || normalIconUrl;
818
+
819
+ const normalImg = new Image();
820
+ normalImg.src = normalIconUrl;
821
+ normalImg.onload = function () {
822
+ const normalIcon = L.icon({
823
+ iconUrl: normalIconUrl,
824
+ iconSize: [normalImg.width, normalImg.height],
825
+ iconAnchor: [normalImg.width / 2, normalImg.height],
826
+ });
827
+
828
+ const loadSelectedIcon = (callback) => {
829
+ if (selectedIconUrl === normalIconUrl) {
830
+ callback(normalIcon);
831
+ return;
832
+ }
833
+ const selectedImgEl = new Image();
834
+ selectedImgEl.src = selectedIconUrl;
835
+ selectedImgEl.onload = function () {
836
+ callback(L.icon({
837
+ iconUrl: selectedIconUrl,
838
+ iconSize: [selectedImgEl.width, selectedImgEl.height],
839
+ iconAnchor: [selectedImgEl.width / 2, selectedImgEl.height],
840
+ }));
841
+ };
842
+ };
843
+
844
+ loadSelectedIcon((selectedIcon) => {
845
+ const newMarker = createMarker(
846
+ markerData,
847
+ { icon: normalIcon },
848
+ { icon: selectedIcon },
849
+ cluster || null,
850
+ true,
851
+ onSelectionChangeMap.get(map),
852
+ map,
853
+ true,
854
+ null
855
+ );
856
+
857
+ if (!cluster) {
858
+ newMarker.addTo(map);
859
+ }
860
+
861
+ markersData.push(markerData);
862
+ selectMarker(newMarker, map, true);
863
+
864
+ if (routeSettings?.enabled) {
865
+ calculateRoute(map);
866
+ }
867
+ });
868
+ };
869
+ }
836
870
  export default {
837
871
  run,
838
872
  getSelectedMarkers,
@@ -842,4 +876,5 @@ export default {
842
876
  onBeforeRouteCalculation,
843
877
  onAfterRouteCalculation,
844
878
  recalculateRoute,
845
- }
879
+ addMarkerAndSelect,
880
+ }