First commit / commit pertama
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
/* =========================================================
|
||||
skpl-layers.js — Layer SKPL reusable untuk peta login.
|
||||
Choropleth kelurahan (KF-06) + Heatmap (KF-08) + legenda +
|
||||
layer switcher overlay (KF-09) + pencarian wilayah (KF-11).
|
||||
|
||||
Pakai: panggil attachSkplLayers(map) setelah map dibuat.
|
||||
Butuh leaflet.heat sudah dimuat di halaman.
|
||||
========================================================= */
|
||||
(function () {
|
||||
function injectStyle() {
|
||||
if (document.getElementById('skpl-layer-style')) return;
|
||||
const css = `
|
||||
.skpl-legend{background:#fff;padding:10px 12px;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,.15);font-size:.76rem;line-height:1.6}
|
||||
.skpl-legend h4{font-size:.78rem;margin:0 0 6px}
|
||||
.skpl-legend i{display:inline-block;width:13px;height:13px;border-radius:3px;margin-right:6px;vertical-align:middle}
|
||||
.skpl-search{background:#fff;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,.15);padding:6px;width:220px}
|
||||
.skpl-search input{width:100%;border:1px solid #E5E7EB;border-radius:7px;padding:7px 9px;font-size:.83rem;outline:none}
|
||||
.skpl-search input:focus{border-color:#2563EB}
|
||||
.skpl-results .item{padding:7px 9px;font-size:.82rem;cursor:pointer;border-radius:6px}
|
||||
.skpl-results .item:hover{background:#EFF6FF;color:#1D4ED8}`;
|
||||
const s = document.createElement('style');
|
||||
s.id = 'skpl-layer-style';
|
||||
s.textContent = css;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function colorFor(p) {
|
||||
return p >= 10 ? '#EF4444' : p >= 5 ? '#FB923C' : p >= 2 ? '#FBBF24' : p > 0 ? '#A3E635' : '#2ECC71';
|
||||
}
|
||||
|
||||
window.attachSkplLayers = async function (map) {
|
||||
if (!map || typeof L === 'undefined') return;
|
||||
injectStyle();
|
||||
const API = (typeof APP_BASE !== 'undefined' ? APP_BASE : '') + '/api';
|
||||
let features = [];
|
||||
|
||||
// Choropleth
|
||||
let choro = null;
|
||||
try {
|
||||
const j = await (await fetch(`${API}/wilayah.php`)).json();
|
||||
const data = j.status === 'success' ? j.data : { type: 'FeatureCollection', features: [] };
|
||||
features = data.features || [];
|
||||
choro = L.geoJSON(data, {
|
||||
style: f => ({
|
||||
fillColor: colorFor(f.properties.persentase_miskin || 0),
|
||||
fillOpacity: 0.5, color: '#374151', weight: 1.5,
|
||||
}),
|
||||
onEachFeature: (f, layer) => {
|
||||
const p = f.properties;
|
||||
layer.bindPopup(`<div style="font-weight:700;margin-bottom:6px;">${p.nama}</div>
|
||||
<div style="font-size:.85rem;line-height:1.6;">
|
||||
Penduduk: <strong>${p.jumlah_penduduk}</strong><br>
|
||||
Warga miskin: <strong>${p.jumlah_miskin}</strong><br>
|
||||
Persentase: <strong>${p.persentase_miskin}%</strong></div>`);
|
||||
layer.bindTooltip(p.nama, { sticky: true });
|
||||
},
|
||||
});
|
||||
} catch (e) { /* abaikan */ }
|
||||
|
||||
// Heatmap
|
||||
let heat = null;
|
||||
try {
|
||||
const j = await (await fetch(`${API}/heatmap.php`)).json();
|
||||
const pts = j.status === 'success' ? j.data : [];
|
||||
if (L.heatLayer) {
|
||||
heat = L.heatLayer(pts, {
|
||||
radius: 30, blur: 22, maxZoom: 17,
|
||||
gradient: { 0.2: '#3B82F6', 0.5: '#FBBF24', 0.9: '#EF4444' },
|
||||
});
|
||||
}
|
||||
} catch (e) { /* abaikan */ }
|
||||
|
||||
// Layer switcher overlay
|
||||
const overlays = {};
|
||||
if (choro) overlays['🗺️ Choropleth Kelurahan'] = choro;
|
||||
if (heat) overlays['🔥 Heatmap Kepadatan'] = heat;
|
||||
if (Object.keys(overlays).length) {
|
||||
L.control.layers(null, overlays, { collapsed: true, position: 'topright' }).addTo(map);
|
||||
}
|
||||
|
||||
// Legenda
|
||||
const legend = L.control({ position: 'bottomleft' });
|
||||
legend.onAdd = function () {
|
||||
const div = L.DomUtil.create('div', 'skpl-legend');
|
||||
const g = [
|
||||
['#2ECC71', 'Tidak ada kasus'], ['#A3E635', '< 2%'],
|
||||
['#FBBF24', '2 – 5%'], ['#FB923C', '5 – 10%'], ['#EF4444', '> 10% (prioritas)'],
|
||||
];
|
||||
div.innerHTML = '<h4>Tingkat Kemiskinan</h4>' +
|
||||
g.map(x => `<div><i style="background:${x[0]}"></i>${x[1]}</div>`).join('');
|
||||
return div;
|
||||
};
|
||||
legend.addTo(map);
|
||||
|
||||
// Pencarian wilayah (KF-11)
|
||||
const search = L.control({ position: 'topright' });
|
||||
search.onAdd = function () {
|
||||
const div = L.DomUtil.create('div', 'skpl-search');
|
||||
div.innerHTML = `<input type="text" placeholder="🔍 Cari kelurahan...">
|
||||
<div class="skpl-results"></div>`;
|
||||
L.DomEvent.disableClickPropagation(div);
|
||||
return div;
|
||||
};
|
||||
search.addTo(map);
|
||||
const root = document.querySelector('.skpl-search');
|
||||
if (root) {
|
||||
const input = root.querySelector('input');
|
||||
const res = root.querySelector('.skpl-results');
|
||||
input.addEventListener('input', function () {
|
||||
const q = this.value.toLowerCase().trim();
|
||||
res.innerHTML = '';
|
||||
if (!q) return;
|
||||
features.filter(f => f.properties.nama.toLowerCase().includes(q)).slice(0, 6).forEach(f => {
|
||||
const it = document.createElement('div');
|
||||
it.className = 'item';
|
||||
it.textContent = f.properties.nama;
|
||||
it.onclick = () => {
|
||||
map.fitBounds(L.geoJSON(f).getBounds(), { padding: [60, 60] });
|
||||
res.innerHTML = '';
|
||||
input.value = f.properties.nama;
|
||||
};
|
||||
res.appendChild(it);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user