/* ========================================================= public-map.js — Peta Publik SKPL Choropleth (KF-06) + Heatmap (KF-08) + Layer switcher (KF-09) + Legenda + Pencarian wilayah (KF-11). `map` & `APP_BASE` disediakan global oleh peta_publik.php. ========================================================= */ (function () { const API = APP_BASE + '/api'; let choroplethLayer = null; let heatLayer = null; let wilayahFeatures = []; // ---- Skala warna persentase kemiskinan ---- function colorFor(persen) { return persen >= 10 ? '#EF4444' // merah — tinggi : persen >= 5 ? '#FB923C' // oranye — sedang-tinggi : persen >= 2 ? '#FBBF24' // kuning — sedang : persen > 0 ? '#A3E635' // hijau muda — rendah : '#2ECC71'; // hijau — tanpa kasus } function styleWilayah(feature) { const p = feature.properties.persentase_miskin || 0; return { fillColor: colorFor(p), fillOpacity: 0.55, color: '#374151', weight: 1.5, }; } function popupWilayah(props) { return `
${props.nama}
Penduduk: ${props.jumlah_penduduk}
Warga miskin: ${props.jumlah_miskin}
Persentase: ${props.persentase_miskin}%
`; } // ---- Choropleth (KF-06) ---- async function loadChoropleth() { const r = await fetch(`${API}/wilayah.php`); const j = await r.json(); const data = j.status === 'success' ? j.data : { type: 'FeatureCollection', features: [] }; wilayahFeatures = data.features || []; choroplethLayer = L.geoJSON(data, { style: styleWilayah, onEachFeature: (f, layer) => { layer.bindPopup(popupWilayah(f.properties)); layer.bindTooltip(f.properties.nama, { sticky: true }); }, }).addTo(map); if (wilayahFeatures.length) { map.fitBounds(choroplethLayer.getBounds(), { padding: [40, 40] }); } return choroplethLayer; } // ---- Heatmap (KF-08) ---- async function loadHeatmap() { const r = await fetch(`${API}/heatmap.php`); const j = await r.json(); const points = j.status === 'success' ? j.data : []; heatLayer = L.heatLayer(points, { radius: 30, blur: 22, maxZoom: 17, gradient: { 0.2: '#3B82F6', 0.5: '#FBBF24', 0.9: '#EF4444' }, }); return heatLayer; } // ---- Legenda (KF-06) ---- function addLegend() { const legend = L.control({ position: 'bottomleft' }); legend.onAdd = function () { const div = L.DomUtil.create('div', 'legend'); const grades = [ { c: '#2ECC71', t: 'Tidak ada kasus' }, { c: '#A3E635', t: '< 2% miskin' }, { c: '#FBBF24', t: '2 – 5%' }, { c: '#FB923C', t: '5 – 10%' }, { c: '#EF4444', t: '> 10% (prioritas)' }, ]; div.innerHTML = '

Tingkat Kemiskinan

' + grades.map(g => `
${g.t}
`).join(''); return div; }; legend.addTo(map); } // ---- Pencarian wilayah (KF-11) ---- function addSearch() { const ctrl = L.control({ position: 'topright' }); ctrl.onAdd = function () { const div = L.DomUtil.create('div', 'map-search'); div.innerHTML = `
`; L.DomEvent.disableClickPropagation(div); return div; }; ctrl.addTo(map); const input = document.getElementById('searchWilayah'); const results = document.getElementById('searchResults'); input.addEventListener('input', function () { const q = this.value.toLowerCase().trim(); results.innerHTML = ''; if (!q) return; wilayahFeatures .filter(f => f.properties.nama.toLowerCase().includes(q)) .slice(0, 6) .forEach(f => { const item = document.createElement('div'); item.className = 'search-item'; item.textContent = f.properties.nama; item.onclick = () => { const layer = L.geoJSON(f); map.fitBounds(layer.getBounds(), { padding: [60, 60] }); results.innerHTML = ''; input.value = f.properties.nama; }; results.appendChild(item); }); }); } // ---- Inisialisasi ---- Promise.all([loadChoropleth(), loadHeatmap()]).then(([choro, heat]) => { // Layer switcher (KF-09) L.control.layers(null, { 'Batas Kelurahan (Choropleth)': choro, 'Heatmap Kepadatan': heat, }, { collapsed: false, position: 'topright' }).addTo(map); addLegend(); addSearch(); }); })();