PageSourceSearch

https://www.bubi.si/media/plg_fields_aggpxtrack/js/aggpxtrack.js?ce0549

js bubi.si collected 2026-09-25 00:19:35 UTC 22,219 bytes, 503 lines download raw bytes

1/**
2 * @package      plg_fields_aggpxtrack
3 * @copyright    Copyright (C) 2005 - 2026 Astrid Günther. All rights reserved.
4 * @license      GNU General Public License version 2 or later;
5 */
6
7// Funktion zum Initialisieren der Karten
8function initAggpxMaps() {
9    
10    const leafletmaps = document.querySelectorAll('.leafletmap');
11    
12
13    if (typeof L === 'undefined') {
14        console.error('AGGPXTrack Error: Leaflet (L) is not defined. Please check if leaflet.js is loaded.');
15        return;
16    }
17
18    leafletmaps.forEach(function (container) {
19        const unique = container.getAttribute('data-unique');
20        
21
22        if (!unique) {
23            
24            return;
25        }
26
27        // Optionen aus Joomla.getOptions laden
28        const options = Joomla.getOptions('plg_fields_aggpxtrack.' + unique, {});
29        
30
31        // Prüfen, ob der Container eine Höhe hat
32        if (container.clientHeight === 0) {
33            
34        }
35
36        
37
38        // 1. Map Initialisierung
39        const scrollWheelZoomEnabled = options.scrollwheelzoom === 1;
40        const map = L.map(container, {
41            scrollWheelZoom: scrollWheelZoomEnabled
42        }).setView([51.505, -0.09], 10);
43
44        // 2. Base Layer Auswahl
45        let tileLayer;
46        const mapType = options.maptype;
47        const osmAttrib = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>';
48
49        switch (mapType) {
50            case 'osmde':
51                tileLayer = L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', { attribution: osmAttrib });
52                break;
53            case 'opentopomap':
54                tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', { 
55                    attribution: 'Map data: &copy; OpenStreetMap contributors, SRTM | Map style: &copy; OpenTopoMap',
56                    maxZoom: 17
57                });
58                break;
59            case 'thunderforest':
60                tileLayer = L.tileLayer('https://{s}.tile.thunderforest.com/' + options.thunderforestmaptype + '/{z}/{x}/{y}.png?apikey=' + options.thunderforestkey, {
61                    attribution: '&copy; Thunderforest, &copy; OpenStreetMap contributors',
62                    maxZoom: 22
63                });
64                break;
65            case 'mapbox':
66                tileLayer = L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}
66?access_token=' + options.mapboxkey, {
67                    attribution: '&copy; Mapbox, &copy; OpenStreetMap contributors',
68                    tileSize: 512,
69                    zoomOffset: -1
70                });
71                break;
72            case 'geoportailfrance':
73                tileLayer = L.tileLayer('https://wxs.ign.fr/' + options.geoportailfrancekey + '/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE=normal&TILEMATRIXSET=PM&FORMAT=image/jpeg&LAYER=GEOGRAPHICALGRIDSYSTEMS.MAPS&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', {
74                    attribution: '&copy; IGN-F/Geoportail',
75                    maxZoom: 18
76                });
77                break;
78            case 'google':
79                if (L.gridLayer.googleMutant) {
80                    tileLayer = L.gridLayer.googleMutant({
81                        type: options.googlemapstype || 'roadmap'
82                    });
83                }
84                break;
85            default:
86                tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: osmAttrib });
87        }
88        tileLayer.addTo(map);
89        
90        // Karte am Container speichern, damit andere Skripte darauf zugreifen können
91        container.leaflet_map = map;
92
93        if (typeof L.GPX === 'undefined') {
94            console.error('AGGPXTrack Error: Leaflet.GPX plugin is not loaded.');
95            return;
96        }
97
98        // 3. GPX Konfiguration
99        // showelements Parameter verarbeiten (z.B. "track,waypoint" -> ["track", "waypoint"])
100        let parseElements = ['track', 'route', 'waypoint']; // Standard
101        if (options.showelements) {
102            parseElements = options.showelements.split(',').map(s => s.trim());
103        }
104        
105        // Marker Options: Benutzerdefinierte Icons oder Standard
106        let markerOptions = {};
107        
108        if (options.custom_icons === 1 && typeof L.AwesomeMarkers !== 'undefined') {
109            
110            
111            const wptIcon = L.AwesomeMarkers.icon({
112                icon: options.customiconwaypoint_icon || 'home',
113                prefix: 'fa',
114                markerColor: options.customiconwaypoint_markercolor || 'red',
115                iconColor: options.customiconwaypoint_iconcolor || '#ffffff',
116                spin: options.customiconwaypoint_spin === 'true' || options.customiconwaypoint_spin === '1',
117                extraClasses: options.customiconwaypoint_extraclasses || ''
118            });
119            
120            
121            // Verwende benutzerdefinierte Font Awesome Icons
122            markerOptions = {
123                startIcon: L.AwesomeMarkers.icon({
124                    icon: options.customiconstart_icon || 'home',
125                    prefix: 'fa',
126                    markerColor: options.customiconstart_markercolor || 'red',
127                    iconColor: options.customiconstart_iconcolor || '#ffffff',
128                    spin: options.customiconstart_spin === 'true' || options.customiconstart_spin === '1',
129                    extraClasses: options.customiconstart_extraclasses || ''
130                }),
131                endIcon: L.AwesomeMarkers.icon({
132                    icon: options.customiconend_icon || 'home',
133                    prefix: 'fa',
134                    markerColor: options.customiconend_markercolor || 'red',
135                    iconColor: options.customiconend_iconcolor || '#ffffff',
136                    spin: options.customiconend_spin === 'true' || options.customiconend_spin === '1',
137                    extraClasses: options.customiconend_extraclasses || ''
138                }),
139                wptIcons: {
140                    '': wptIcon
141                },
142                clickable: true
143            };
144            
145            
146            // Special Waypoints hinzufügen
147            if (options.specialwaypoints && Array.isArray(options.specialwaypoints)) {
148                
149                options.specialwaypoints.forEach(function(wp) {
150                    if (wp.tagname) {
151                        markerOptions.wptIcons[wp.tagname] = L.AwesomeMarkers.icon({
152                            icon: wp.icon || 'home',
153                            prefix: 'fa',
154                            markerColor: wp.markercolor || 'red',
155                            iconColor: wp.iconcolor || '#ffffff',
156                            spin: wp.spin === '1',
157                            extraClasses: wp.extraclasses || ''
158                        });
159                    }
160                });
161            }
162            
163            markerOptions.wptIconTypeUrls = {};
164            markerOptions.wptIconUrls = {};
165        } else {
166            
167            // Verwende Standard PNG Icons
168            markerOptions = {
169                startIconUrl: options.startIconUrl,
170                endIconUrl: options.endIconUrl,
171                shadowUrl: options.shadowUrl,
172                wptIconUrls: { '': options.wptIconUrls },
173                wptIconTypeUrls: options.wptIconTypeUrls || {},
174                clickable: true
175            };
176        }
177        
178        const gpxLayer = new L.GPX(options.gpx_file_name, {
179            async: true,
180            gpx_options: {
181                parseElements: parseElements,
182                show_kilometer_point: options.show_kilometer_points === 1,
183                kilometer_point_options: {
184                    kilometer_point_color: options.kilometer_point_color || '#0000ff',
185                    kilometer_point_color_text: options.kilometer_point_color_text || '#ffffff',
186                    kilometer_point_intervall: parseInt(options.kilometer_point_intervall) || 1,
187                    kilometer_point_radius: parseInt(options.kilometer_point_radius) || 10
188                },
189                joinTrackSegments: true
190            },
191            polyline_options: {
192                color: options.polylinecolor || '#0000ff',
193                opacity: 0.75,
194                weight: 5,
195                lineCap: 'round'
196            },
197            marker_options: markerOptions
198        });
199
200        // 4. Elevation Control (Höhenprofil)
201        let elControl = null;
202        if (options.show_elevationcontrol === 1) {
203            // Funktion zum Initialisieren des Elevation Controls
204            const initElevationControl = function() {
205                if (typeof d3 === 'undefined') {
206                    console.error('AGGPXTrack: D3 library not loaded');
207                    return;
208                }
209                if (typeof L.Control.Elevation === 'undefined' || typeof L.control.elevation !== 'function') {
210                    
211                    setTimeout(initElevationControl, 100);
212                    return;
213                }
214                
215                
216                
217                
218                
219                
220                const elevationPosition = options.elevationcontrol_position || 'topright';
221                const isCollapsed = options.elevationcontrol_collapsed === 1;
222                const elevationTheme = options.elevationcontrol_theme || 'lime-theme';
223                
224                const elevationOptions = {
225                    theme: elevationTheme,
226                    width: parseInt(options.elevationcontrol_width) || 600,
227                    height: parseInt(options.elevationcontrol_height) || 170,
228                    collapsed: isCollapsed,
229                    summary: options.elevationcontrol_summary || 'multiline',
230                    margins: {
231                        top: parseInt(options.elevationcontrol_margins_top) || 10,
232                        right: parseInt(options.elevationcontrol_margins_right) || 20,
233                        bottom: parseInt(options.elevationcontrol_margins_bottom) || 30,
234                        left: parseInt(options.elevationcontrol_margins_left) || 50
235                    }
236                };
237                
238                // Position nur setzen wenn es eine gültige Leaflet-Position ist
239                if (['topleft', 'topright', 'bottomleft', 'bottomright'].includes(elevationPosition)) {
240                    elevationOptions.position = elevationPosition;
241                } else {
242                    elevationOptions.position = 'topright';
243                }
244                
245                
246                elControl = L.control.elevation(elevationOptions);
247                
248                
249                
250                // Wenn Position 'above' oder 'below', manuell in DIV rendern
251                if (elevationPosition === 'above' || elevationPosition === 'below') {
252                    const targetDiv = elevationPosition === 'above' 
253                        ? document.getElementById('aggpxtrackabove' + unique)
254                        : document.getElementById('aggpxtrackbelow' + unique);
255                    
256                    if (targetDiv) {
257                        
258                        elControl.onAdd(map);
259                        targetDiv.appendChild(elControl.getContainer());
260                        
261                        // Button muss auch hier sichtbar gemacht werden
262                        if (isCollapsed && elControl._button) {
263                            elControl._button.style.display = 'block';
264                            elControl._button.style.position = 'absolute';
265                            elControl._button.style.top = '0';
266                            elControl._button.style.left = '0';
267                            elControl._button.style.zIndex = '10000';
268                        }
269                    } else {
270                        console.error('AGGPXTrack: Target div not found for position', elevationPosition);
271                    }
272                } else {
273                    elControl.addTo(map);
274                    
275                    
276                    
277                    // Für topleft: Button muss über anderen Controls sein
278                    if (elevationPosition === 'topleft' && isCollapsed && elControl._button) {
279                        elControl._button.style.zIndex = '10000';
280                    }
281                }
282                
283                
284                
285                // Fix für Toggle-Button: Immer hinzufügen, auch wenn nicht collapsed
286                // Damit alte Felder ohne elevationcontrol_collapsed auch funktionieren
287                if (elControl._button) {
288                    
289                    
290                    // Mache Button sichtbar per JavaScript
291                    elControl._button.style.display = 'block';
292                    elControl._button.style.width = '36px';
293                    elControl._button.style.height = '36px';
294                    elControl._button.style.visibility = 'visible';
295                    elControl._button.style.opacity = '1';
296                    elControl._button.style.position = 'relative';
297                    elControl._button.style.zIndex = '1000';
298                    elControl._button.style.backgroundColor = '#f8f8f9';
299                    elControl._button.style.border = '2px solid #333';
300                    elControl._button.style.borderRadius = '5px';
301                    elControl._button.style.cursor = 'pointer';
302                    
303                    // Füge Text hinzu damit Button erkennbar ist
304                    elControl._button.innerHTML = '▼';
305                    elControl._button.style.fontSize = '20px';
306                    elControl._button.style.lineHeight = '32px';
307                    elControl._button.style.textAlign = 'center';
308                    elControl._button.style.color = '#333';
309                    elControl._button.title = 'Höhenprofil ein-/ausblenden';
310                    
311                    // Entferne alle existierenden Handler
312                    L.DomEvent.off(elControl._button, 'click');
313                    L.DomEvent.off(elControl._button, 'mousedown');
314                    L.DomEvent.off(elControl._button, 'touchstart');
315                    
316                    // Füge Handler mit nativem addEventListener hinzu (höhere Priorität)
317                    const toggleHandler = function(e) {
318                        e.preventDefault();
319                        e.stopPropagation();
320                        e.stopImmediatePropagation();
321                        
322                        const container = elControl._container;
323                        const svg = container.querySelector('svg');
324                        
325                        if (container.classList.contains('elevation-collapsed')) {
326                            container.classList.remove('elevation-collapsed');
327                            if (svg) svg.style.display = 'block';
328                            
329                        } else {
330                            container.classList.add('elevation-collapsed');
331                            if (svg) svg.style.display = 'none';
332                            
333                        }
334                        return false;
335                    };
336                    
337                    // Verwende nur click event, nicht mousedown
338                    elControl._button.addEventListener('click', toggleHandler, true);
339                    
340                    // Initiales Verstecken des SVG wenn collapsed
341                    if (isCollapsed) {
342                        const svg = elControl._container.querySelector('svg');
343                        if (svg) svg.style.display = 'none';
344                    }
345                }
346                
347                gpxLayer.on('addline', function(e) {
348                    
349                    elControl.addData(e.line);
350                    
351                    // Fallback: Setze Farbe direkt per JavaScript
352                    setTimeout(function() {
353                        const areaPath = document.querySelector('.elevation.' + elevationTheme + ' path.area');
354                        if (areaPath) {
355                            const themeColors = {
356                                'lime-theme': '#9cc222',
357                                'steelblue-theme': '#4682b4',
358                                'purple-theme': '#732c7b'
359                            };
360                            const color = themeColors[elevationTheme] || '#9cc222';
361                            areaPath.style.fill = color;
362                            
363                        }
364                    }, 100);
365                });
366            };
367            
368            // Starte Initialisierung
369            initElevationControl();
370        }
371
372        // 5. Scale & Fullscreen
373        if (options.show_fullscreencontrol === 1 && L.Control.Fullscreen) {
374            map.addControl(new L.Control.Fullscreen());
375        }
376
377        if (options.scale !== 0) {
378            L.control.scale({
379                metric: options['scale-metric'] === 1,
380                imperial: options['scale-imperial'] === 1
381            }).addTo(map);
382        }
383
384        // 6. Locate Control (Aktuelle Position)
385        if (options.show_currentposition === 1 && L.control.locate) {
386            L.control.locate({
387                position: options.currentposition_position || 'topleft',
388                strings: {
389                    title: "Show me where I am"
390                },
391                locateOptions: {
392                    enableHighAccuracy: true
393                }
394            }).addTo(map);
395        }
396
397        // 7. EasyPrint
398        if (options.show_easyprint === 1 && L.easyPrint) {
399            L.easyPrint({
400                position: options.easyprint_position || 'topleft',
401                sizeModes: ['Current', 'A4Landscape', 'A4Portrait'],
402                filename: 'map-export',
403                exportOnly: true,
404                hideControlContainer: true
405            }).addTo(map);
406        }
407
408        // 8. Omnivore (Zusätzliche Layer)
409        if (options.show_omnivore === 1 && typeof omnivore !== 'undefined') {
410            const omnivoreFile = options.omnivore_file;
411            if (omnivoreFile) {
412                // Erkennt Format automatisch anhand der Endung (kml, gpx, csv, etc.)
413                let layer;
414                const ext = omnivoreFile.split('.').pop().toLowerCase();
415
416                if (ext === 'gpx') {
417                    layer = omnivore.gpx(omnivoreFile);
418                } else if (ext === 'csv' || ext === 'tsv') {
419                    layer = omnivore.csv(omnivoreFile);
420                } else {
421                    // Default zu KML
422                    layer = omnivore.kml(omnivoreFile);
423                }
424
425                if (layer) {
426                    // Binde Popups für jeden Layer
427                    layer.on('ready', function() {
428                        layer.eachLayer(function(marker) {
429                            if (marker.feature && marker.feature.properties) {
430                                const props = marker.feature.properties;
431                                let popupContent = '';
432                                
433                                if (props.name) {
434                                    popupContent += '<strong>' + props.name + '</strong><br>';
435                                }
436                                if (props.description) {
437                                    popupContent += props.description;
438                                }
439                                
440                                if (popupContent) {
441                                    marker.bindPopup(popupContent);
442                                }
443                            }
444                        });
445                    });
446                    
447                    layer.addTo(map);
448                }
449            }
450        }
451
452        // 9. GPX Loaded: Zoom & Statistiken füllen
453        gpxLayer.on('loaded', function (e) {
454            const gpx = e.target;
455            map.fitBounds(gpx.getBounds(), { padding: [20, 20] });
456
457            // Statistiken aus dem GPX-Objekt auslesen
458            const stats = {
459                'distance': (gpx.get_distance() / 1000).toFixed(2),
460                'eleMax': gpx.get_elevation_max().toFixed(0),
461                'eleMin': gpx.get_elevation_min().toFixed(0),
462                'ele': gpx.get_elevation_gain().toFixed(0), // Mapping für 'ele' im PHP Template
463                'eleGain': gpx.get_elevation_gain().toFixed(0),
464                'eleLoss': gpx.get_elevation_loss().toFixed(0),
465                'speed': gpx.get_moving_speed().toFixed(1),
466                'hr': (gpx.get_heartrate_avg && gpx.get_heartrate_avg()
466) ? gpx.get_heartrate_avg().toFixed(0) : '-',
467                'cad': (gpx.get_cadence_avg && gpx.get_cadence_avg()) ? gpx.get_cadence_avg().toFixed(0) : '-',
468                'atemp': (gpx.get_temp_avg && gpx.get_temp_avg()) ? gpx.get_temp_avg().toFixed(1) : '-'
469            };
470
471            // HTML Elemente mit den berechneten Werten füllen
472            Object.keys(stats).forEach(key => {
473                const valEl = document.getElementById(key + '_value' + unique);
474                if (valEl) valEl.innerHTML = stats[key];
475            });
476        });
477
478        gpxLayer.on('error', function(e) {
479            console.error('AGGPXTrack GPX Load Error:', e);
480        });
481
482        gpxLayer.addTo(map);
483
484        // Event feuern, damit externe Skripte (markers, panellayers, layertree) wissen, dass die Karte bereit ist.
485        const event = new CustomEvent('aggpxtrack:ready', {
486            detail: {
487                map: map,
488                unique: unique,
489                options: options
490            }
491        });
492        container.dispatchEvent(event);
493    });
494}
495
496// Warte bis alle Scripts geladen sind (window.load statt DOMContentLoaded)
497if (document.readyState === 'complete') {
498    // Seite bereits geladen
499    initAggpxMaps();
500} else {
501    // Warte auf window.load (alle Scripts inkl. defer sind geladen)
502    window.addEventListener('load', initAggpxMaps);
503}

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.