// =================================================== // 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: `
${d.status_operasional} | ${d.no_telp || '-'}
${alamatRingkas}