/* =========================================================
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();
});
})();