(function () { const lumajangCenter = [-8.1269, 113.2248]; const lumajangBounds = L.latLngBounds( L.latLng(-8.55, 112.75), L.latLng(-7.70, 113.55), ); const defaultZoom = 10; const singleMarkerZoom = 14; const minZoom = 10; const maxZoom = 18; const markerColors = { ar: '#dc2626', video: '#2563eb', }; let map; let markerLayer; let markers = []; let activeMedia = 'all'; let searchQuery = ''; function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function createMarkerIcon(mediaType) { const color = markerColors[mediaType] || markerColors.ar; return L.divIcon({ className: 'tourism-marker', html: ``, iconSize: [30, 42], iconAnchor: [15, 42], popupAnchor: [0, -38], }); } function buildPopupContent(item) { const isVideo = item.mediaType === 'video'; const badgeClass = isVideo ? 'text-bg-primary' : 'text-bg-danger'; return `
${escapeHtml(item.title)}

${escapeHtml(item.title)}

${escapeHtml(item.mediaLabel)}
${escapeHtml(item.location)}
${escapeHtml(item.category)}
Lihat Detail
`; } function markerMatches(marker) { const item = marker.tourismData; const matchesMedia = activeMedia === 'all' || item.mediaType === activeMedia; const haystack = `${item.title} ${item.location} ${item.category}`.toLowerCase(); const matchesSearch = !searchQuery || haystack.includes(searchQuery); return matchesMedia && matchesSearch; } function getVisibleMarkers() { return markers.filter((marker) => markerLayer.hasLayer(marker)); } function setMarkerVisibility() { markers.forEach((marker) => { const shouldShow = markerMatches(marker); const isShown = markerLayer.hasLayer(marker); if (shouldShow && !isShown) { markerLayer.addLayer(marker); } if (!shouldShow && isShown) { markerLayer.removeLayer(marker); } }); const visibleMarkers = getVisibleMarkers(); updateStats(); if (!visibleMarkers.length) { map.closePopup(); return; } const exactSearchMatch = searchQuery ? visibleMarkers.find((marker) => marker.tourismData.title.toLowerCase().includes(searchQuery)) : null; if (exactSearchMatch) { focusMarker(exactSearchMatch); return; } fitMarkers(visibleMarkers); } function updateStats() { const totalElement = document.getElementById('mapTotalMarkers'); const arElement = document.getElementById('mapArMarkers'); const videoElement = document.getElementById('mapVideoMarkers'); const arCount = markers.filter((marker) => marker.tourismData.mediaType === 'ar').length; const videoCount = markers.filter((marker) => marker.tourismData.mediaType === 'video').length; if (totalElement) totalElement.textContent = markers.length; if (arElement) arElement.textContent = arCount; if (videoElement) videoElement.textContent = videoCount; } function fitMarkers(targetMarkers) { if (!targetMarkers.length) { map.setView(lumajangCenter, defaultZoom); return; } if (targetMarkers.length === 1) { map.setView(targetMarkers[0].getLatLng(), singleMarkerZoom); return; } const group = L.featureGroup(targetMarkers); map.fitBounds(group.getBounds().pad(0.2), { maxZoom, }); } function refreshMapSize(callback, delay = 140) { window.setTimeout(() => { if (!map) { return; } map.invalidateSize(true); if (typeof callback === 'function') { callback(); } }, delay); } function scheduleMapInvalidation() { setTimeout(() => { map.invalidateSize(true); }, 500); setTimeout(() => { map.invalidateSize(true); }, 1000); setTimeout(() => { map.invalidateSize(true); }, 2000); } function focusMarker(marker) { map.setView(marker.getLatLng(), singleMarkerZoom); marker.openPopup(); } function resetView() { searchQuery = ''; activeMedia = 'all'; const searchInput = document.getElementById('tourismMapSearch'); const mediaSelect = document.getElementById('tourismMapMedia'); if (searchInput) searchInput.value = ''; if (mediaSelect) mediaSelect.value = 'all'; markers.forEach((marker) => { if (!markerLayer.hasLayer(marker)) { markerLayer.addLayer(marker); } }); map.closePopup(); updateStats(); refreshMapSize(() => map.setView(lumajangCenter, defaultZoom), 120); } function addFullscreenControl() { const FullscreenControl = L.Control.extend({ options: { position: 'topleft' }, onAdd() { const button = L.DomUtil.create('button', 'leaflet-control fullscreen-control'); button.type = 'button'; button.title = 'Fullscreen'; button.setAttribute('aria-label', 'Fullscreen'); button.innerHTML = ''; L.DomEvent.disableClickPropagation(button); L.DomEvent.on(button, 'click', () => { const container = map.getContainer(); if (document.fullscreenElement) { document.exitFullscreen(); } else if (container.requestFullscreen) { container.requestFullscreen(); } }); return button; }, }); map.addControl(new FullscreenControl()); document.addEventListener('fullscreenchange', () => { refreshMapSize(() => fitMarkers(getVisibleMarkers()), 180); }); } function bindControls() { document.getElementById('tourismMapSearch')?.addEventListener('input', (event) => { searchQuery = event.target.value.trim().toLowerCase(); setMarkerVisibility(); }); document.getElementById('tourismMapMedia')?.addEventListener('change', (event) => { activeMedia = event.target.value; setMarkerVisibility(); }); document.getElementById('tourismMapFit')?.addEventListener('click', () => { fitMarkers(getVisibleMarkers()); }); document.getElementById('tourismMapReset')?.addEventListener('click', resetView); window.addEventListener('resize', () => { window.clearTimeout(window.tourismMapResizeTimer); window.tourismMapResizeTimer = window.setTimeout(() => { refreshMapSize(() => fitMarkers(getVisibleMarkers()), 40); }, 180); }); window.addEventListener('load', () => { refreshMapSize(() => fitMarkers(getVisibleMarkers()), 220); }); } function initTourismMap() { const mapElement = document.getElementById('tourismMap'); const data = Array.isArray(window.tourismMapData) ? window.tourismMapData : []; if (!mapElement || typeof L === 'undefined') { return; } if (mapElement._leaflet_id) return; map = L.map(mapElement, { center: lumajangCenter, zoom: defaultZoom, minZoom, maxZoom, maxBounds: lumajangBounds, maxBoundsViscosity: 1, zoomControl: true, scrollWheelZoom: true, }); map.setMaxBounds(lumajangBounds); L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', { subdomains: 'abcd', maxZoom: 20, noWrap: true, attribution: '© OpenStreetMap contributors © CARTO', }).addTo(map); scheduleMapInvalidation(); markerLayer = L.layerGroup().addTo(map); markers = data.map((item) => { const marker = L.marker([Number(item.latitude), Number(item.longitude)], { icon: createMarkerIcon(item.mediaType), title: item.title, }); marker.tourismData = item; marker.bindPopup(buildPopupContent(item), { className: 'tourism-popup', maxWidth: 300, minWidth: 260, }); markerLayer.addLayer(marker); return marker; }); addFullscreenControl(); bindControls(); updateStats(); if (markers.length) { refreshMapSize(() => map.setView(lumajangCenter, defaultZoom), 180); } else { mapElement.classList.add('is-empty'); refreshMapSize(() => map.setView(lumajangCenter, defaultZoom), 180); } } function bootTourismMap() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTourismMap, { once: true }); } else { initTourismMap(); } } bootTourismMap(); })();