// =================================================== // app.js - ARSITEKTUR WEBGIS V2 (KODE BERSIH & TERPUSAT) // =================================================== const state = { isMarkerMode: false, isRoadMode: false, isLandMode: false, tempRoadCoords: [], tempRoadLayer: null, tempLandCoords: [], tempLandLayer: null }; const dataStore = { spbu: {}, jalan: {}, tanah: {} }; const mapLayers = { spbu: {}, jalan: {}, tanah: {} }; const latPontianak = -0.0227; const lngPontianak = 109.3366; const levelZoom = 13; const savedLat = localStorage.getItem('mapCenterLat'); const savedLng = localStorage.getItem('mapCenterLng'); const savedZoom = localStorage.getItem('mapZoom'); const initialLat = savedLat ? parseFloat(savedLat) : latPontianak; const initialLng = savedLng ? parseFloat(savedLng) : lngPontianak; const initialZoom = savedZoom ? parseInt(savedZoom) : levelZoom; const map = L.map('map', { zoomControl: false }).setView([initialLat, initialLng], initialZoom); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); L.control.zoom({ position: 'bottomright' }).addTo(map); // Menyimpan posisi peta saat bergeser atau zoom map.on('moveend', function() { const center = map.getCenter(); localStorage.setItem('mapCenterLat', center.lat); localStorage.setItem('mapCenterLng', center.lng); }); map.on('zoomend', function() { localStorage.setItem('mapZoom', map.getZoom()); }); // ==================================================== // FITUR BARU: REVERSE GEOCODING (OSM NOMINATIM) // ==================================================== async function dapatkanAlamat(lat, lng) { try { // Memanggil API Geocoding Gratis dari OpenStreetMap const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&zoom=18&addressdetails=1`); if (!response.ok) throw new Error("Gagal mengambil alamat"); const data = await response.json(); return data.display_name || "Alamat detail tidak ditemukan di area ini"; } catch (error) { console.error("Geocoding Error:", error); return "Gagal memuat alamat. Periksa koneksi internet."; } } // --- KOMPONEN UI & FILTERING --- window.toggleSidebar = () => { const sidebar = document.getElementById('sidebar-container'); const btn = document.getElementById('toggle-sidebar'); sidebar.classList.toggle('collapsed'); const isCollapsed = sidebar.classList.contains('collapsed'); btn.innerHTML = isCollapsed ? '❯' : '❮'; // Simpan status sidebar ke memori browser localStorage.setItem('sidebarCollapsed', isCollapsed); setTimeout(() => map.invalidateSize(), 300); }; // Mengembalikan status sidebar saat halaman dimuat document.addEventListener('DOMContentLoaded', () => { if (localStorage.getItem('sidebarCollapsed') === 'true') { const sidebar = document.getElementById('sidebar-container'); const btn = document.getElementById('toggle-sidebar'); if(sidebar && btn) { sidebar.classList.add('collapsed'); btn.innerHTML = '❯'; } } }); // Filter dihapus dari 002 const RightToolbar = L.Control.extend({ options: { position: 'topright' }, onAdd: function() { const container = L.DomUtil.create('div', 'custom-leaflet-toolbar'); container.innerHTML = ` `; L.DomEvent.disableClickPropagation(container); return container; } }); map.addControl(new RightToolbar()); // Logika filter dihapus dari 002 // --- LOGIKA ALAT PETA --- window.toggleMarkerMode = () => { if (state.isRoadMode) toggleRoadMode(); if (state.isLandMode) toggleLandMode(); state.isMarkerMode = !state.isMarkerMode; const btn = document.getElementById('btn-tool-marker'); if (state.isMarkerMode) { btn.classList.add('active'); map.getContainer().style.cursor = 'crosshair'; Object.values(mapLayers.spbu).forEach(m => m.dragging.enable()); } else { btn.classList.remove('active'); map.getContainer().style.cursor = ''; Object.values(mapLayers.spbu).forEach(m => m.dragging.disable()); } }; window.toggleRoadMode = () => { if (state.isMarkerMode) toggleMarkerMode(); if (state.isLandMode) toggleLandMode(); state.isRoadMode = !state.isRoadMode; const btn = document.getElementById('btn-tool-jalan'); if (state.isRoadMode) { btn.classList.add('active'); map.getContainer().style.cursor = 'crosshair'; } else { btn.classList.remove('active'); map.getContainer().style.cursor = ''; batalGambarJalan(); } }; window.toggleLandMode = () => { if (state.isMarkerMode) toggleMarkerMode(); if (state.isRoadMode) toggleRoadMode(); state.isLandMode = !state.isLandMode; const btn = document.getElementById('btn-tool-tanah'); if (state.isLandMode) { btn.classList.add('active'); map.getContainer().style.cursor = 'crosshair'; } else { btn.classList.remove('active'); map.getContainer().style.cursor = ''; batalGambarTanah(); } }; map.on('click', async function(e) { if (state.isMarkerMode) { resetFormModal(); document.getElementById('spbu-modal-title').innerText = "Tambah SPBU Baru"; document.getElementById('spbu-lat').value = e.latlng.lat; document.getElementById('spbu-lng').value = e.latlng.lng; // Atur state loading untuk alamat const alamatInput = document.getElementById('spbu-alamat'); alamatInput.value = "Sedang melacak alamat dari satelit..."; document.getElementById('spbu-action-wrapper').innerHTML = ` `; bukaModalSpbu(); // KUNCI: Tarik alamat secara asinkron lalu buka kunci tombol simpan const alamatDitemukan = await dapatkanAlamat(e.latlng.lat, e.latlng.lng); alamatInput.value = alamatDitemukan; const btnSimpan = document.getElementById('btn-eksekusi-spbu'); if(btnSimpan) { btnSimpan.innerText = "Simpan Lokasi"; btnSimpan.disabled = false; } } else if (state.isRoadMode) { state.tempRoadCoords.push([e.latlng.lat, e.latlng.lng]); if (state.tempRoadLayer) map.removeLayer(state.tempRoadLayer); state.tempRoadLayer = L.polyline(state.tempRoadCoords, { color: '#1A365D', weight: 4, dashArray: '5, 8' }).addTo(map); const btnSelesai = document.getElementById('btn-selesai-jalan'); if (btnSelesai && state.tempRoadCoords.length >= 2) btnSelesai.style.display = 'block'; } else if (state.isLandMode) { state.tempLandCoords.push([e.latlng.lat, e.latlng.lng]); if (state.tempLandLayer) map.removeLayer(state.tempLandLayer); state.tempLandLayer = L.polygon(state.tempLandCoords, { color: '#D69E2E', fillColor: '#D69E2E', fillOpacity: 0.4, weight: 3, dashArray: '5, 8' }).addTo(map); const btnSelesai = document.getElementById('btn-selesai-tanah'); if (btnSelesai && state.tempLandCoords.length >= 3) btnSelesai.style.display = 'block'; } }); // --- UTILITAS & KOSMETIK MODAL --- function triggerToastNotification(pesan = "Aksi berhasil dieksekusi!") { const toast = document.getElementById('toast-notification'); toast.innerHTML = pesan; toast.classList.add('show'); setTimeout(() => { toast.classList.remove('show'); }, 3000); } const setupDropdownColor = (idSelect, colorMap) => { const select = document.getElementById(idSelect); if(select) { const update = () => { select.style.borderLeftColor = colorMap[select.value] || '#CBD5E0'; }; select.addEventListener('change', update); } }; setupDropdownColor('jalan-status', { 'Jalan Nasional': '#E53E3E', 'Jalan Provinsi': '#3182CE', 'Jalan Kabupaten': '#38A169' }); setupDropdownColor('tanah-status', { 'SHM': '#38A169', 'HGB': '#DD6B20', 'HGU': '#3182CE', 'HP': '#718096' }); function bukaModalSpbu() { document.getElementById('modal-spbu').style.display = 'flex'; } window.tutupModalSpbu = function() { document.getElementById('modal-spbu').style.display = 'none'; document.getElementById('spbu-nama').readOnly = false; document.getElementById('spbu-telp').readOnly = false; document.getElementById('spbu-status').disabled = false; }; function resetFormModal() { document.getElementById('spbu-id').value = ""; document.getElementById('spbu-nama').value = ""; document.getElementById('spbu-telp').value = ""; document.getElementById('spbu-status').value = "Buka 24 Jam"; document.getElementById('spbu-alamat').value = ""; } window.handleMarkerClick = function(id) { if (state.isMarkerMode || state.isRoadMode || state.isLandMode) return; if (!dataStore.spbu[id]) return; const d = dataStore.spbu[id]; document.getElementById('spbu-id').value = d.id; document.getElementById('spbu-nama').value = d.nama_spbu; document.getElementById('spbu-telp').value = d.no_telp; document.getElementById('spbu-status').value = d.status_operasional; document.getElementById('spbu-alamat').value = d.alamat || "Alamat belum direkam"; document.getElementById('spbu-modal-title').innerText = "Detail & Kelola SPBU"; document.getElementById('spbu-action-wrapper').innerHTML = ` `; bukaModalSpbu(); }; window.lihatSpbuDariCard = function(id) { if (!dataStore.spbu[id]) return; const d = dataStore.spbu[id]; document.getElementById('spbu-id').value = d.id; document.getElementById('spbu-nama').value = d.nama_spbu; document.getElementById('spbu-telp').value = d.no_telp; document.getElementById('spbu-status').value = d.status_operasional; document.getElementById('spbu-alamat').value = d.alamat || "Alamat belum direkam"; document.getElementById('spbu-nama').readOnly = true; document.getElementById('spbu-telp').readOnly = true; document.getElementById('spbu-status').disabled = true; document.getElementById('spbu-modal-title').innerText = "Informasi SPBU (Read-Only)"; document.getElementById('spbu-action-wrapper').innerHTML = ``; bukaModalSpbu(); }; // ... JALAN & TANAH UI FUNCTIONS (TETAP SAMA SEPERTI SEBELUMNYA) ... window.selesaiGambarJalan = function() { if (state.tempRoadCoords.length < 2) return; let totalJarak = 0; for (let i = 0; i < state.tempRoadCoords.length - 1; i++) totalJarak += map.distance(L.latLng(state.tempRoadCoords[i]), L.latLng(state.tempRoadCoords[i+1])); document.getElementById('jalan-id').value = ""; document.getElementById('jalan-nama').value = ""; document.getElementById('jalan-status').value = "Jalan Kabupaten"; document.getElementById('jalan-status').dispatchEvent(new Event('change')); document.getElementById('jalan-jarak').value = Math.round(totalJarak); document.getElementById('jalan-koordinat').value = JSON.stringify(state.tempRoadCoords); document.getElementById('jalan-modal-title').innerText = "Simpan Data Jalan"; document.getElementById('jalan-action-wrapper').innerHTML = ` `; document.getElementById('modal-jalan').style.display = 'flex'; document.getElementById('btn-selesai-jalan').style.display = 'none'; }; window.batalGambarJalan = function() { state.tempRoadCoords = []; if (state.tempRoadLayer) map.removeLayer(state.tempRoadLayer); state.tempRoadLayer = null; const btn = document.getElementById('btn-selesai-jalan'); if (btn) btn.style.display = 'none'; document.getElementById('modal-jalan').style.display = 'none'; }; window.handleRoadClick = function(id) { if (state.isRoadMode || state.isMarkerMode || state.isLandMode || !dataStore.jalan[id]) return; const d = dataStore.jalan[id]; document.getElementById('jalan-id').value = d.id; document.getElementById('jalan-nama').value = d.nama_jalan; document.getElementById('jalan-status').value = d.status_jalan; document.getElementById('jalan-jarak').value = d.jarak_meter; document.getElementById('jalan-status').dispatchEvent(new Event('change')); document.getElementById('jalan-nama').readOnly = false; document.getElementById('jalan-status').disabled = false; document.getElementById('jalan-modal-title').innerText = "Detail & Kelola Jalan"; document.getElementById('jalan-action-wrapper').innerHTML = ` `; document.getElementById('modal-jalan').style.display = 'flex'; }; window.selesaiGambarTanah = function() { if (state.tempLandCoords.length < 3) return; let area = 0, R = 6378137; const ll = state.tempLandCoords.map(c => L.latLng(c[0], c[1])); for (let i = 0; i < ll.length; i++) { let p1 = ll[i], p2 = ll[(i + 1) % ll.length]; area += (p2.lng - p1.lng) * Math.PI / 180 * (2 + Math.sin(p1.lat * Math.PI / 180) + Math.sin(p2.lat * Math.PI / 180)); } document.getElementById('tanah-id').value = ""; document.getElementById('tanah-nama').value = ""; document.getElementById('tanah-status').value = "SHM"; document.getElementById('tanah-status').dispatchEvent(new Event('change')); document.getElementById('tanah-luas').value = Math.round(Math.abs(area * R * R / 2.0)); document.getElementById('tanah-koordinat').value = JSON.stringify(state.tempLandCoords); document.getElementById('tanah-modal-title').innerText = "Simpan Data Parsil Tanah"; document.getElementById('tanah-action-wrapper').innerHTML = ` `; document.getElementById('modal-tanah').style.display = 'flex'; document.getElementById('btn-selesai-tanah').style.display = 'none'; }; window.batalGambarTanah = function() { state.tempLandCoords = []; if (state.tempLandLayer) map.removeLayer(state.tempLandLayer); state.tempLandLayer = null; const btn = document.getElementById('btn-selesai-tanah'); if (btn) btn.style.display = 'none'; document.getElementById('modal-tanah').style.display = 'none'; }; window.handleLandClick = function(id) { if (state.isRoadMode || state.isMarkerMode || state.isLandMode || !dataStore.tanah[id]) return; const d = dataStore.tanah[id]; document.getElementById('tanah-id').value = d.id; document.getElementById('tanah-nama').value = d.nama_pemilik; document.getElementById('tanah-status').value = d.status_kepemilikan; document.getElementById('tanah-luas').value = d.luas_meter; document.getElementById('tanah-status').dispatchEvent(new Event('change')); document.getElementById('tanah-modal-title').innerText = "Detail & Kelola Tanah"; document.getElementById('tanah-action-wrapper').innerHTML = ` `; document.getElementById('modal-tanah').style.display = 'flex'; }; // ==================================================== // DECOUPLED RENDERING ENGINE (LOGIKA PENGGAMBARAN) // ==================================================== function renderSPBU() { const cardContainer = document.getElementById('card-container'); cardContainer.innerHTML = ''; Object.values(mapLayers.spbu).forEach(m => map.removeLayer(m)); mapLayers.spbu = {}; Object.values(dataStore.spbu).forEach(d => { const isBuka = d.status_operasional === 'Buka 24 Jam'; const customIcon = L.divIcon({ className: 'custom-div-icon', html: `
`, iconSize: [30, 42], iconAnchor: [15, 42] }); const marker = L.marker([d.latitude, d.longitude], { icon: customIcon, draggable: state.isMarkerMode }).addTo(map); marker.bindTooltip(`${d.nama_spbu}`, { direction: 'top', offset: [0, -35], className: 'spbu-tooltip' }); marker.on('click', () => handleMarkerClick(d.id)); // KUNCI: Reverse Geocoding saat Drag-and-Drop marker.on('dragend', async function(e) { const pos = e.target.getLatLng(); triggerToastNotification(" Menganalisis alamat baru..."); // Tunggu alamat baru dari satelit sebelum dikirim ke database const alamatBaru = await dapatkanAlamat(pos.lat, pos.lng); fetch('../otak_spbu/api_update_coords.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: d.id, lat: pos.lat, lng: pos.lng, alamat: alamatBaru }) }).then(res => res.ok ? res.json() : Promise.reject(res)).then(res => { dataStore.spbu[d.id].latitude = pos.lat; dataStore.spbu[d.id].longitude = pos.lng; dataStore.spbu[d.id].alamat = alamatBaru; triggerToastNotification(" Koordinat & Alamat berhasil digeser!"); renderSPBU(); // Gambar ulang untuk update text di Sidebar }).catch(err => { alert("Gagal menyimpan posisi."); e.target.setLatLng([dataStore.spbu[d.id].latitude, dataStore.spbu[d.id].longitude]); }); }); mapLayers.spbu[d.id] = marker; // Menampilkan Alamat yang terpotong rapi di Sidebar Card const statusClass = isBuka ? 'status-buka' : 'status-tutup'; const alamatRingkas = d.alamat ? (d.alamat.substring(0, 45) + '...') : 'Alamat belum direkam'; cardContainer.innerHTML += `

${d.nama_spbu}

${d.status_operasional} | ${d.no_telp || '-'}

${alamatRingkas}

`; }); } function renderJalan() { Object.values(mapLayers.jalan).forEach(m => map.removeLayer(m)); mapLayers.jalan = {}; Object.values(dataStore.jalan).forEach(d => { let lineColor = '#38A169', lineWeight = 4; if (d.status_jalan === 'Jalan Nasional') { lineColor = '#E53E3E'; lineWeight = 8; } else if (d.status_jalan === 'Jalan Provinsi') { lineColor = '#3182CE'; lineWeight = 6; } const polyline = L.polyline(JSON.parse(d.koordinat), { color: lineColor, weight: lineWeight, opacity: 0.8 }).addTo(map); polyline.bindTooltip(`${d.nama_jalan}
${d.jarak_meter} meter`, { direction: 'auto', className: 'spbu-tooltip' }); polyline.on('mouseover', function(e) { this.setStyle({ weight: lineWeight + 3, opacity: 1 }); }); polyline.on('mouseout', function(e) { this.setStyle({ weight: lineWeight, opacity: 0.8 }); }); polyline.on('click', () => handleRoadClick(d.id)); mapLayers.jalan[d.id] = polyline; }); } function renderTanah() { Object.values(mapLayers.tanah).forEach(m => map.removeLayer(m)); mapLayers.tanah = {}; Object.values(dataStore.tanah).forEach(d => { let fillColor = '#38A169'; if (d.status_kepemilikan === 'HGB') fillColor = '#DD6B20'; else if (d.status_kepemilikan === 'HGU') fillColor = '#3182CE'; else if (d.status_kepemilikan === 'HP') fillColor = '#718096'; const polygon = L.polygon(JSON.parse(d.koordinat), { color: fillColor, fillColor: fillColor, fillOpacity: 0.4, weight: 2 }).addTo(map); polygon.bindTooltip(`${d.nama_pemilik}
${d.luas_meter} m² (${d.status_kepemilikan})`, { direction: 'auto', className: 'spbu-tooltip' }); polygon.on('mouseover', function(e) { this.setStyle({ fillOpacity: 0.7, weight: 4 }); }); polygon.on('mouseout', function(e) { this.setStyle({ fillOpacity: 0.4, weight: 2 }); }); polygon.on('click', () => handleLandClick(d.id)); mapLayers.tanah[d.id] = polygon; }); } // ==================================================== // API FETCH MANAGER (KOMUNIKASI DATABASE) // ==================================================== const safeFetch = async (url, payload = null) => { const options = payload ? { method: 'POST', body: JSON.stringify(payload) } : {}; const res = await fetch(url, options); if (!res.ok) throw new Error(`HTTP Error: ${res.status}`); const data = await res.json(); if (data.error) throw new Error(data.error); return data; }; // --- SPBU --- window.muatDataSPBU = () => { return safeFetch('../otak_spbu/api_tampil.php').then(data => { dataStore.spbu = {}; data.forEach(d => { dataStore.spbu[d.id] = d; }); renderSPBU(); }); }; window.eksekusiSimpanBaru = () => { // Alamat ikut ditangkap dan dikirim const payload = { nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value, alamat: document.getElementById('spbu-alamat').value, lat: document.getElementById('spbu-lat').value, lng: document.getElementById('spbu-lng').value }; safeFetch('../otak_spbu/api_simpan.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU ditambahkan!"); muatDataSPBU(); }).catch(err => alert(err)); }; window.eksekusiUpdate = (id) => { const payload = { id: id, nama: document.getElementById('spbu-nama').value, status: document.getElementById('spbu-status').value, telp: document.getElementById('spbu-telp').value }; safeFetch('../otak_spbu/api_update.php', payload).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU diperbarui!"); muatDataSPBU(); }).catch(err => alert(err)); }; window.eksekusiHapus = (id) => { if(!confirm("Hapus SPBU ini?")) return; safeFetch('../otak_spbu/api_hapus.php', { id: id }).then(() => { tutupModalSpbu(); triggerToastNotification(" SPBU dihapus."); muatDataSPBU(); }).catch(err => alert(err)); }; // --- JALAN --- window.muatDataJalan = () => { return safeFetch('../otak_jalan/api_tampil.php').then(data => { dataStore.jalan = {}; data.forEach(d => { dataStore.jalan[d.id] = d; }); renderJalan(); }); }; window.eksekusiSimpanJalan = () => { const payload = { nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value, jarak: document.getElementById('jalan-jarak').value, koordinat: document.getElementById('jalan-koordinat').value }; safeFetch('../otak_jalan/api_simpan.php', payload).then(() => { batalGambarJalan(); triggerToastNotification(" Jalan ditambahkan!"); muatDataJalan(); }).catch(err => alert(err)); }; window.eksekusiUpdateJalan = (id) => { const payload = { id: id, nama: document.getElementById('jalan-nama').value, status: document.getElementById('jalan-status').value }; safeFetch('../otak_jalan/api_update.php', payload).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan diperbarui!"); muatDataJalan(); }).catch(err => alert(err)); }; window.eksekusiHapusJalan = (id) => { if(!confirm("Hapus jalur ini?")) return; safeFetch('../otak_jalan/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-jalan').style.display='none'; triggerToastNotification(" Jalan dihapus."); muatDataJalan(); }).catch(err => alert(err)); }; // --- TANAH --- window.muatDataTanah = () => { return safeFetch('../otak_parsil/api_tampil.php').then(data => { dataStore.tanah = {}; data.forEach(d => { dataStore.tanah[d.id] = d; }); renderTanah(); }); }; window.eksekusiSimpanTanah = () => { const payload = { nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value, luas: document.getElementById('tanah-luas').value, koordinat: document.getElementById('tanah-koordinat').value }; safeFetch('../otak_parsil/api_simpan.php', payload).then(() => { batalGambarTanah(); triggerToastNotification(" Tanah ditambahkan!"); muatDataTanah(); }).catch(err => alert(err)); }; window.eksekusiUpdateTanah = (id) => { const payload = { id: id, nama: document.getElementById('tanah-nama').value, status: document.getElementById('tanah-status').value }; safeFetch('../otak_parsil/api_update.php', payload).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification(" Tanah diperbarui!"); muatDataTanah(); }).catch(err => alert(err)); }; window.eksekusiHapusTanah = (id) => { if(!confirm("Hapus parsil ini?")) return; safeFetch('../otak_parsil/api_hapus.php', { id: id }).then(() => { document.getElementById('modal-tanah').style.display='none'; triggerToastNotification(" Tanah dihapus."); muatDataTanah(); }).catch(err => alert(err)); }; // ==================================================== // LIFECYCLE AWAL (PROMISE ALL MANAGEMENT) // ==================================================== setTimeout(() => { map.invalidateSize(); console.log("Menghubungkan ke Database untuk penarikan data masal..."); Promise.all([ muatDataSPBU(), muatDataJalan(), muatDataTanah() ]).then(() => { console.log(" Sistem WebGIS V2 Siap dan Data Tersinkronisasi Penuh."); }).catch(err => { console.error("Kegagalan Fatal Jaringan:", err); }); }, 500); // ==================================================== // RENDER LAYER GEOJSON (BATAS WILAYAH) // ==================================================== fetch('../components/Admin_Kecamatan.json') .then(response => { if (!response.ok) throw new Error("File GeoJSON tidak ditemukan."); return response.json(); }) .then(data => { L.geoJSON(data, { style: function(feature) { return { color: "#000000", // Warna garis batas (Hitam) weight: 2, // Ketebalan garis opacity: 0.7, fillColor: "#0056b3", // Warna isi wilayah fillOpacity: 0.1 // Transparansi agar peta di bawahnya tetap terlihat }; }, onEachFeature: function(feature, layer) { if (feature.properties && feature.properties.KECAMATAN) { layer.bindPopup(`Wilayah: ${feature.properties.KECAMATAN}`); } } }).addTo(map); console.log("Layer GeoJSON Batas Wilayah berhasil dimuat."); }) .catch(error => console.error("Gagal memuat layer wilayah:", error));