142 lines
5.3 KiB
JavaScript
142 lines
5.3 KiB
JavaScript
/* =========================================================
|
||
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 `<div style="font-weight:700;margin-bottom:6px;">${props.nama}</div>
|
||
<div style="font-size:0.85rem;line-height:1.6;">
|
||
Penduduk: <strong>${props.jumlah_penduduk}</strong><br>
|
||
Warga miskin: <strong>${props.jumlah_miskin}</strong><br>
|
||
Persentase: <strong>${props.persentase_miskin}%</strong>
|
||
</div>`;
|
||
}
|
||
|
||
// ---- 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 = '<h4>Tingkat Kemiskinan</h4>' +
|
||
grades.map(g => `<div><i style="background:${g.c}"></i>${g.t}</div>`).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 = `<input type="text" id="searchWilayah" placeholder="🔍 Cari kelurahan..." autocomplete="off">
|
||
<div id="searchResults" class="search-results"></div>`;
|
||
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();
|
||
});
|
||
})();
|