/** * MAPS TOPOLOGI - MAPLIBRE GEOJSON VERSION (High Performance) * Menggunakan GeoJSON Source & Symbol Layer agar ringan untuk ribuan data. */ let map; var currentStyle = 'hybrid'; // --- DEFINISI STYLE GOOGLE MAPS --- const googleHybridStyle = { 'version': 8, 'sources': { 'google-hybrid': { 'type': 'raster', 'tiles': ['https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}'], 'tileSize': 256, 'attribution': '© Google Maps' } }, 'layers': [{ 'id': 'google-hybrid-layer', 'type': 'raster', 'source': 'google-hybrid', 'minzoom': 0, 'maxzoom': 22 }] }; const googleStreetStyle = { 'version': 8, 'sources': { 'google-street': { 'type': 'raster', 'tiles': ['https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'], 'tileSize': 256, 'attribution': '© Google Maps' } }, 'layers': [{ 'id': 'google-street-layer', 'type': 'raster', 'source': 'google-street', 'minzoom': 0, 'maxzoom': 22 }] }; function initMap(center) { // Init MapLibre map = new maplibregl.Map({ container: 'map', style: googleHybridStyle, // Default Hybrid center: [center.lng, center.lat], // [Lng, Lat] zoom: 10, attributionControl: true, dragRotate: false, pitchWithRotate: false, touchPitch: false }); map.touchZoomRotate.disableRotation(); map.addControl(new maplibregl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right'); map.addControl(new maplibregl.FullscreenControl(), 'top-left'); // Tambahkan tombol ganti layer addLayerSwitcher(); // Tunggu map load selesai map.on('load', function() { // 1. Load Icon ke GPU (Wajib untuk GeoJSON) loadIcons(map, () => { // 2. Setup Source & Layer setelah icon siap setupMapLayers(); // 3. Ambil data pertama kali fetchMarkers(); // 4. Refresh marker tiap 30 detik setInterval(fetchMarkers, 30000); }); }); } // --- FUNGSI BARU: Load Icon Gambar ke Memory Peta --- function loadIcons(map, callback) { // Daftar icon sesuai file yang ada di folder dist/img/ const icons = [ { name: 'icon-odp', url: 'dist/img/ODP.png' }, { name: 'icon-on', url: 'dist/img/ON.png' }, { name: 'icon-off', url: 'dist/img/OF.png' }, { name: 'icon-on1', url: 'dist/img/ON1.png' }, { name: 'icon-off1', url: 'dist/img/OF1.png' } ]; let loadedCount = 0; // Load satu per satu icons.forEach(ic => { map.loadImage(ic.url, (error, image) => { if (error) { console.error("Gagal load icon:", ic.url); } else { if (!map.hasImage(ic.name)) map.addImage(ic.name, image); } loadedCount++; // Jika semua icon sudah diproses (sukses/gagal), jalankan callback if (loadedCount === icons.length) { if (callback) callback(); } }); }); } // --- FUNGSI BARU: Menyiapkan Wadah (Source & Layer) --- function setupMapLayers() { // Tambahkan Source Kosong (Wadah Data) map.addSource('markers-source', { type: 'geojson', data: { type: 'FeatureCollection', features: [] } }); // Tambahkan Layer Tampilan (Symbol Layer) map.addLayer({ id: 'markers-layer', type: 'symbol', source: 'markers-source', layout: { 'icon-image': ['get', 'iconName'], // Ambil nama icon dari properties GeoJSON 'icon-size': 1, // Sesuaikan ukuran (0.5 - 1.0) 'icon-allow-overlap': true, 'icon-anchor': 'bottom' // Ujung bawah icon menunjuk ke titik } }); // Event Klik untuk Popup map.on('click', 'markers-layer', (e) => { const coordinates = e.features[0].geometry.coordinates.slice(); const content = e.features[0].properties.content; // Pastikan popup muncul di atas elemen lain while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; } new maplibregl.Popup({ offset: 25 }) .setLngLat(coordinates) .setHTML(content) .addTo(map); }); // Ubah kursor jadi tangan saat hover map.on('mouseenter', 'markers-layer', () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', 'markers-layer', () => { map.getCanvas().style.cursor = ''; }); } function addLayerSwitcher() { class LayerSwitchControl { onAdd(map) { this._map = map; this._container = document.createElement('div'); this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group'; const btn = document.createElement('button'); btn.innerHTML = '🗺️'; // Icon peta btn.title = "Ganti Tampilan (Satelit/Jalan)"; btn.type = "button"; btn.onclick = () => { if (currentStyle === 'hybrid') { map.setStyle(googleStreetStyle); currentStyle = 'street'; } else { map.setStyle(googleHybridStyle); currentStyle = 'hybrid'; } // PENTING: Saat ganti style, resource gambar & source hilang. // Kita harus load ulang icon dan layer. setTimeout(() => { loadIcons(map, () => { // Cek apakah source masih ada, jika hilang buat lagi if (!map.getSource('markers-source')) { setupMapLayers(); fetchMarkers(); // Ambil data lagi } }); }, 500); }; this._container.appendChild(btn); return this._container; } onRemove() { this._container.parentNode.removeChild(this._container); this._map = undefined; } } map.addControl(new LayerSwitchControl(), 'top-right'); } function fetchMarkers() { // topologiId diambil dari variabel global di halaman PHP fetch(`load/maps_marker.php?i=${topologiId}`) .then(response => { if (!response.ok) throw new Error('Network response was not ok'); return response.json(); }) .then(res => { if (!res.status) { console.warn("⚠️ Tidak ada data marker"); return; } // --- KONVERSI DATA API KE GEOJSON --- const features = res.data.map(item => { // Tentukan nama icon berdasarkan filename dari database let iconName = 'icon-odp'; // Default // Cek string URL icon untuk menentukan jenisnya if (item.icon.includes('ODP.png')) iconName = 'icon-odp'; else if (item.icon.includes('ON.png')) iconName = 'icon-on'; else if (item.icon.includes('OF.png')) iconName = 'icon-off'; else if (item.icon.includes('ON1.png')) iconName = 'icon-on1'; else if (item.icon.includes('OF1.png')) iconName = 'icon-off1'; return { type: 'Feature', geometry: { type: 'Point', coordinates: [parseFloat(item.lng), parseFloat(item.lat)] }, properties: { content: item.content, // HTML Popup iconName: iconName // Nama icon untuk diload } }; }); // --- UPDATE DATA DI PETA (RINGAN) --- const source = map.getSource('markers-source'); if (source) { source.setData({ type: 'FeatureCollection', features: features }); console.log(`✅ ${features.length} Data updated via GeoJSON.`); } }) .catch(error => console.error('❌ Error fetching markers:', error)); } // Entry Point: Ambil center dari API, lalu init map window.onload = function() { fetch(`load/maps_marker.php?i=${topologiId}`) .then(res => res.json()) .then(res => { if (res.status && res.centerlonglat) { console.log("📍 Init map pakai centerlonglat:", res.centerlonglat); initMap(res.centerlonglat); } else { console.warn("⚠️ Centerlonglat tidak ada, pakai fallback Jakarta"); initMap({ lat: -6.2, lng: 106.816666 }); } }) .catch(err => { console.error("❌ Gagal ambil data awal:", err); initMap({ lat: -6.2, lng: 106.816666 }); // fallback }); };