PageSourceSearch

https://www.apag.de/js/map.js

js apag.de collected 2026-10-01 12:38:50 UTC 48,209 bytes, 1,288 lines download raw bytes

1/**
2 * This file relies on prefilled data globally available
3 */
4(function(data) {
5    let $win = $(window),
6        $body = $('body'),
7        mapsAllowed = $('#map').hasClass('maps-allowed'),
8        mapGlobalSettings = {
9            startZoom: 15,
10            maxZoom: 17,
11            // startLng: 6.083611,
12            startLng: 6.083880928779806,
13            // startLat: 50.775555,
14            startLat: 50.77534719439608,
15            cityZooms: {
16                'Aachen': 15,
17                'Berlin': 14,
18                'Datteln': 15,
19                'Dülmen': 15,
20                'Würselen': 15
21            }
22        },
23        map,
24        infoWindows = new Array(window.app.facilities.length),
25        poisInfoWindows = [],
26        poisMarkers = [],
27        // pf list
28        $mapWrapper = $('#map'),
29        $mapControls = $('#map-controls'),
30        $cityUl = $mapControls.find('#pills-cities'),
31        $capacityTypeUl = $mapControls.find('#pills-capacity-types'),
32        $cityItems = $cityUl.find('button'),
33        $capacityTypeItems = $capacityTypeUl.find('button'),
34        $facilityListWrapper = $('#map-facility-list-wrapper'),
35        $facilityListToggles = $('#facility-list-toggle, #map-facility-list-wrapper .close'),
36        $facilityListCapacityTypeButtons = $('#map-facility-list-wrapper nav button'),
37        $facilityListSearch = $('#map-facility-list-wrapper .facility-list-search'),
38        $facilityList = $('#map-facility-list-wrapper ul.map-facility-list'),
39        $facilityListItems = $('#map-facility-list-wrapper ul.map-facility-list li'),
40        $facilityListSearchHint = $('#map-facility-list-wrapper .search-hint'),
41        panInfoWindowWhenListOpenWidth = 1599,
42        windowWidthMapPanThreshold = 1900,
43        _capacityTypes = [
44            'parking',
45            'charging',
46            'bikes'
47        ],
48        _capacityTypesLabelMapping = {
49            'parking' : '.availability-car-parking',
50            'charging' : '.availability-car-charging',
51            'bikes' : '.availability-bike-parking'
52        },
53        _capacityTypesServiceMapping = {
54            'parking' : 'car_parking',
55            'charging' : 'car_charging',
56            'bikes' : 'bike_parking'
57        };
58        
59
60    // add parking facilities interaction, add map functions if allowed
61        
62    window.app.facilities.forEach(function(facility, index) {
63        (function() {
64            let $facilityListItem = $facilityListItems.filter(function($element) {
65                return facility.uuid === $(this).attr('data-facility-uuid');
66            });
67
68            facility['facilityListItem'] = $facilityListItem;
69            $facilityListItem.data('facility', facility);
70
71            /**
72             * Update a facility and all its references
73             */
74            facility.update = function() {
75                let available = getAvailableForType(facility),
76                    // @todo get trend from api
77                    trend = getTrendForType(facility);
78
79                let that = this;
80
81                // // update facility list item labels
82                // $.each(_capacityTypes, function(key, name) {
83
84                //     if (facility.availableCapacityTypesString.indexOf( _capacityTypesServiceMapping[name] ) > -1) {
85                //         that.facilityListItem.find(_capacityTypesLabelMapping[name]).text(
86                //             'available_' + name in facility 
87                //                 ? facility['available_' + name] 
88                //                 : window.app.overviewMap.settings.notAvailableString
89                //         );
90
91                //         that.facilityListItem
92                //             .find(_capacityTypesLabelMapping[name])
93                //             .removeClass('trend-easy trend-medium trend-difficult')
94                //             .addClass(
95                //                 'trend-' + (
96                //                     'trend_' + name in facility 
97                //                         ? facility['trend_' + name] 
98                //                         : 'not-available'
99
100                //                 )
101                //             );
102                //     }
103                // });
104            };
105
106            /**
107             * Test
108             */
109            facility.hasMaintenanceFor = function(capacityType) {
110                if (
111                    this['maintenance_' + capacityType]
112                    && true == this['maintenance_' + capacityType]
113                ) {
114                    return true;
115                }
116                return false;
117            };
118
119            window.app.facilities[index] = facility;
120        })();
121    }); // data loop end
122
123
124    /**
125     * Calculate a trend (which is rather a capacity stepping)
126     */
127    function getTrendForType(facility) {
128        let keyCapacity = getFilterKeyFor(data.capacityTypeShowing),
129            keyTrend = keyCapacity.replace('capacity', 'trend');
130
131        return facility[keyTrend] || null;
132    }
133
134    /**
135     * Get the availability for the currently selected capacity type
136     */
137    function getAvailableForType(facility) {
138        let keyCapacity = getFilterKeyFor(data.capacityTypeShowing),
139            keyAvailable = keyCapacity.replace('capacity', 'available');
140
141        if (null !== facility[keyAvailable] && undefined !== facility[keyAvailable]) {
142            return facility[keyAvailable];
143        }
144        else {
145            return null;
146        }
147    }
148
149    /**
150     * Return all facilities that are in a city
151     */
152    function getFacilitiesByCity(city) {
153        let facilities = [];
154        window.app.facilities.forEach(function(facility) {
155            if (city == facility.address_city) {
156                facilities.push(facility);
157            }
158        });
159        return facilities;
160    }
161
162    /**
163     * @todo
164     */
165    function getFilterKeyFor(capacityType) {
166        switch (capacityType) {
167            case 'car_charging':
168                return 'capacity_charging';
169                break;
170            case 'bike_parking':
171                return 'capacity_bikes';
172                break;
173            case 'car_parking':
174                return 'capacity_parking';
175                break;
176        }
177    }
178    
179    /**
180     * Get facilities for a specific capacity type within a city
181     */
182    function getFacilitiesByCityAndCapacityType(city, capacityType) {
183        let facilities = getFacilitiesByCity(city);
184            // filterKey = getFilterKeyFor(capacityType);
185
186        return facilities.filter(function(facility) {
187            return facility.availableCapacityTypesString.indexOf(capacityType) > -1;
188        });
189    }
190
191    /**
192     * Add pf list capacity type button event listener
193     */
194    $facilityListCapacityTypeButtons.on('click', function() {
195        var capacityType = $(this).data('capacitytype');
196
197        $facilityList.removeClass('showing-car_parking showing-car_charging showing-bike_parking')
198        .addClass('showing-' + capacityType);
199
200        $facilityListSearch.trigger('input');
201    });
202
203    /**
204     * Add pf search input event listener
205     */
206    $facilityListSearch.on('input', function() {
207        var input = $.trim($facilityListSearch.val());
208
209        $facilityListItems.removeClass('d-none');
210
211        if ('' == input) {
212            $facilityListItems.removeClass('d-none');
213        }
214        else {
215            $facilityListItems.not( function(index, element) {
216                return $(element).text().toLowerCase().indexOf(input.toLowerCase()) > -1;
217            } ).not('.city-label').addClass('d-none');
218
219            $facilityListItems.filter('.city-label').each(function() {
220                if (0 === $(this).nextUntil('.city-label').filter(':visible').length) {
221                    $(this).addClass('d-none');
222                }
223            });
224        }
225
226        setTimeout(function() {
227            if (0 === $facilityListItems.filter(':visible').length) {
228                $facilityListSearchHint.removeClass('d-none');
229            }
230            else {
231                $facilityListSearchHint.addClass('d-none');
232            }
233        }, 50);
234
235    });
236
237    /**
238     * Add pf toggle event listener
239     */
240    $facilityListToggles.on('click', function() {
241        $mapWrapper.toggleClass('facility-list-open');
242
243        if ($win.width() < windowWidthMapPanThreshold) {
244            if ($mapWrapper.hasClass('facility-list-open')) {
245                map.panBy(-.5 * $facilityListWrapper.width(), 0);
246            }
247            else {
248                map.panBy(.5 * $facilityListWrapper.width(), 0);   
249            }
250        }
251    });
252
253    /**
254     * maps will call this function after loaded successfully
255     */
256    function initMap() {
257        // make the custom overlays available we need in our map
258        window.addMapClasses();
259
260        // create the map
261        map = new google.maps.Map(document.getElementById('map-wrapper'), {
262            center: { lat: mapGlobalSettings.startLat, lng: mapGlobalSettings.startLng },
263            mapTypeControl: false,
264            zoom: mapGlobalSettings.startZoom,
265            zoomControl: true,
266            mapTypeControl: false,
267            scaleControl: true,
268            streetViewControl: false,
269            rotateControl: false,
270            fullscreenControl: false
271        });
272
273        // when the map rendered and list is open, add pan shift
274        google.maps.event.addListenerOnce(map, 'idle', function() {
275            if ($mapWrapper.hasClass('facility-list-open') && $win.width() < windowWidthMapPanThreshold) {
276                map.panBy(-.5 * $facilityListWrapper.width(), 0);
277            }
278        });
279
280        // close open info windows on blur
281        map.addListener('click', closeAllInfoWindows);
282
283        let poiStyles = data.mapStyles;
284        map.setOptions({styles: poiStyles});
285
286        // draw custom pois
287
288        if (window.app.overviewMap.settings.pois) {
289            /**
290             * @todo
291             */
292            function buildPoiInfowWindow(poi) {
293                return poi.infoWindow || '<div class="poi-info-window"><h5 class="iw-title">' + poi.title + '</h5></div>';
294            }
295
296            $.each(window.app.overviewMap.settings.pois, function(city, data) {
297                $.each(data, function(index, poi) {
298                    var marker = new google.maps.Marker({
299                            position: { lat: poi.lat, lng: poi.lng },
300                            map,
301                            icon: {
302                                url: window.app.overviewMap.settings.icons[poi.icon],
303                                scaledSize: new google.maps.Size(24, 32)
304                            },
305                            title: poi.title
306                        }),
307                        infoWindowContent = buildPoiInfowWindow(poi),
308                        infoWindow = new PoiInfoWindow(
309                            marker,
310                            infoWindowContent
311                        );
312
313                    poisInfoWindows.push(infoWindow);
314                    poisMarkers.push(marker);
315
316                    let shadow = new MarkerShadow(marker, 'poi-shadow-marker');
317                    shadow.setMap(map);
318
319                    marker.shadow = shadow;
320
321                    /**
322                     * Add marker click event listener
323                     */
324                    marker.addListener('click', function() {
325                        closeAllInfoWindows();
326                        infoWindow.setMap(map);
327                    });
328
329                });
330            });    
331        }
332
333        /**
334         * https://stackoverflow.com/a/10666030
335         */
336        function getLatLngWithOffset(latLng, offsetX, offsetY, zoom) {
337            // latLng is the apparent centre-point
338            // offsetX is the distance you want that point to move to the right, in pixels
339            // offsetY is the distance you want that point to move upwards, in pixels
340            // offset can be negative
341            // offsetX and offsetY are both optional
342
343            let scale = Math.pow(2, zoom || map.getZoom()),
344                worldCoordinateCenter = map.getProjection().fromLatLngToPoint(latLng),
345                pixelOffset = new google.maps.Point(
346                    (offsetX / scale) || 0,
347                    (offsetY / scale) || 0
348                ),
349                worldCoordinateNewCenter = new google.maps.Point(
350                    worldCoordinateCenter.x - pixelOffset.x,
351                    worldCoordinateCenter.y + pixelOffset.y
352                );
353
354            return map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
355        }
356        
357        // add maps functions, markers, badges, etc
358
359        $.each(window.app.facilities, function(index, facility) {
360            (function() {
361                let iconKey = '';
362
363                if (facility.type === 'ParkingFacility') {
364                    if (facility.facility_type && data.icons[facility.facility_type.toLowerCase()]) {
365                        iconKey = facility.facility_type.toLowerCase();
366                    }
367                    else {
368                        iconKey = 'parking_facility_default';
369                    }
370                }
371                else {
372                    iconKey = 'bike_parking';
373                }
374
375                // add the marker
376                let marker = new google.maps.Marker({
377                        animation: google.maps.Animation.DROP,
378                        position: { lat: facility.lat, lng: facility.lng },
379                        map,
380                        icon: {
381                            url: data.icons[iconKey],
382                            scaledSize: new google.maps.Size(49, 68)
383                        },
384                        title: facility.label
385                    });
386
387                facility['marker'] = marker;
388                facility['marker']['facility'] = facility;
389
390                // add pf list link
391
392                let $facilityListItem = $facilityListItems.filter(function($element) {
393                    return facility.uuid === $(this).attr('data-facility-uuid');
394                });
395
396                facility['facilityListItem'] = $facilityListItem;
397                $facilityListItem.data('facility', facility);
398
399                // add occupancy badge
400
401                let badge = new FacilityAvailabilityBadge(
402                        facility,
403                        data.capacityTypeShowing,
404                        new google.maps.LatLng(facility.lat, facility.lng),
405                        facility.available_parking || null,
406                        facility.trend_parking || null
407                    );
408                badge.setMap(map);
409                facility['badge'] = badge;
410
411                // add a pin shadow
412
413                let shadow = new MarkerShadow(marker);
414                shadow.setMap(map);
415
416                marker.shadow = shadow;
417                facility.shadow = shadow;
418
419                // add the info window
420
421                let infoWindowContent = buildInfoWindowContent(window.app.overviewMap.settings.facilities[index]),
422                    infoWindow = new FacilityInfoWindow(
423                        marker,
424                        infoWindowContent
425                    );
426                infoWindows[index] = infoWindow;
427
428                // add maintenance badge
429
430                let maintenanceBadge = new MaintenanceBadge(
431                        facility,
432                        new google.maps.LatLng(facility.lat, facility.lng)
433                    );
434                maintenanceBadge.setMap(map);
435                facility['maintenanceBadge'] = maintenanceBadge;
436
437                // $.each(facility.availableCapacityTypesString.split(','), function(index, capacityType) {
438                //     let maintenanceBadge = new MaintenanceBadge(
439                //             new google.maps.LatLng(facility.lat, facility.lng),
440                //             capacityType,
441                //             facility.uuid
442                //         );
443                //     maintenanceBadge.setMap(map);
444                //     facility['maintenance_badge_' + capacityType] = maintenanceBadge;
445                // });
446
447                /**
448                 * Add marker click event listener
449                 */
450                marker.addListener('click', function() {
451                    facility.focus();
452                });
453
454                /**
455                 * Pan the map to a facility
456                 */
457                facility.focus = function() {
458                    closeAllInfoWindows();
459
460                    let currentMapZoom = map.getZoom(),
461                        $infoWindow = $(infoWindow.containerDiv),
462                        $wrapper = $('<div class="if-calc-wrapper"></div>').appendTo($body),
463                        $infoWindowClone = $(infoWindow.containerDiv)
464                            .clone()
465                            .appendTo($wrapper),
466                        panOffsetX = $mapWrapper.hasClass('facility-list-open') && $win.width() > ($facilityListWrapper.width() * 2) && $win.width() < windowWidthMapPanThreshold
467                                ? .5 * $facilityListWrapper.width() 
468                                : 0,
469                        offsetLatLng = getLatLngWithOffset(
470                            this.marker.getPosition(),
471                            panOffsetX,
472                            $infoWindowClone.height() * -.5,
473                            facility.address_city === data.cityShowing
474                                ? currentMapZoom
475                                : mapGlobalSettings.cityZooms[facility.address_city]
476                        );
477
478                    // this is buggy without a timeout
479                    setTimeout(function() {
480                        map.panTo( 
481                            offsetLatLng
482                        );
483                    }, 30);
484
485                    $wrapper.remove();
486
487                    setTimeout(function() {
488                        facility.openInfoWindow();
489                    }, 250);
490                };
491
492                /**
493                 * Open a facility's info window
494                 */
495                facility.openInfoWindow = function(capacityType) {
496                    closeAllInfoWindows();
497                    infoWindow.setMap(map);
498                };
499
500                /**
501                 * Update a facility and all its references
502                 */
503                facility.update = function() {
504                    let available = getAvailableForType(facility),
505                        // @todo get trend from api
506                        trend = getTrendForType(facility);
507
508                    // update google maps marker badge
509                    this.badge.update(data.capacityTypeShowing, available, trend);
510                    this.maintenanceBadge.update(data.capacityTypeShowing);
511
512                    let that = this;
513
514                    // // update facility list item labels
515                    // $.each(_capacityTypes, function(key, name) {
516
517                    //     if (facility.availableCapacityTypesString.indexOf( _capacityTypesServiceMapping[name] ) > -1) {
518                    //         that.facilityListItem.find(_capacityTypesLabelMapping[name]).text(
519                    //             'available_' + name in facility 
520                    //                 ? facility['available_' + name] 
521                    //                 : window.app.overviewMap.settings.notAvailableString
522                    //         );
523
524                    //         that.facilityListItem
525                    //             .find(_capacityTypesLabelMapping[name])
526                    //             .removeClass('trend-easy trend-medium trend-difficult')
527                    //             .addClass(
528                    //                 'trend-' + (
529                    //                     'trend_' + name in facility 
530                    //                         ? facility['trend_' + name] 
531                    //                         : 'not-available'
532
533                    //                 )
534                    //             );
535                    //     }
536                    // });
537                };
538
539                // when on a detail page or preselection is set
540
541                if (window.app.overviewMap.settings.preSelected === facility.uuid) {
542                    var preselectedCity = facility.address_city;
543
544                    if ($win.width() < 768 && $mapWrapper.hasClass('facility-list-open')) {
545                        $mapWrapper.removeClass('facility-list-open')
546                    }
547
548                    setTimeout(function() {
549                        if (data.cityShowing !== preselectedCity) {
550                            $cityItems.filter(function() {
551                                return $(this).data('city') === preselectedCity;
552                            }).trigger('click');
553                        }
554                    }, 400);
555
556                    // when the map rendered and list is open, add pan shift
557                    google.maps.event.addListenerOnce(map, 'idle', function() {
558                        setTimeout(function() {
559                            // new google.maps.event.trigger(marker, 'click');
560                            facility.focus();
561                        }, 120);
562                    });
563                }
564
565                window.app.facilities[index] = facility;
566            })();
567        }); // data loop end
568
569        /**
570         * Close all info windows
571         */
572        function closeAllInfoWindows() {
573            infoWindows.forEach(function(iw) {
574                iw.setMap(null);
575            });
576            poisInfoWindows.forEach(function(iw) {
577                iw.setMap(null);
578            });
579        }
580        
581        /**
582         * @todo https://pineco.de/using-laravels-localization-js/
583         */
584        function buildInfoWindowContent(facility) {
585            return facility.infoWindow;
586        }
587        
588        /**
589         * Get all facilities that are currently visible in map bounds
590         */
591        function getVisibleFacilities() {
592            let bounds = map.getBounds(),
593                facilities = [];
594
595            if (undefined == bounds) return facilities;
596
597            window.app.facilities.forEach(function(facility) {
598                if (bounds.contains(facility.marker.getPosition())) {
599                    facilities.push(facility);
600                }
601            });
602            return facilities;
603        }
604
605        /**
606         * Disable/ enable capacity filters based on the map
607         */
608        function updateCapacityTypeItems() {
609            let facilities = getVisibleFacilities(),
610                types = Object.keys(data.capacityTypes);
611
612            types.shift(); // the first type will always be available
613            types.forEach(function(capacityType) {
614
615                if (facilities.filter(function(facility) {
616                    return facility.availableCapacityTypesString.indexOf(capacityType) > -1;
617                }).length) {
618                    // @todo rewrite in jquery
619                    Array.from($capacityTypeItems)
620                        .filter($item => $item.matches('[data-capacitytype="' + capacityType + '"]'))
621                        [0].classList.remove('disabled');
622                }
623                else {
624                    // @todo rewrite in jquery
625                    Array.from($capacityTypeItems)
626                        .filter($item => $item.matches('[data-capacitytype="' + capacityType + '"]'))
627                        [0].classList.add('disabled');
628                }
629            });
630        }
631        
632        /**
633         * Set the current city after clicking the filter
634         */
635        function setCity(city) {
636            if ('Aachen' == city) {
637                map.setCenter({ lat: mapGlobalSettings.startLat, lng: mapGlobalSettings.startLng });
638                map.setZoom(mapGlobalSettings.startZoom);
639            }
640            else {
641                let bounds = new google.maps.LatLngBounds();
642                let facilities = getFacilitiesByCity(city);
643                facilities.forEach(function(facility) {
644                    bounds.extend(facility.marker.position);
645                });
646                map.fitBounds(bounds);
647                map.setZoom( mapGlobalSettings.cityZooms[city] );
648            }
649            data.cityShowing = city;
650
651            if (
652                $mapWrapper.hasClass('facility-list-open') 
653                && $win.width() <= panInfoWindowWhenListOpenWidth
654                && $win.width() < windowWidthMapPanThreshold
655            ) {
656                map.panBy( 
657                    -.5 * $facilityListWrapper.width(),
658                    0
659                );
660            }
661        }
662
663        /**
664         * Set the current capacity type after clicking the filter
665         */
666        function setCapacityType(capacityType) {
667            data.capacityTypeShowing = capacityType;
668
669            $.each(window.app.facilities, function(index, facility) {
670                if (facility.availableCapacityTypesString.indexOf(capacityType) > -1) {
671                    let icon = facility.marker.getIcon();
672                    
673                    icon.url = (
674                        'car_parking' == capacityType
675                        ? data.icons[facility['facility_type'].toLowerCase()] || data.icons['parking_facility_default']
676                        : data.icons[capacityType]
677                    );
678
679                    facility.marker.setMap(null);
680                    facility.marker.setIcon(icon);
681                    facility.marker.setAnimation(google.maps.Animation.DROP);
682                    facility.marker.setMap(map);
683
684                    // facility.badge.hide();
685                    // // facility.badge.setMap(null);
686                    // // facility.badge.setMap(map);
687                    // facility.badge.setCapacityType(
688                    //     capacityType,
689                    //     getAvailableForType(facility),
690                    //     getTrendForType(facility)
691                    // );
692                    // facility.badge.show();
693
694                    // facility.badge.hide();
695                    // facility.badge.setMap(null);
696                    // facility.badge.setMap(map);
697                    facility.badge.setCapacityType(
698                        capacityType,
699                        getAvailableForType(facility),
700                        getTrendForType(facility)
701                    );
702
703                    facility.shadow.hide();
704                    facility.shadow.setMap(null);
705                    facility.shadow.setMap(map);
706
707                    // facility.maintenanceBadge.update(capacityType);
708
709                    // if (
710                    //     facility['maintenance_' + capacityType]
711                    //     && true == facility['maintenance_' + capacityType]
712                    // ) {
713                    //     facility['maintenanceBadge'].show();
714                    // }
715                    // else {
716                    //     facility['maintenanceBadge'].hide();
717                    // }
718
719                    // setTimeout(function() {
720                    facility.update();
721                    // }, 200);
722                }
723                else {
724                    facility.marker.setMap(null);
725                    // facility.badge.setMap(null);
726                    facility.shadow.setMap(null);
727                    // facility.maintenanceBadge.setMap(null);
728                    facility.badge.hide();
729                    facility.maintenanceBadge.hide();
730                }
731            });
732
733            // check if cities even have facilities with the currently selected capacity type
734
735            data.cities.forEach(function(city) {
736                // if (data.cityShowing == city) return true;
737
738                let facilities = getFacilitiesByCityAndCapacityType(city, capacityType);
739
740                if (facilities.length) {
741                    $cityUl.find('[data-city="' + city + '"]').removeClass('disabled');
742                }
743                else {
744                    $cityUl.find('[data-city="' + city + '"]').addClass('disabled');
745                }
746            });
747        }
748
749        /**
750         * close info window buttons
751         */
752        $mapWrapper.on('click', '.facility-info-window .close', closeAllInfoWindows);
753
754        // add events to navigation
755
756        /**
757         * Add city button event listener
758         */
759        $cityItems.on('click', function() {
760            if ($(this).hasClass('disabled')) return false;
761
762            if (1 === +$cityUl.css('--drop-active') && !$cityUl.hasClass('open')) return true;
763
764            setCity($(this).data('city'));
765            closeAllInfoWindows();
766        });
767        
768        /**
769         * Add capacity type button event listener
770         */
771        $capacityTypeItems.on('click', function() {
772            if ($(this).hasClass('disabled')) return true;
773            setCapacityType($(this).data('capacitytype'));
774            closeAllInfoWindows();
775        });
776
777        /**
778         * remove the maps scroll hint
779         */
780        // $('#map-wrapper').on('mouse.hintremove mousewheel.hintremove wheel.hintremove DOMMouseScroll.hintremove scroll.hintremove', function() {
781        //     var $hint = $(this).find(':contains("Halte die Taste"):not(:has(*))');
782        //     if ($hint.length) {
783        //         $hint.parent().remove();
784        //         $('#map-wrapper').off('mouse.hintremove mousewheel.hintremove wheel.hintremove DOMMouseScroll.hintremove scroll.hintremove');
785        //     }
786        // });
787        
788        map.addListener('drag', updateCapacityTypeItems);
789        map.addListener('center_changed', updateCapacityTypeItems);
790        map.addListener('zoom_changed', updateCapacityTypeItems);
791        map.addListener('zoom_changed', function() {
792            var zoomLvl = map.getZoom();
793
794            if (zoomLvl < 15) {
795                // small pois
796                $.each(poisMarkers, function(index, marker) {
797                    var icon = marker.getIcon();
798                    icon.size = new google.maps.Size(16, 21);
799                    icon.scaledSize = new google.maps.Size(16, 21);
800                    marker.setIcon(icon);
801                });
802            }
803            else if (zoomLvl >= 16) {
804                $.each(poisMarkers, function(index, marker) {
805                    var icon = marker.getIcon();
806                    icon.size = new google.maps.Size(32, 42);
807                    icon.scaledSize = new google.maps.Size(32, 42);
808                    marker.setIcon(icon);
809                });
810            }
811            else {
812                $.each(poisMarkers, function(index, marker) {
813                    var icon = marker.getIcon();
814                    icon.size = new google.maps.Size(24, 32);
815                    icon.scaledSize = new google.maps.Size(24, 32);
816                    marker.setIcon(icon);
817                });
818            }
819        });
820
821        // when ready
822        google.maps.event.addListenerOnce(map, 'idle', function() {
823            setTimeout(function() {
824                updateCapacityTypeItems();
825            }, 30);
826        });   
827
828        // pf list controls
829
830        /**
831         * @todo
832         */
833        $facilityListWrapper.on('click', '.facility-list-link', function(event) {
834            event.preventDefault();
835        });
836
837        /**
838         * Add pf list item event listener
839         */
840        $facilityListItems.on('click', function() {
841            var $item = $(this);
842
843            if ($item.hasClass('city-label')) return true;
844
845            $item.data('facility').focus();
846
847            // close list when screen too small
848            if (
849                $win.width() < ($facilityListWrapper.width() * 2) 
850                && $mapWrapper.hasClass('facility-list-open')
851                && $win.width() < windowWidthMapPanThreshold
852            ) {
853                $mapWrapper.toggleClass('facility-list-open');
854            }
855
856            var capacityType = $facilityListCapacityTypeButtons.filter('.active').first().data('capacitytype'),
857                city = $item.data('city');
858
859            if (data.capacityTypeShowing !== capacityType) {
860                let $capacityTypeItem = $capacityTypeItems.filter('[data-capacitytype="' + $facilityListCapacityTypeButtons.filter('.active').first().data('capacitytype') + '"]');
861                
862                $capacityTypeItem.removeClass('disabled').click();
863            }
864
865            if (data.cityShowing !== city) {
866                $cityItems.filter('[data-city="' + city + '"]').removeClass('disabled').click();
867            }
868        });
869
870        // trigger
871
872        setCapacityType(data.capacityTypeShowing);
873        $facilityListCapacityTypeButtons.filter('[data-capacitytype="' + data.capacityTypeShowing 
873+ '"]').click();
874
875    } // init map
876
877    window.initMap = initMap; // make this globally available for Google Maps callback
878
879})(window.app.overviewMap.settings);
880
881// init classes needed for gmap
882(function() {
883    /**
884     * @todo
885     */
886    function addMapClasses() {
887        /**
888         * Add the availability badge
889         */
890        class FacilityAvailabilityBadge extends google.maps.OverlayView {
891            constructor(facility, capacityType, position, available, trend) {
892                super();
893                this.facility = facility;
894                this.capacityType = capacityType;
895                this.position = position;
896                this.trend = trend;
897                this.available = available;
898                this.containerDiv = $('<div/>', {
899                    text: this.formatAvailable(available),
900                    'class': [
901                        'badge-container',
902                        'availability-label',
903                        // 'availability-car-parking',
904                        'trend-' + (trend || 'not-available'),
905                        'transition-delay'
906                    ].join(' '),
907                    'data-facility-uuid': facility.uuid,
908                    'data-max': 99
909                }).get(0);
910                this.hidden = false;
911
912                FacilityAvailabilityBadge.preventMapHitsAndGesturesFrom(this.containerDiv);
913            }
914
915            /** Called when the popup is added to the map. */
916            onAdd() {
917                this.getPanes().floatPane.appendChild(this.containerDiv);
918                let that = this;
919                setTimeout(function() {
920                    if (that.facility.availableCapacityTypesString.indexOf(window.app.overviewMap.settings.capacityTypeShowing) > -1) {
921                        that.show();
922                    }
923                    else {
924                        that.hide();
925                    }
926                }, 20);
927            }
928
929            /** Called when the popup is removed from the map. */
930            onRemove() {
931                if (this.containerDiv.parentElement) {
932                    this.containerDiv.parentElement.removeChild(this.containerDiv);
933                }
934            }
935
936            /** Called each frame when the popup needs to draw itself. */
937            draw() {
938                let divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
939                // Hide the popup when it is far out of view.
940                let display =
941                    Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
942                        ? "inline-block"
943                        : "none";
944
945                if (display === "inline-block") {
946                    this.containerDiv.style.left = divPosition.x + "px";
947                    this.containerDiv.style.top = divPosition.y + "px";
948                }
949
950                if (this.containerDiv.style.display !== display) {
951                    this.containerDiv.style.display = display;
952                }
953            }
954
955            /** Custom: Change the availability text and color */
956            update(capacityType, available, trend) {
957                this.checkForMaintenance();
958                this.capacityType = capacityType;
959                this.available = available;
960                this.trend = trend;
961
962                if (!this.hidden) {
963                    $(this.containerDiv)
964                        .text(this.formatAvailable(available))
965                        .removeClass('trend-easy trend-medium trend-difficult trend-not-available')
966                        .addClass('trend-' + (trend || 'not-available'));
967                }
968
969            }
970
971            /** Custom: @todo */
972            setCapacityType(capacityType, available, trend) {
973                // this.checkForMaintenance();
974                this.capacityType = capacityType;
975
976                $(this.containerDiv)
977                    .removeClass('availability-car-parking availability-car-charging availability-bike-parking')
978                    .addClass('availability-' + capacityType.replaceAll('_', '-'));
979
980                // this.update(capacityType, available, trend);
981            }
982
983            checkForMaintenance() {
984                if (
985                    this.facility.hasMaintenanceFor(this.capacityType)
986                    || this.capacityType != window.app.overviewMap.settings.capacityTypeShowing
987                ) {
988                    this.hide();
989                }
990                else if (
991                    this.capacityType == window.app.overviewMap.settings.capacityTypeShowing
992                    && this.facility.availableCapacityTypesString.indexOf(window.app.overviewMap.settings.capacityTypeShowing) > -1
993                ) {
994                    this.show();
995                }
996            }
997
998            /** Custom: format text for available */
999            formatAvailable(available) {
1000                if (null === available || undefined === available) {
1001                    return window.app.overviewMap.settings.notAvailableString;
1002                }
1003
1004                if (+available > 99) return '99+';
1005                return available;
1006            }
1007
1008            hide() {
1009                $(this.containerDiv).removeClass('transition-delay');
1010
1011                let that = this;
1012
1013                setTimeout(function() {
1014                    $(that.containerDiv).removeClass('show');
1015                }, 20);
1016                this.hidden = true;
1017            }
1018
1019            show() {
1020                $(this.containerDiv).addClass('transition-delay');
1021
1022                let that = this;
1023
1024                setTimeout(function() {
1025                    $(that.containerDiv).addClass('show');
1026                }, 20);
1027                this.hidden = false;
1028            }
1029        }
1030
1031        window.FacilityAvailabilityBadge = FacilityAvailabilityBadge;
1032
1033        /**
1034         * Add the maintenance badge
1035         */
1036        class MaintenanceBadge extends google.maps.OverlayView {
1037            constructor(facility, position) {
1038                super();
1039                this.facility = facility;
1040                this.position = position;
1041                this.containerDiv = $('<div/>', {
1042                    'html': $('#template-maintenance-icon').html(),
1043                    'class': [
1044                        'maintenance-badge-container'
1045                    ].join(' ')
1046                }).get(0);
1047                this.hidden = false;
1048
1049                MaintenanceBadge.preventMapHitsAndGesturesFrom(this.containerDiv);
1050            }
1051
1052            /** Called when the popup is added to the map. */
1053            onAdd() {
1054                this.getPanes().floatPane.appendChild(this.containerDiv);
1055                let that = this;
1056                setTimeout(function() {
1057                    that.update(window.app.overviewMap.settings.capacityTypeShowing);
1058                    // if (
1059                    //     that.facility.availableCapacityTypesString.indexOf(window.app.overviewMap.settings.capacityTypeShowing) > -1
1060                    //     && 
1061                    // ) {
1062                    //     that.show();
1063                    // }
1064                    // else {
1065                    //     that.hide();
1066                    // }
1067                }, 20);
1068            }
1069
1070            /** Called when the popup is removed from the map. */
1071            onRemove() {
1072                if (this.containerDiv.parentElement) {
1073                    this.containerDiv.parentElement.removeChild(this.containerDiv);
1074                }
1075            }
1076
1077            /** Called each frame when the popup needs to draw itself. */
1078            draw() {
1079                let divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
1080                // Hide the popup when it is far out of view.
1081                let display =
1082                    Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
1083                        ? "inline-block"
1084                        : "none";
1085
1086                if (display === "inline-block") {
1087                    this.containerDiv.style.left = divPosition.x + "px";
1088                    this.containerDiv.style.top = divPosition.y + "px";
1089                }
1090
1091                if (this.containerDiv.style.display !== display) {
1092                    this.containerDiv.style.display = display;
1093                }
1094            }
1095
1096            update(capacityType) {
1097                if (
1098                    capacityType == window.app.overviewMap.settings.capacityTypeShowing
1099                    && this.facility.availableCapacityTypesString.indexOf(window.app.overviewMap.settings.capacityTypeShowing) > -1
1100                    && this.facility.hasMaintenanceFor(capacityType)
1101                ) {
1102                    this.show();
1103                }
1104                else {
1105                    this.hide();
1106                }
1107            }
1108
1109            hide() {
1110                $(this.containerDiv).removeClass('transition-delay');
1111
1112                let that = this;
1113
1114                setTimeout(function() {
1115                    $(that.containerDiv).removeClass('show');
1116                }, 20);
1117                this.hidden = true;
1118            }
1119
1120            show() {
1121                $(this.containerDiv).addClass('transition-delay');
1122
1123                let that = this;
1124
1125                setTimeout(function() {
1126                    $(that.containerDiv).addClass('show');
1127                }, 20);
1128                this.hidden = false;
1129            }
1130        }
1131
1132        window.MaintenanceBadge = MaintenanceBadge;
1133
1134
1135        /**
1136         * Add the availability badge
1137         */
1138        class MarkerShadow extends google.maps.OverlayView {
1139            constructor(marker, additionalClasses) {
1140                super();
1141                this.marker = marker;
1142                this.position = marker.getPosition();
1143                this.containerDiv = $('<div/>', {
1144                    'class': 'marker-shadow ' + (additionalClasses || '')
1145                }).get(0);
1146
1147                MarkerShadow.preventMapHitsAndGesturesFrom(this.containerDiv);
1148            }
1149
1150            /** Called when the popup is added to the map. */
1151            onAdd() {
1152                this.getPanes().overlayShadow.appendChild(this.containerDiv);
1153                let that = this;
1154                setTimeout(function() {
1155                    $(that.containerDiv).addClass('added');
1156                }, 100);
1157            }
1158
1159            /** Called when the popup is removed from the map. */
1160            onRemove() {
1161                if (this.containerDiv.parentElement) {
1162                    this.containerDiv.parentElement.removeChild(this.containerDiv);
1163                }
1164            }
1165
1166            /** Called each frame when the popup needs to draw itself. */
1167            draw() {
1168                let divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
1169                // Hide the popup when it is far out of view.
1170                let display =
1171                    Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
1172                        ? "inline-block"
1173                        : "none";
1174
1175                if (display === "inline-block") {
1176                    this.containerDiv.style.left = divPosition.x + "px";
1177                    this.containerDiv.style.top = divPosition.y + "px";
1178                }
1179
1180                if (this.containerDiv.style.display !== display) {
1181                    this.containerDiv.style.display = display;
1182                }
1183            }
1184
1185            hide() {
1186                $(this.containerDiv).removeClass('added');
1187            }
1188        }
1189
1190        window.MarkerShadow = MarkerShadow;
1191
1192        /**
1193         * Custom Info Window for future proof styling
1194         */
1195        class FacilityInfoWindow extends google.maps.OverlayView {
1196            constructor(marker, content) {
1197                super();
1198                this.marker = marker;
1199                this.position = marker.getPosition();
1200                this.containerDiv = $(content).get(0);
1201
1202                FacilityInfoWindow.preventMapHitsAndGesturesFrom(this.containerDiv);
1203            }
1204
1205            /** Called when the popup is added to the map. */
1206            onAdd() {
1207                this.getPanes().floatPane.appendChild(this.containerDiv);
1208            }
1209
1210            /** Called when the popup is removed from the map. */
1211            onRemove() {
1212                if (this.containerDiv.parentElement) {
1213                    this.containerDiv.parentElement.removeChild(this.containerDiv);
1214                }
1215            }
1216
1217            /** Called each frame when the popup needs to draw itself. */
1218            draw() {
1219                let divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
1220                // Hide the popup when it is far out of view.
1221                var display =
1222                    Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
1223                        ? "inline-block"
1224                        : "none";
1225
1226                if (display === "inline-block") {
1227                    this.containerDiv.style.left = divPosition.x + "px";
1228                    this.containerDiv.style.top = divPosition.y + "px";
1229                }
1230
1231                if (this.containerDiv.style.display !== display) {
1232                    this.containerDiv.style.display = display;
1233                }
1234            }
1235        }
1236
1237        window.FacilityInfoWindow = FacilityInfoWindow;
1238
1239        /**
1240         * Custom Info Window for future proof styling
1241         */
1242        class PoiInfoWindow extends google.maps.OverlayView {
1243            constructor(marker, content) {
1244                super();
1245                this.marker = marker;
1246                this.position = marker.getPosition();
1247                this.containerDiv = $(content).get(0);
1248
1249                PoiInfoWindow.preventMapHitsAndGesturesFrom(this.containerDiv);
1250            }
1251
1252            /** Called when the popup is added to the map. */
1253            onAdd() {
1254                this.getPanes().floatPane.appendChild(this.containerDiv);
1255            }
1256
1257            /** Called when the popup is removed from the map. */
1258            onRemove() {
1259                if (this.containerDiv.parentElement) {
1260                    this.containerDiv.parentElement.removeChild(this.containerDiv);
1261                }
1262            }
1263
1264            /** Called each frame when the popup needs to draw itself. */
1265            draw() {
1266                let divPosition = this.getProjection().fromLatLngToDivPixel(this.position);
1267                // Hide the popup when it is far out of view.
1268                var display =
1269                    Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
1270                        ? "inline-block"
1271                        : "none";
1272
1273                if (display === "inline-block") {
1274                    this.containerDiv.style.left = divPosition.x + "px";
1275                    this.containerDiv.style.top = divPosition.y + "px";
1276                }
1277
1278                if (this.containerDiv.style.display !== display) {
1279                    this.containerDiv.style.display = display;
1280                }
1281            }
1282        }
1283
1284        window.PoiInfoWindow = PoiInfoWindow;
1285    }
1286
1287    window.addMapClasses = addMapClasses;
1288})();

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.