Project UAS Sistem Informasi Geografis (SIG)
This commit is contained in:
+425
@@ -0,0 +1,425 @@
|
||||
/* ============================================================
|
||||
draw.js — WebGIS SPBU Pontianak
|
||||
Handles: SPBU, Jalan, Parsil, Masjid + Radius, Penduduk Miskin
|
||||
============================================================ */
|
||||
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors', maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
/* ── LAYER GROUPS ── */
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var layerGroup24 = L.featureGroup().addTo(map);
|
||||
var layerGroupTidak = L.featureGroup().addTo(map);
|
||||
var layerGroupMasjid = L.featureGroup().addTo(map);
|
||||
var layerGroupPenduduk = L.featureGroup().addTo(map);
|
||||
|
||||
var show24 = true, showTidak = true;
|
||||
var showMasjid = true, showPenduduk = true;
|
||||
|
||||
/* Storage kalkulasi radius */
|
||||
var masjidData = [];
|
||||
var pendudukData = [];
|
||||
|
||||
let currentLayer = null;
|
||||
|
||||
window.setMode = function(mode) {
|
||||
console.log('Mode Pointing changed to: ' + mode);
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
TOGGLE LAYERS
|
||||
============================================================ */
|
||||
function toggleLayer(type) {
|
||||
if (type === '24jam') {
|
||||
show24 = !show24;
|
||||
show24 ? map.addLayer(layerGroup24) : map.removeLayer(layerGroup24);
|
||||
document.getElementById('btn24').classList.toggle('active', show24);
|
||||
} else if (type === 'tidak') {
|
||||
showTidak = !showTidak;
|
||||
showTidak ? map.addLayer(layerGroupTidak) : map.removeLayer(layerGroupTidak);
|
||||
document.getElementById('btnTidak').classList.toggle('active', showTidak);
|
||||
} else if (type === 'masjid') {
|
||||
showMasjid = !showMasjid;
|
||||
showMasjid ? map.addLayer(layerGroupMasjid) : map.removeLayer(layerGroupMasjid);
|
||||
document.getElementById('btnMasjid').classList.toggle('active', showMasjid);
|
||||
} else if (type === 'penduduk') {
|
||||
showPenduduk = !showPenduduk;
|
||||
showPenduduk ? map.addLayer(layerGroupPenduduk) : map.removeLayer(layerGroupPenduduk);
|
||||
document.getElementById('btnPenduduk').classList.toggle('active', showPenduduk);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DRAW CONTROL
|
||||
============================================================ */
|
||||
var drawControl = new L.Control.Draw({
|
||||
position: 'topleft',
|
||||
edit: { featureGroup: drawnItems },
|
||||
draw: {
|
||||
marker: true,
|
||||
polyline: { shapeOptions: { color: '#a855f7', weight: 3 } },
|
||||
polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } },
|
||||
rectangle: false, circle: false, circlemarker: false
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
/* ============================================================
|
||||
HELPERS
|
||||
============================================================ */
|
||||
function getLineColor(s) {
|
||||
return s==='Nasional' ? '#ef4444' : s==='Provinsi' ? '#38bdf8' : '#a855f7';
|
||||
}
|
||||
function getLength(ll) {
|
||||
let t=0;
|
||||
for(let i=0;i<ll.length-1;i++) t+=ll[i].distanceTo(ll[i+1]);
|
||||
return t;
|
||||
}
|
||||
function fmtNum(n, dec=2) {
|
||||
return parseFloat(n).toLocaleString('id-ID', { maximumFractionDigits: dec });
|
||||
}
|
||||
function statusBadge(s, type) {
|
||||
if (type==='point') return s==='24 Jam' ? `<span class="ip-badge badge-green">24 Jam</span>` : `<span class="ip-badge badge-red">Tidak 24 Jam</span>`;
|
||||
if (type==='line') { let c=s==='Nasional'?'badge-red':s==='Provinsi'?'badge-blue':'badge-gray'; return `<span class="ip-badge ${c}">${s}</span>`; }
|
||||
return `<span class="ip-badge ${s==='SHM'?'badge-green':'badge-blue'}">${s}</span>`;
|
||||
}
|
||||
|
||||
/* ── Icons ── */
|
||||
function makeIcon(status) {
|
||||
var c = status==='24 Jam' ? '#4ade80' : '#f87171';
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="26" height="38" viewBox="0 0 26 38"><path d="M13 0C5.8 0 0 5.8 0 13c0 9.75 13 25 13 25S26 22.75 26 13C26 5.8 20.2 0 13 0z" fill="${c}" stroke="rgba(255,255,255,.25)" stroke-width="1.5"/><circle cx="13" cy="13" r="5" fill="rgba(0,0,0,.4)"/><circle cx="13" cy="13" r="3" fill="white"/></svg>`, iconSize:[26,38], iconAnchor:[13,38], popupAnchor:[0,-40], className:'' });
|
||||
}
|
||||
function makeMasjidIcon() {
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="30" height="42" viewBox="0 0 30 42"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.25 15 27 15 27S30 26.25 30 15C30 6.7 23.3 0 15 0z" fill="#f59e0b" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><text x="15" y="20" text-anchor="middle" font-size="13" fill="white">🕌</text></svg>`, iconSize:[30,42], iconAnchor:[15,42], popupAnchor:[0,-44], className:'' });
|
||||
}
|
||||
function makePendudukIcon(inRadius) {
|
||||
var c = inRadius ? '#f59e0b' : '#e879f9';
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="22" height="32" viewBox="0 0 22 32"><path d="M11 0C4.9 0 0 4.9 0 11c0 8.25 11 21 11 21S22 19.25 22 11C22 4.9 17.1 0 11 0z" fill="${c}" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><circle cx="11" cy="11" r="4" fill="rgba(0,0,0,.35)"/><circle cx="11" cy="11" r="2.5" fill="white"/></svg>`, iconSize:[22,32], iconAnchor:[11,32], popupAnchor:[0,-34], className:'' });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RADIUS LOGIC — cek semua penduduk vs semua masjid
|
||||
Jika kena ≥2 radius → pilih masjid terdekat
|
||||
============================================================ */
|
||||
function recalcAllRadius(previewM = null) {
|
||||
pendudukData.forEach(p => {
|
||||
var inAny = false;
|
||||
var minDist = Infinity;
|
||||
|
||||
// Cek semua masjid yang sudah tersimpan
|
||||
masjidData.forEach(m => {
|
||||
var d = map.distance([p.lat, p.lng], [m.lat, m.lng]);
|
||||
if (d <= m.radius) { inAny = true; if (d < minDist) minDist = d; }
|
||||
});
|
||||
|
||||
// Cek juga masjid yang sedang di-preview (sebelum simpan)
|
||||
if (previewM) {
|
||||
var d = map.distance([p.lat, p.lng], [previewM.lat, previewM.lng]);
|
||||
if (d <= previewM.radius) { inAny = true; if (d < minDist) minDist = d; }
|
||||
}
|
||||
|
||||
// Update icon jika status "dalam radius" berubah
|
||||
if (p.inRadius !== inAny) {
|
||||
p.inRadius = inAny;
|
||||
p.markerLayer.setIcon(makePendudukIcon(inAny));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
POPUP BUILDERS
|
||||
============================================================ */
|
||||
function buildInfoPopup(d, type) {
|
||||
const hc = { point:'linear-gradient(135deg,#1d4ed8,#3b82f6)', line:'linear-gradient(135deg,#7c3aed,#a855f7)', polygon:'linear-gradient(135deg,#065f46,#10b981)', masjid:'linear-gradient(135deg,#92400e,#f59e0b)', penduduk:'linear-gradient(135deg,#701a75,#e879f9)' };
|
||||
const ic = { point:'⛽', line:'🛣️', polygon:'📐', masjid:'🕌', penduduk:'👨👩👧👦' };
|
||||
const ti = { point:`SPBU — ${d.nama}`, line:`Jalan — ${d.nama}`, polygon:`Parsil — ${d.nama}`, masjid:`Masjid — ${d.nama}`, penduduk:`KK — ${d.nama_kk}` };
|
||||
|
||||
let rows = '';
|
||||
if (type==='point') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">No. SPBU</span><span class="ip-val">${d.no_spbu}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'point')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Koordinat</span><span class="ip-val" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span></div>`;
|
||||
} else if (type==='line') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'line')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Panjang</span><span class="ip-val">${fmtNum(d.panjang)} m</span></div>`;
|
||||
} else if (type==='polygon') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'polygon')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Luas</span><span class="ip-val">${fmtNum(d.luas)} m²</span></div>`;
|
||||
} else if (type==='masjid') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">PIC</span><span class="ip-val">${d.pic}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Radius</span><span class="ip-val" id="radiusDisplay_${d.id}">${fmtNum(d.radius,0)} m</span></div>
|
||||
<div class="radius-slider-wrap">
|
||||
<input type="range" min="100" max="5000" step="50" value="${d.radius}" id="radiusSlider_${d.id}" oninput="onRadiusSlide(${d.id}, this.value)">
|
||||
<div class="radius-val"><span>100m</span><span>5000m</span></div>
|
||||
</div>
|
||||
<button class="pf-btn" style="margin-top:10px" onclick="saveRadius(${d.id})">💾 Simpan Radius</button>`;
|
||||
} else if (type==='penduduk') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Jml. Anggota KK</span><span class="ip-val">${d.jumlah_anggota} orang</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Alamat</span><span class="ip-val" style="font-size:11px">${d.alamat}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Koordinat</span><span class="ip-val" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span></div>`;
|
||||
}
|
||||
|
||||
return `<div class="ip"><div class="ip-head" style="background:${hc[type]}">${ic[type]} ${ti[type]}</div><div class="ip-body">${rows}<button class="ip-del" onclick="hapusData(${d.id},'${type}')">🗑 Hapus</button></div></div>`;
|
||||
}
|
||||
|
||||
function onRadiusSlide(masjidId, val) {
|
||||
val = parseFloat(val);
|
||||
var el = document.getElementById('radiusDisplay_' + masjidId);
|
||||
if (el) el.textContent = fmtNum(val,0) + ' m';
|
||||
var m = masjidData.find(x => x.id === masjidId);
|
||||
if (m) { m.radius = val; m.circleLayer.setRadius(val); recalcAllRadius(); }
|
||||
}
|
||||
|
||||
function saveRadius(masjidId) {
|
||||
var m = masjidData.find(x => x.id === masjidId);
|
||||
if (!m) return;
|
||||
fetch('php/update_masjid_radius.php', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ id: masjidId, radius: m.radius })
|
||||
}).then(r=>r.json()).then(r=>{
|
||||
if (r.message && r.message.includes('Berhasil')) alert('Radius berhasil disimpan!');
|
||||
else alert('Gagal: ' + r.message);
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LOAD DATA
|
||||
============================================================ */
|
||||
fetch('php/show.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
var pendudukRaw = [];
|
||||
|
||||
data.forEach(d => {
|
||||
if (d.type === 'point') {
|
||||
var layer = L.marker([d.latitude, d.longitude], { icon: makeIcon(d.status) });
|
||||
layer._type='point'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'point'), { maxWidth:260 });
|
||||
d.status==='24 Jam' ? layerGroup24.addLayer(layer) : layerGroupTidak.addLayer(layer);
|
||||
}
|
||||
if (d.type === 'line') {
|
||||
var coords = JSON.parse(d.geom);
|
||||
var layer = L.polyline(coords, { color:getLineColor(d.status), weight:4, opacity:0.9 });
|
||||
layer._type='line'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'line'), { maxWidth:260 });
|
||||
drawnItems.addLayer(layer);
|
||||
}
|
||||
if (d.type === 'polygon') {
|
||||
var coords = JSON.parse(d.geom);
|
||||
var layer = L.polygon(coords, { color:'#10b981', weight:2, fillColor:'#10b981', fillOpacity:0.3 });
|
||||
layer._type='polygon'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'polygon'), { maxWidth:260 });
|
||||
drawnItems.addLayer(layer);
|
||||
}
|
||||
if (d.type === 'masjid') {
|
||||
var mLat=parseFloat(d.latitude), mLng=parseFloat(d.longitude), mR=parseFloat(d.radius);
|
||||
var circle = L.circle([mLat,mLng], { radius:mR, color:'#f59e0b', fillColor:'#f59e0b', fillOpacity:0.08, weight:2, dashArray:'6 4' });
|
||||
layerGroupMasjid.addLayer(circle);
|
||||
var marker = L.marker([mLat,mLng], { icon:makeMasjidIcon() });
|
||||
marker._type='masjid'; marker._db_id=d.id;
|
||||
marker.bindPopup(buildInfoPopup(d,'masjid'), { maxWidth:290 });
|
||||
layerGroupMasjid.addLayer(marker);
|
||||
masjidData.push({ id:parseInt(d.id), lat:mLat, lng:mLng, radius:mR, circleLayer:circle, markerLayer:marker });
|
||||
}
|
||||
if (d.type === 'penduduk') pendudukRaw.push(d);
|
||||
});
|
||||
|
||||
/* Load penduduk setelah masjid siap */
|
||||
pendudukRaw.forEach(d => {
|
||||
var pLat=parseFloat(d.latitude), pLng=parseFloat(d.longitude);
|
||||
var inRadius=false, minDist=Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var dist = map.distance([pLat,pLng],[m.lat,m.lng]);
|
||||
if (dist<=m.radius && dist<minDist) { minDist=dist; inRadius=true; }
|
||||
});
|
||||
var marker = L.marker([pLat,pLng], { icon:makePendudukIcon(inRadius) });
|
||||
marker._type='penduduk'; marker._db_id=d.id;
|
||||
marker.bindPopup(buildInfoPopup(d,'penduduk'), { maxWidth:260 });
|
||||
layerGroupPenduduk.addLayer(marker);
|
||||
pendudukData.push({ id:parseInt(d.id), lat:pLat, lng:pLng, markerLayer:marker, inRadius });
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Gagal load data:', err));
|
||||
|
||||
/* ============================================================
|
||||
HAPUS
|
||||
============================================================ */
|
||||
function hapusData(id, type) {
|
||||
if (!confirm('Yakin ingin menghapus?')) return;
|
||||
fetch(`php/delete.php?id=${id}&type=${type}`)
|
||||
.then(r=>r.json()).then(r=>{
|
||||
if (r.message && r.message.toLowerCase().includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error||r.message));
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
MODE SELECTOR (pojok kiri bawah)
|
||||
============================================================ */
|
||||
|
||||
/* ============================================================
|
||||
ON CREATED
|
||||
============================================================ */
|
||||
map.on(L.Draw.Event.CREATED, function(e) {
|
||||
var layer = e.layer;
|
||||
|
||||
// Get mode directly from the DOM to ensure reliability
|
||||
var activeMode = 'spbu';
|
||||
var modeInput = document.querySelector('input[name="mode"]:checked');
|
||||
if (modeInput) activeMode = modeInput.value;
|
||||
|
||||
console.log('Creating marker in mode: ' + activeMode);
|
||||
|
||||
if (e.layerType === 'marker') {
|
||||
if (activeMode==='spbu') { drawnItems.addLayer(layer); handlePoint(layer); }
|
||||
else if (activeMode==='masjid') { handleMasjid(layer); }
|
||||
else if (activeMode==='penduduk') { handlePenduduk(layer); }
|
||||
}
|
||||
if (e.layerType==='polyline') { drawnItems.addLayer(layer); handleLine(layer); }
|
||||
if (e.layerType==='polygon') { drawnItems.addLayer(layer); handlePolygon(layer); }
|
||||
});
|
||||
|
||||
/* ── SPBU ── */
|
||||
function handlePoint(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head pf-head-point">⛽ Tambah SPBU</div><div class="pf-body">
|
||||
<label>Nama SPBU</label><input id="pNama" placeholder="contoh: SPBU Sungai Jawi">
|
||||
<label>No. SPBU</label><input id="pNo" placeholder="contoh: 64.761.xx">
|
||||
<label>Status Operasional</label>
|
||||
<select id="pStatus"><option value="24 Jam">24 Jam</option><option value="Tidak">Tidak 24 Jam</option></select>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePoint(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:280 }).openPopup();
|
||||
}
|
||||
function savePoint(lat, lng) {
|
||||
var nama=document.getElementById('pNama').value.trim(), no=document.getElementById('pNo').value.trim(), status=document.getElementById('pStatus').value;
|
||||
if (!nama||!no) { alert('Nama dan No. SPBU wajib diisi!'); return; }
|
||||
fetch('php/insert.php',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({nama,no,status,lat,lng}) })
|
||||
.then(r=>r.json()).then(r=>{ if(r.message&&r.message.includes('berhasil')) location.reload(); else alert('Gagal: '+r.message); });
|
||||
}
|
||||
|
||||
/* ── MASJID ── */
|
||||
function handleMasjid(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
var defaultRadius = 500;
|
||||
window._previewLatLng = ll;
|
||||
|
||||
// Tambahkan marker ke peta agar popup bisa muncul
|
||||
layer.addTo(map);
|
||||
|
||||
var previewCircle = L.circle([ll.lat,ll.lng], { radius:defaultRadius, color:'#f59e0b', fillColor:'#f59e0b', fillOpacity:0.08, weight:2, dashArray:'6 4' }).addTo(map);
|
||||
window._previewCircle = previewCircle;
|
||||
|
||||
recalcAllRadius({ lat:ll.lat, lng:ll.lng, radius:defaultRadius });
|
||||
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head" style="background:linear-gradient(135deg,#92400e,#f59e0b);color:#fff;">🕌 Tambah Masjid</div><div class="pf-body">
|
||||
<label>Nama Masjid</label><input id="mNama" placeholder="contoh: Masjid Al-Falah">
|
||||
<label>PIC Masjid</label><input id="mPIC" placeholder="contoh: Ust. Ahmad">
|
||||
<label>Radius Deteksi</label>
|
||||
<div class="radius-slider-wrap">
|
||||
<input type="range" min="100" max="5000" step="50" value="${defaultRadius}" id="mRadiusSlider" oninput="previewRadius(this.value)">
|
||||
<div class="radius-val"><span>100m</span><span id="mRadiusVal">${defaultRadius} m</span><span>5000m</span></div>
|
||||
</div>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="saveMasjid(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:290 }).openPopup();
|
||||
|
||||
layer.on('popupclose', function() {
|
||||
map.removeLayer(previewCircle);
|
||||
window._previewCircle=null;
|
||||
window._previewLatLng=null;
|
||||
map.removeLayer(layer);
|
||||
recalcAllRadius();
|
||||
});
|
||||
}
|
||||
function previewRadius(val) {
|
||||
val = parseFloat(val);
|
||||
document.getElementById('mRadiusVal').textContent = fmtNum(val,0)+' m';
|
||||
if (window._previewCircle) window._previewCircle.setRadius(val);
|
||||
if (window._previewLatLng) {
|
||||
recalcAllRadius({ lat:window._previewLatLng.lat, lng:window._previewLatLng.lng, radius:val });
|
||||
}
|
||||
}
|
||||
function saveMasjid(lat, lng) {
|
||||
var nama=document.getElementById('mNama').value.trim(), pic=document.getElementById('mPIC').value.trim(), radius=parseFloat(document.getElementById('mRadiusSlider').value);
|
||||
if (!nama||!pic) { alert('Nama dan PIC wajib diisi!'); return; }
|
||||
fetch('php/insert_masjid.php',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({nama,pic,radius,lat,lng}) })
|
||||
.then(r=>r.json()).then(r=>{ if(r.id) location.reload(); else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── PENDUDUK ── */
|
||||
function handlePenduduk(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.addTo(map); // Tambahkan marker ke peta agar popup bisa muncul
|
||||
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head" style="background:linear-gradient(135deg,#701a75,#e879f9);color:#fff;">👨👩👧👦 Tambah Penduduk Miskin</div><div class="pf-body">
|
||||
<label>Nama Kepala Keluarga</label><input id="pdNama" placeholder="contoh: Bapak Suratman">
|
||||
<label>Jumlah Anggota KK</label><input id="pdJml" type="number" min="1" value="1">
|
||||
<label>Alamat</label><input id="pdAlamat" placeholder="contoh: Jl. Imam Bonjol No. 5">
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePenduduk(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:290 }).openPopup();
|
||||
layer.on('popupclose', function() { map.removeLayer(layer); });
|
||||
}
|
||||
function savePenduduk(lat, lng) {
|
||||
var nama_kk=document.getElementById('pdNama').value.trim(), jumlah_anggota=parseInt(document.getElementById('pdJml').value)||1, alamat=document.getElementById('pdAlamat').value.trim();
|
||||
if (!nama_kk||!alamat) { alert('Nama KK dan Alamat wajib diisi!'); return; }
|
||||
fetch('php/insert_penduduk.php',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({nama_kk,jumlah_anggota,alamat,lat,lng}) })
|
||||
.then(r=>r.json()).then(r=>{ if(r.id) location.reload(); else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── LINE ── */
|
||||
function handleLine(layer) {
|
||||
currentLayer=layer;
|
||||
var ll=layer.getLatLngs(), p=getLength(ll); layer._temp={latlngs:ll,panjang:p};
|
||||
L.popup({maxWidth:280}).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf"><div class="pf-head pf-head-line">🛣️ Tambah Jalan</div><div class="pf-body">
|
||||
<label>Nama Jalan</label><input id="lNama" placeholder="contoh: Jl. Ahmad Yani">
|
||||
<label>Klasifikasi</label><select id="lStatus"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select>
|
||||
<div class="pf-info">📏 ${fmtNum(p)} m</div>
|
||||
<button class="pf-btn" onclick="saveLine()">💾 Simpan</button>
|
||||
</div></div>`).openOn(map);
|
||||
}
|
||||
function saveLine() {
|
||||
var layer=currentLayer; if(!layer){alert('Layer tidak ditemukan');return;}
|
||||
var nama=document.getElementById('lNama').value.trim(), status=document.getElementById('lStatus').value;
|
||||
if(!nama){alert('Nama wajib diisi!');return;}
|
||||
fetch('php/insert_line.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama,status,panjang:layer._temp.panjang,geom:JSON.stringify(layer._temp.latlngs)})})
|
||||
.then(r=>r.json()).then(r=>{ if(r.id){layer._db_id=r.id;layer._type='line';map.closePopup();alert('Berhasil!');} else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── POLYGON ── */
|
||||
function handlePolygon(layer) {
|
||||
currentLayer=layer;
|
||||
var ll=layer.getLatLngs(), luas=L.GeometryUtil.geodesicArea(ll[0]); layer._temp={latlngs:ll,luas};
|
||||
L.popup({maxWidth:280}).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf"><div class="pf-head pf-head-polygon">📐 Tambah Parsil</div><div class="pf-body">
|
||||
<label>Nama Area</label><input id="gNama" placeholder="contoh: Lahan A">
|
||||
<label>Status Kepemilikan</label><select id="gStatus"><option value="SHM">SHM</option><option value="HGB">HGB</option><option value="HGU">HGU</option><option value="HP">HP</option></select>
|
||||
<div class="pf-info">📐 ${fmtNum(luas)} m²</div>
|
||||
<button class="pf-btn" onclick="savePolygon()">💾 Simpan</button>
|
||||
</div></div>`).openOn(map);
|
||||
}
|
||||
function savePolygon() {
|
||||
var layer=currentLayer; if(!layer){alert('Layer tidak ditemukan');return;}
|
||||
var nama=document.getElementById('gNama').value.trim(), status=document.getElementById('gStatus').value;
|
||||
if(!nama){alert('Nama wajib diisi!');return;}
|
||||
fetch('php/insert_polygon.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama,status,luas:layer._temp.luas,geom:JSON.stringify(layer._temp.latlngs)})})
|
||||
.then(r=>r.json()).then(r=>{ if(r.id){layer._db_id=r.id;layer._type='polygon';map.closePopup();alert('Berhasil!');} else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── EDIT & DELETE ── */
|
||||
map.on('draw:edited', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (!layer._db_id) return;
|
||||
if (layer._type==='line') fetch('php/update_line.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:layer._db_id,geom:JSON.stringify(layer.getLatLngs()),panjang:getLength(layer.getLatLngs())})});
|
||||
if (layer._type==='polygon') fetch('php/update_polygon.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:layer._db_id,geom:JSON.stringify(layer.getLatLngs()),luas:L.GeometryUtil.geodesicArea(layer.getLatLngs()[0])})});
|
||||
});
|
||||
});
|
||||
map.on('draw:deleted', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (layer._db_id&&layer._type) fetch(`php/delete.php?id=${layer._db_id}&type=${layer._type}`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,331 @@
|
||||
/* ============================================================
|
||||
draw_geometri.js - WebGIS Point, Polyline, Polygon
|
||||
============================================================ */
|
||||
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var layerGroupPoint = L.featureGroup().addTo(map);
|
||||
var currentLayer = null;
|
||||
|
||||
var drawControl = new L.Control.Draw({
|
||||
position: 'topleft',
|
||||
edit: { featureGroup: drawnItems },
|
||||
draw: {
|
||||
marker: true,
|
||||
polyline: { shapeOptions: { color: '#a855f7', weight: 3 } },
|
||||
polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } },
|
||||
rectangle: false,
|
||||
circle: false,
|
||||
circlemarker: false
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
function getLineColor(status) {
|
||||
return status === 'Nasional' ? '#ef4444' : status === 'Provinsi' ? '#38bdf8' : '#a855f7';
|
||||
}
|
||||
|
||||
function getLength(latlngs) {
|
||||
var total = 0;
|
||||
for (var i = 0; i < latlngs.length - 1; i++) {
|
||||
total += latlngs[i].distanceTo(latlngs[i + 1]);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function fmtNum(n, dec = 2) {
|
||||
return parseFloat(n).toLocaleString('id-ID', { maximumFractionDigits: dec });
|
||||
}
|
||||
|
||||
function statusBadge(status, type) {
|
||||
if (type === 'line') {
|
||||
var c = status === 'Nasional' ? 'badge-red' : status === 'Provinsi' ? 'badge-blue' : 'badge-gray';
|
||||
return `<span class="ip-badge ${c}">${status}</span>`;
|
||||
}
|
||||
return `<span class="ip-badge ${status === 'SHM' ? 'badge-green' : 'badge-blue'}">${status}</span>`;
|
||||
}
|
||||
|
||||
function makePointIcon() {
|
||||
var c = '#3b82f6';
|
||||
return L.divIcon({
|
||||
html: `<svg xmlns="http://www.w3.org/2000/svg" width="26" height="38" viewBox="0 0 26 38"><path d="M13 0C5.8 0 0 5.8 0 13c0 9.75 13 25 13 25S26 22.75 26 13C26 5.8 20.2 0 13 0z" fill="${c}" stroke="rgba(255,255,255,.25)" stroke-width="1.5"/><circle cx="13" cy="13" r="5" fill="rgba(0,0,0,.4)"/><circle cx="13" cy="13" r="3" fill="white"/></svg>`,
|
||||
iconSize: [26, 38],
|
||||
iconAnchor: [13, 38],
|
||||
popupAnchor: [0, -40],
|
||||
className: ''
|
||||
});
|
||||
}
|
||||
|
||||
function buildInfoPopup(d, type) {
|
||||
var headerColors = {
|
||||
point: 'linear-gradient(135deg,#1d4ed8,#3b82f6)',
|
||||
line: 'linear-gradient(135deg,#7c3aed,#a855f7)',
|
||||
polygon: 'linear-gradient(135deg,#065f46,#10b981)'
|
||||
};
|
||||
var icons = { point: '📍', line: '▱', polygon: '▰' };
|
||||
var titles = {
|
||||
point: `Point - ${d.nama}`,
|
||||
line: `Jalan - ${d.nama}`,
|
||||
polygon: `Parsil - ${d.nama}`
|
||||
};
|
||||
|
||||
var rows = '';
|
||||
if (type === 'point') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Nama</span><span class="ip-val">${d.nama}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Kode Point</span><span class="ip-val">${d.no_spbu}</span></div>`;
|
||||
} else if (type === 'line') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status, 'line')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Panjang</span><span class="ip-val">${fmtNum(d.panjang)} m</span></div>`;
|
||||
} else if (type === 'polygon') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status, 'polygon')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Luas</span><span class="ip-val">${fmtNum(d.luas)} m²</span></div>`;
|
||||
}
|
||||
|
||||
return `<div class="ip">
|
||||
<div class="ip-head" style="background:${headerColors[type]}">${icons[type]} ${titles[type]}</div>
|
||||
<div class="ip-body">${rows}<button class="ip-del" onclick="hapusData(${d.id},'${type}')">Hapus</button></div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
fetch('../php/show_geometri.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
data.forEach(d => {
|
||||
if (d.type === 'point') {
|
||||
var pointLayer = L.marker([d.latitude, d.longitude], { icon: makePointIcon() });
|
||||
pointLayer._type = 'point';
|
||||
pointLayer._db_id = d.id;
|
||||
pointLayer.bindPopup(buildInfoPopup(d, 'point'), { maxWidth: 260 });
|
||||
layerGroupPoint.addLayer(pointLayer);
|
||||
}
|
||||
|
||||
if (d.type === 'line') {
|
||||
var lineCoords = JSON.parse(d.geom);
|
||||
var lineLayer = L.polyline(lineCoords, { color: getLineColor(d.status), weight: 4, opacity: 0.9 });
|
||||
lineLayer._type = 'line';
|
||||
lineLayer._db_id = d.id;
|
||||
lineLayer.bindPopup(buildInfoPopup(d, 'line'), { maxWidth: 260 });
|
||||
drawnItems.addLayer(lineLayer);
|
||||
}
|
||||
|
||||
if (d.type === 'polygon') {
|
||||
var polygonCoords = JSON.parse(d.geom);
|
||||
var polygonLayer = L.polygon(polygonCoords, {
|
||||
color: '#10b981',
|
||||
weight: 2,
|
||||
fillColor: '#10b981',
|
||||
fillOpacity: 0.3
|
||||
});
|
||||
polygonLayer._type = 'polygon';
|
||||
polygonLayer._db_id = d.id;
|
||||
polygonLayer.bindPopup(buildInfoPopup(d, 'polygon'), { maxWidth: 260 });
|
||||
drawnItems.addLayer(polygonLayer);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Gagal load data:', err));
|
||||
|
||||
function hapusData(id, type) {
|
||||
if (!confirm('Yakin ingin menghapus?')) return;
|
||||
fetch(`../php/delete.php?id=${id}&type=${type}`)
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.message && r.message.toLowerCase().includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error || r.message));
|
||||
});
|
||||
}
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function(e) {
|
||||
var layer = e.layer;
|
||||
|
||||
if (e.layerType === 'marker') {
|
||||
drawnItems.addLayer(layer);
|
||||
handlePoint(layer);
|
||||
}
|
||||
if (e.layerType === 'polyline') {
|
||||
drawnItems.addLayer(layer);
|
||||
handleLine(layer);
|
||||
}
|
||||
if (e.layerType === 'polygon') {
|
||||
drawnItems.addLayer(layer);
|
||||
handlePolygon(layer);
|
||||
}
|
||||
});
|
||||
|
||||
function handlePoint(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.bindPopup(`<div class="pf">
|
||||
<div class="pf-head pf-head-point">📍 Tambah Point</div>
|
||||
<div class="pf-body">
|
||||
<label>Nama Point</label><input id="pNama" placeholder="contoh: Titik Survei A">
|
||||
<label>Kode Point</label><input id="pNo" placeholder="contoh: P-001">
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePoint(${ll.lat},${ll.lng})">Simpan</button>
|
||||
</div>
|
||||
</div>`, { maxWidth: 280 }).openPopup();
|
||||
}
|
||||
|
||||
function savePoint(lat, lng) {
|
||||
var nama = document.getElementById('pNama').value.trim();
|
||||
var no = document.getElementById('pNo').value.trim();
|
||||
var status = '24 Jam';
|
||||
if (!nama || !no) {
|
||||
alert('Nama dan kode point wajib diisi!');
|
||||
return;
|
||||
}
|
||||
fetch('../php/insert.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, no, status, lat, lng })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.message && r.message.includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error || r.message));
|
||||
});
|
||||
}
|
||||
|
||||
function handleLine(layer) {
|
||||
currentLayer = layer;
|
||||
var latlngs = layer.getLatLngs();
|
||||
var panjang = getLength(latlngs);
|
||||
layer._temp = { latlngs, panjang };
|
||||
L.popup({ maxWidth: 280 }).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf">
|
||||
<div class="pf-head pf-head-line">▱ Tambah Jalan</div>
|
||||
<div class="pf-body">
|
||||
<label>Nama Jalan</label><input id="lNama" placeholder="contoh: Jl. Ahmad Yani">
|
||||
<label>Klasifikasi</label><select id="lStatus"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select>
|
||||
<div class="pf-info">${fmtNum(panjang)} m</div>
|
||||
<button class="pf-btn" onclick="saveLine()">Simpan</button>
|
||||
</div>
|
||||
</div>`).openOn(map);
|
||||
}
|
||||
|
||||
function saveLine() {
|
||||
var layer = currentLayer;
|
||||
if (!layer) {
|
||||
alert('Layer tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
var nama = document.getElementById('lNama').value.trim();
|
||||
var status = document.getElementById('lStatus').value;
|
||||
if (!nama) {
|
||||
alert('Nama wajib diisi!');
|
||||
return;
|
||||
}
|
||||
fetch('../php/insert_line.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
nama,
|
||||
status,
|
||||
panjang: layer._temp.panjang,
|
||||
geom: JSON.stringify(layer._temp.latlngs)
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.id) {
|
||||
layer._db_id = r.id;
|
||||
layer._type = 'line';
|
||||
map.closePopup();
|
||||
alert('Berhasil!');
|
||||
} else {
|
||||
alert('Gagal: ' + (r.error || r.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handlePolygon(layer) {
|
||||
currentLayer = layer;
|
||||
var latlngs = layer.getLatLngs();
|
||||
var luas = L.GeometryUtil.geodesicArea(latlngs[0]);
|
||||
layer._temp = { latlngs, luas };
|
||||
L.popup({ maxWidth: 280 }).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf">
|
||||
<div class="pf-head pf-head-polygon">▰ Tambah Parsil</div>
|
||||
<div class="pf-body">
|
||||
<label>Nama Area</label><input id="gNama" placeholder="contoh: Lahan A">
|
||||
<label>Status Kepemilikan</label><select id="gStatus"><option value="SHM">SHM</option><option value="HGB">HGB</option><option value="HGU">HGU</option><option value="HP">HP</option></select>
|
||||
<div class="pf-info">${fmtNum(luas)} m²</div>
|
||||
<button class="pf-btn" onclick="savePolygon()">Simpan</button>
|
||||
</div>
|
||||
</div>`).openOn(map);
|
||||
}
|
||||
|
||||
function savePolygon() {
|
||||
var layer = currentLayer;
|
||||
if (!layer) {
|
||||
alert('Layer tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
var nama = document.getElementById('gNama').value.trim();
|
||||
var status = document.getElementById('gStatus').value;
|
||||
if (!nama) {
|
||||
alert('Nama wajib diisi!');
|
||||
return;
|
||||
}
|
||||
fetch('../php/insert_polygon.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
nama,
|
||||
status,
|
||||
luas: layer._temp.luas,
|
||||
geom: JSON.stringify(layer._temp.latlngs)
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.id) {
|
||||
layer._db_id = r.id;
|
||||
layer._type = 'polygon';
|
||||
map.closePopup();
|
||||
alert('Berhasil!');
|
||||
} else {
|
||||
alert('Gagal: ' + (r.error || r.message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
map.on('draw:edited', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (!layer._db_id) return;
|
||||
if (layer._type === 'line') {
|
||||
fetch('../php/update_line.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: layer._db_id,
|
||||
geom: JSON.stringify(layer.getLatLngs()),
|
||||
panjang: getLength(layer.getLatLngs())
|
||||
})
|
||||
});
|
||||
}
|
||||
if (layer._type === 'polygon') {
|
||||
fetch('../php/update_polygon.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
id: layer._db_id,
|
||||
geom: JSON.stringify(layer.getLatLngs()),
|
||||
luas: L.GeometryUtil.geodesicArea(layer.getLatLngs()[0])
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
map.on('draw:deleted', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (layer._db_id && layer._type) {
|
||||
fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,584 @@
|
||||
/* ============================================================
|
||||
draw_sosial.js — WebGIS Pendataan Sosial Pontianak
|
||||
v3.0 — Smart DSS: Heatmap, Masjid Penanggung, Bantuan, Blind Spot
|
||||
============================================================ */
|
||||
|
||||
/* ── MAP INIT ── */
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors', maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
/* ── LAYER GROUPS ── */
|
||||
var layerGroupMasjid = L.featureGroup().addTo(map);
|
||||
var layerGroupPenduduk = L.featureGroup().addTo(map);
|
||||
var heatmapLayer = null; // inisialisasi setelah data dimuat
|
||||
var showMasjid = true;
|
||||
var showPenduduk = true;
|
||||
var showHeatmap = false;
|
||||
|
||||
/* ── DATA STORAGE ── */
|
||||
var masjidData = [];
|
||||
var pendudukData = [];
|
||||
var blindSpotIds = [];
|
||||
|
||||
/* ── CONSTANTS ── */
|
||||
const JENIS_BANTUAN_OPTS = [
|
||||
{ value: 'BLT / Modal Usaha', icon: '💰' },
|
||||
{ value: 'Bedah Rumah', icon: '🏠' },
|
||||
{ value: 'Air Bersih / PDAM', icon: '💧' },
|
||||
{ value: 'Sembako', icon: '🍚' },
|
||||
{ value: 'Beasiswa Pendidikan', icon: '📚' },
|
||||
];
|
||||
|
||||
function getJenisIcon(jenis) {
|
||||
const found = JENIS_BANTUAN_OPTS.find(o => o.value === jenis);
|
||||
return found ? found.icon : '🤝';
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
TOGGLE LAYERS
|
||||
============================================================ */
|
||||
function toggleLayer(type) {
|
||||
if (type === 'masjid') {
|
||||
showMasjid = !showMasjid;
|
||||
showMasjid ? map.addLayer(layerGroupMasjid) : map.removeLayer(layerGroupMasjid);
|
||||
document.getElementById('btnMasjid').classList.toggle('active', showMasjid);
|
||||
} else if (type === 'penduduk') {
|
||||
showPenduduk = !showPenduduk;
|
||||
showPenduduk ? map.addLayer(layerGroupPenduduk) : map.removeLayer(layerGroupPenduduk);
|
||||
document.getElementById('btnPenduduk').classList.toggle('active', showPenduduk);
|
||||
if (!showPenduduk && showHeatmap) toggleHeatmap(); // heatmap off jika penduduk off
|
||||
} else if (type === 'heatmap') {
|
||||
toggleHeatmap();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleHeatmap() {
|
||||
showHeatmap = !showHeatmap;
|
||||
var btn = document.getElementById('btnHeatmap');
|
||||
if (showHeatmap) {
|
||||
// Heatmap ON: sembunyikan marker penduduk
|
||||
map.removeLayer(layerGroupPenduduk);
|
||||
showPenduduk = false;
|
||||
document.getElementById('btnPenduduk').classList.remove('active');
|
||||
if (heatmapLayer) map.addLayer(heatmapLayer);
|
||||
if (btn) btn.classList.add('active');
|
||||
} else {
|
||||
// Heatmap OFF: kembalikan marker penduduk
|
||||
if (heatmapLayer) map.removeLayer(heatmapLayer);
|
||||
map.addLayer(layerGroupPenduduk);
|
||||
showPenduduk = true;
|
||||
document.getElementById('btnPenduduk').classList.add('active');
|
||||
if (btn) btn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DRAW CONTROL — hanya Marker
|
||||
============================================================ */
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var drawControl = new L.Control.Draw({
|
||||
position: 'topleft',
|
||||
edit: { featureGroup: drawnItems },
|
||||
draw: { marker:true, polyline:false, polygon:false, rectangle:false, circle:false, circlemarker:false }
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
/* ============================================================
|
||||
HELPERS
|
||||
============================================================ */
|
||||
function fmtNum(n, dec=2) {
|
||||
return parseFloat(n).toLocaleString('id-ID', { maximumFractionDigits: dec });
|
||||
}
|
||||
|
||||
function getSkorLabel(skor) {
|
||||
if (skor >= 70) return { label: 'Sangat Rentan', color: '#ef4444', emoji: '🔴' };
|
||||
if (skor >= 40) return { label: 'Rentan', color: '#f97316', emoji: '🟠' };
|
||||
return { label: 'Cukup', color: '#eab308', emoji: '🟡' };
|
||||
}
|
||||
|
||||
function findMasjidPenanggung(pLat, pLng) {
|
||||
var nearest = null;
|
||||
var minDist = Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var d = map.distance([pLat, pLng], [m.lat, m.lng]);
|
||||
if (d <= m.radius && d < minDist) { minDist = d; nearest = m; }
|
||||
});
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/* ── Icons ── */
|
||||
function makeMasjidIcon() {
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="30" height="42" viewBox="0 0 30 42"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.25 15 27 15 27S30 26.25 30 15C30 6.7 23.3 0 15 0z" fill="#f59e0b" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><text x="15" y="20" text-anchor="middle" font-size="13" fill="white">🕌</text></svg>`, iconSize:[30,42], iconAnchor:[15,42], popupAnchor:[0,-44], className:'' });
|
||||
}
|
||||
function makePendudukIcon(inRadius, isBlindspot=false) {
|
||||
var c = isBlindspot ? '#ef4444' : (inRadius ? '#f59e0b' : '#e879f9');
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="22" height="32" viewBox="0 0 22 32"><path d="M11 0C4.9 0 0 4.9 0 11c0 8.25 11 21 11 21S22 19.25 22 11C22 4.9 17.1 0 11 0z" fill="${c}" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><circle cx="11" cy="11" r="4" fill="rgba(0,0,0,.35)"/><circle cx="11" cy="11" r="2.5" fill="white"/></svg>`, iconSize:[22,32], iconAnchor:[11,32], popupAnchor:[0,-34], className: isBlindspot ? 'blindspot-marker' : '' });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
RADIUS LOGIC
|
||||
============================================================ */
|
||||
function recalcAllRadius(previewM = null) {
|
||||
pendudukData.forEach(p => {
|
||||
var inAny = false;
|
||||
var minDist = Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var d = map.distance([p.lat, p.lng], [m.lat, m.lng]);
|
||||
if (d <= m.radius) { inAny = true; if (d < minDist) minDist = d; }
|
||||
});
|
||||
if (previewM) {
|
||||
var d = map.distance([p.lat, p.lng], [previewM.lat, previewM.lng]);
|
||||
if (d <= previewM.radius) { inAny = true; if (d < minDist) minDist = d; }
|
||||
}
|
||||
if (p.inRadius !== inAny) {
|
||||
p.inRadius = inAny;
|
||||
p.markerLayer.setIcon(makePendudukIcon(inAny));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
POPUP BUILDERS
|
||||
============================================================ */
|
||||
|
||||
/* ── POPUP MASJID (2 Tab) ── */
|
||||
function buildMasjidPopup(d) {
|
||||
// Cari daftar penerima di dalam radius masjid ini
|
||||
var penerima = pendudukData
|
||||
.map(p => {
|
||||
var dist = map.distance([p.lat, p.lng], [d.lat, d.lng]);
|
||||
return { ...p, dist, rawData: p.rawData };
|
||||
})
|
||||
.filter(p => p.dist <= d.radius)
|
||||
.sort((a,b) => b.skor - a.skor);
|
||||
|
||||
var rowsPenerima = '';
|
||||
if (penerima.length === 0) {
|
||||
rowsPenerima = `<div style="color:#6b7280;font-size:12px;text-align:center;padding:12px">Belum ada penduduk dalam radius ini.</div>`;
|
||||
} else {
|
||||
penerima.forEach((p, i) => {
|
||||
var sl = getSkorLabel(p.skor);
|
||||
rowsPenerima += `
|
||||
<div style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid #1e2233;">
|
||||
<span style="font-size:16px;min-width:20px">${sl.emoji}</span>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:12px;font-weight:600;color:#e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${p.nama || '—'}</div>
|
||||
<div style="font-size:10px;color:#6b7280">Skor: ${fmtNum(p.skor,1)} · ${sl.label}</div>
|
||||
</div>
|
||||
<span title="${p.jenis}" style="font-size:16px">${getJenisIcon(p.jenis)}</span>
|
||||
</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
return `<div class="ip" style="min-width:280px">
|
||||
<div class="ip-head" style="background:linear-gradient(135deg,#92400e,#f59e0b)">🕌 ${d.nama}</div>
|
||||
<div style="display:flex;border-bottom:1px solid #1e2233;margin-bottom:8px">
|
||||
<button id="tab1_${d.id}" onclick="switchTab(${d.id},1)" style="flex:1;padding:7px;font-size:11px;background:#1e2233;border:none;color:#f59e0b;cursor:pointer;font-weight:600;border-radius:0">📋 Profil</button>
|
||||
<button id="tab2_${d.id}" onclick="switchTab(${d.id},2)" style="flex:1;padding:7px;font-size:11px;background:transparent;border:none;color:#6b7280;cursor:pointer;border-radius:0">👥 Penerima (${penerima.length})</button>
|
||||
</div>
|
||||
<div id="tabContent1_${d.id}" class="ip-body">
|
||||
<div class="ip-row"><span class="ip-label">PIC</span><span class="ip-val">${d.pic}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Radius</span><span class="ip-val" id="radiusDisplay_${d.id}">${fmtNum(d.radius,0)} m</span></div>
|
||||
<div class="radius-slider-wrap">
|
||||
<input type="range" min="100" max="5000" step="50" value="${d.radius}" id="radiusSlider_${d.id}" oninput="onRadiusSlide(${d.id}, this.value)">
|
||||
<div class="radius-val"><span>100m</span><span>5000m</span></div>
|
||||
</div>
|
||||
<button class="pf-btn" style="margin-top:10px" onclick="saveRadius(${d.id})">💾 Simpan Radius</button>
|
||||
<button class="ip-del" onclick="hapusData(${d.id},'masjid')">🗑 Hapus</button>
|
||||
</div>
|
||||
<div id="tabContent2_${d.id}" class="ip-body" style="display:none;max-height:220px;overflow-y:auto">
|
||||
${rowsPenerima}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function switchTab(id, tab) {
|
||||
document.getElementById('tabContent1_' + id).style.display = tab === 1 ? '' : 'none';
|
||||
document.getElementById('tabContent2_' + id).style.display = tab === 2 ? '' : 'none';
|
||||
document.getElementById('tab1_' + id).style.background = tab === 1 ? '#1e2233' : 'transparent';
|
||||
document.getElementById('tab1_' + id).style.color = tab === 1 ? '#f59e0b' : '#6b7280';
|
||||
document.getElementById('tab2_' + id).style.background = tab === 2 ? '#1e2233' : 'transparent';
|
||||
document.getElementById('tab2_' + id).style.color = tab === 2 ? '#f59e0b' : '#6b7280';
|
||||
}
|
||||
|
||||
/* ── POPUP PENDUDUK ── */
|
||||
function buildPendudukPopup(d) {
|
||||
var skor = parseFloat(d.skor_kerentanan) || 0;
|
||||
var sl = getSkorLabel(skor);
|
||||
var pLat = parseFloat(d.latitude), pLng = parseFloat(d.longitude);
|
||||
|
||||
// Cari masjid penanggung
|
||||
var penanggung = findMasjidPenanggung(pLat, pLng);
|
||||
var penanggungHtml = penanggung
|
||||
? `<div class="ip-row"><span class="ip-label">Masjid Penanggung</span><span class="ip-val" style="color:#f59e0b;font-weight:600">🕌 ${penanggung.nama}</span></div>`
|
||||
: `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val" style="color:#ef4444;font-weight:600">⚠️ Blind Spot</span></div>`;
|
||||
|
||||
// Dropdown jenis bantuan
|
||||
var optionsHtml = JENIS_BANTUAN_OPTS.map(o =>
|
||||
`<option value="${o.value}" ${d.jenis_bantuan === o.value ? 'selected' : ''}>${o.icon} ${o.value}</option>`
|
||||
).join('');
|
||||
|
||||
var selectStyle = `width:100%;padding:6px;border-radius:6px;border:1px solid #1e2233;background:#0a0d14;color:#e2e8f0;font-size:12px;`;
|
||||
|
||||
return `<div class="ip">
|
||||
<div class="ip-head" style="background:linear-gradient(135deg,#701a75,#e879f9)">👨👩👧👦 KK — ${d.nama_kk}</div>
|
||||
<div class="ip-body">
|
||||
<div class="ip-row"><span class="ip-label">Jml. Anggota KK</span><span class="ip-val">${d.jumlah_anggota} orang</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Alamat</span><span class="ip-val" style="font-size:11px">${d.alamat}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Penghasilan</span><span class="ip-val">${d.penghasilan}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Kondisi Rumah</span><span class="ip-val">${d.kondisi_rumah}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Sumber Air</span><span class="ip-val">${d.sumber_air}</span></div>
|
||||
<div class="ip-row">
|
||||
<span class="ip-label">Skor Kerentanan</span>
|
||||
<span class="ip-val" style="color:${sl.color};font-weight:700">${sl.emoji} ${fmtNum(skor,1)} — ${sl.label}</span>
|
||||
</div>
|
||||
${penanggungHtml}
|
||||
<div style="margin-top:10px">
|
||||
<label style="font-size:11px;color:#9ca3af;display:block;margin-bottom:4px">🤝 Jenis Bantuan Direkomendasikan</label>
|
||||
<select id="jenisBantuan_${d.id}" style="${selectStyle}">${optionsHtml}</select>
|
||||
<button class="pf-btn" style="margin-top:6px" onclick="saveJenisBantuan(${d.id})">💾 Simpan Bantuan</button>
|
||||
</div>
|
||||
<button class="ip-del" onclick="hapusData(${d.id},'penduduk')">🗑 Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function saveJenisBantuan(id) {
|
||||
var sel = document.getElementById('jenisBantuan_' + id);
|
||||
if (!sel) return;
|
||||
fetch('../php/update_penduduk.php', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id, jenis_bantuan: sel.value })
|
||||
}).then(r => r.json()).then(r => {
|
||||
if (r.message && r.message.includes('Berhasil')) {
|
||||
// Update local data
|
||||
var p = pendudukData.find(x => x.id === id);
|
||||
if (p) p.jenis = sel.value;
|
||||
renderStats();
|
||||
} else {
|
||||
alert('Gagal: ' + r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── RADIUS SLIDER ── */
|
||||
function onRadiusSlide(masjidId, val) {
|
||||
val = parseFloat(val);
|
||||
var el = document.getElementById('radiusDisplay_' + masjidId);
|
||||
if (el) el.textContent = fmtNum(val,0) + ' m';
|
||||
var m = masjidData.find(x => x.id === masjidId);
|
||||
if (m) { m.radius = val; m.circleLayer.setRadius(val); recalcAllRadius(); }
|
||||
}
|
||||
|
||||
function saveRadius(masjidId) {
|
||||
var m = masjidData.find(x => x.id === masjidId);
|
||||
if (!m) return;
|
||||
fetch('../php/update_masjid.php', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ id: masjidId, radius: m.radius })
|
||||
}).then(r => r.json()).then(r => {
|
||||
if (r.message && r.message.includes('Berhasil')) alert('Radius berhasil disimpan!');
|
||||
else alert('Gagal: ' + r.message);
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LOAD DATA
|
||||
============================================================ */
|
||||
fetch('../php/show_sosial.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
var pendudukRaw = [];
|
||||
|
||||
data.forEach(d => {
|
||||
if (d.type === 'masjid') {
|
||||
var mLat=parseFloat(d.latitude), mLng=parseFloat(d.longitude);
|
||||
var mR = parseFloat(d.radius_layanan) || parseFloat(d.radius) || 500;
|
||||
var circle = L.circle([mLat,mLng], { radius:mR, color:'#f59e0b', fillColor:'#f59e0b', fillOpacity:0.08, weight:2, dashArray:'6 4' });
|
||||
layerGroupMasjid.addLayer(circle);
|
||||
var mEntry = { id:parseInt(d.id), lat:mLat, lng:mLng, radius:mR, nama:d.nama, pic:d.pic, circleLayer:circle };
|
||||
masjidData.push(mEntry);
|
||||
var marker = L.marker([mLat,mLng], { icon:makeMasjidIcon() });
|
||||
marker._type='masjid'; marker._db_id=d.id;
|
||||
mEntry.markerLayer = marker;
|
||||
// Bind popup after pendudukData is populated (defer)
|
||||
layerGroupMasjid.addLayer(marker);
|
||||
}
|
||||
if (d.type === 'penduduk') pendudukRaw.push(d);
|
||||
});
|
||||
|
||||
/* Load penduduk setelah masjid siap */
|
||||
var heatPoints = [];
|
||||
pendudukRaw.forEach(d => {
|
||||
var pLat=parseFloat(d.latitude), pLng=parseFloat(d.longitude);
|
||||
var skor = parseFloat(d.skor_kerentanan) || 0;
|
||||
var inRadius=false, minDist=Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var dist = map.distance([pLat,pLng],[m.lat,m.lng]);
|
||||
if (dist<=m.radius && dist<minDist) { minDist=dist; inRadius=true; }
|
||||
});
|
||||
|
||||
var pEntry = {
|
||||
id:parseInt(d.id), lat:pLat, lng:pLng,
|
||||
skor, jenis: d.jenis_bantuan, nama: d.nama_kk,
|
||||
inRadius, rawData: d
|
||||
};
|
||||
pendudukData.push(pEntry);
|
||||
|
||||
var marker = L.marker([pLat,pLng], { icon:makePendudukIcon(inRadius) });
|
||||
marker._type='penduduk'; marker._db_id=d.id;
|
||||
pEntry.markerLayer = marker;
|
||||
marker.bindPopup(() => buildPendudukPopup(d), { maxWidth:300 });
|
||||
layerGroupPenduduk.addLayer(marker);
|
||||
|
||||
// Heatmap point: [lat, lng, intensity 0-1]
|
||||
heatPoints.push([pLat, pLng, skor / 100]);
|
||||
});
|
||||
|
||||
// Now bind masjid popups (pendudukData is ready)
|
||||
masjidData.forEach(m => {
|
||||
m.markerLayer.bindPopup(() => buildMasjidPopup(m), { maxWidth:310 });
|
||||
});
|
||||
|
||||
// Init heatmap layer
|
||||
if (typeof L.heatLayer !== 'undefined') {
|
||||
heatmapLayer = L.heatLayer(heatPoints, {
|
||||
radius: 35, blur: 25, maxZoom: 16,
|
||||
gradient: { 0.0: '#3b82f6', 0.4: '#eab308', 0.7: '#f97316', 1.0: '#ef4444' }
|
||||
});
|
||||
}
|
||||
|
||||
// Post-load tasks
|
||||
checkBlindSpots();
|
||||
renderStats();
|
||||
})
|
||||
.catch(err => console.error('Gagal load data:', err));
|
||||
|
||||
/* ============================================================
|
||||
BLIND SPOT DETECTION
|
||||
============================================================ */
|
||||
function checkBlindSpots() {
|
||||
blindSpotIds = [];
|
||||
pendudukData.forEach(p => {
|
||||
if (!p.inRadius && p.skor >= 70) {
|
||||
blindSpotIds.push(p.id);
|
||||
// Mark icon merah
|
||||
p.markerLayer.setIcon(makePendudukIcon(false, true));
|
||||
}
|
||||
});
|
||||
|
||||
var panel = document.getElementById('blindSpotPanel');
|
||||
if (!panel) return;
|
||||
if (blindSpotIds.length > 0) {
|
||||
panel.style.display = 'flex';
|
||||
document.getElementById('blindSpotCount').textContent = blindSpotIds.length;
|
||||
} else {
|
||||
panel.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function zoomToBlindSpots() {
|
||||
if (blindSpotIds.length === 0) return;
|
||||
var bounds = [];
|
||||
pendudukData.forEach(p => {
|
||||
if (blindSpotIds.includes(p.id)) bounds.push([p.lat, p.lng]);
|
||||
});
|
||||
if (bounds.length > 0) map.fitBounds(bounds, { padding: [60,60] });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
STATS PANEL
|
||||
============================================================ */
|
||||
function renderStats() {
|
||||
var total = pendudukData.length;
|
||||
var sangat = pendudukData.filter(p => p.skor >= 70).length;
|
||||
var rentan = pendudukData.filter(p => p.skor >= 40 && p.skor < 70).length;
|
||||
var cukup = pendudukData.filter(p => p.skor < 40).length;
|
||||
var bs = blindSpotIds.length;
|
||||
|
||||
var jenisCounts = {};
|
||||
JENIS_BANTUAN_OPTS.forEach(o => jenisCounts[o.value] = 0);
|
||||
pendudukData.forEach(p => {
|
||||
if (jenisCounts[p.jenis] !== undefined) jenisCounts[p.jenis]++;
|
||||
else jenisCounts['BLT / Modal Usaha']++;
|
||||
});
|
||||
|
||||
var jenisRows = JENIS_BANTUAN_OPTS.map(o =>
|
||||
`<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;font-size:11px">
|
||||
<span style="color:#9ca3af">${o.icon} ${o.value}</span>
|
||||
<span style="font-weight:700;color:#e2e8f0">${jenisCounts[o.value]}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
|
||||
var el = document.getElementById('statsContent');
|
||||
if (!el) return;
|
||||
el.innerHTML = `
|
||||
<div class="stat-row" style="margin-bottom:10px">
|
||||
<div class="stat-item"><div class="stat-num">${total}</div><div class="stat-lbl">Total Warga</div></div>
|
||||
<div class="stat-item" style="color:#ef4444"><div class="stat-num" style="color:#ef4444">${sangat}</div><div class="stat-lbl">🔴 Sgt. Rentan</div></div>
|
||||
<div class="stat-item"><div class="stat-num" style="color:#f97316">${rentan}</div><div class="stat-lbl">🟠 Rentan</div></div>
|
||||
<div class="stat-item"><div class="stat-num" style="color:#eab308">${cukup}</div><div class="stat-lbl">🟡 Cukup</div></div>
|
||||
</div>
|
||||
<div style="border-top:1px solid #1e2233;padding-top:8px;margin-bottom:8px">
|
||||
<div style="font-size:10px;color:#6b7280;font-weight:600;margin-bottom:6px;text-transform:uppercase;letter-spacing:0.05em">Jenis Bantuan Dibutuhkan</div>
|
||||
${jenisRows}
|
||||
</div>
|
||||
<div style="border-top:1px solid #1e2233;padding-top:8px">
|
||||
<div style="display:flex;justify-content:space-between;font-size:11px">
|
||||
<span style="color:#9ca3af">⚠️ Blind Spot (skor ≥70)</span>
|
||||
<span style="font-weight:700;color:${bs>0?'#ef4444':'#4ade80'}">${bs} KK</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function toggleStats() {
|
||||
var content = document.getElementById('statsContent');
|
||||
var btn = document.getElementById('statsToggleBtn');
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block';
|
||||
btn.textContent = '▲';
|
||||
} else {
|
||||
content.style.display = 'none';
|
||||
btn.textContent = '▼';
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
HAPUS
|
||||
============================================================ */
|
||||
function hapusData(id, type) {
|
||||
if (!confirm('Yakin ingin menghapus?')) return;
|
||||
fetch(`../php/delete.php?id=${id}&type=${type}`)
|
||||
.then(r=>r.json()).then(r=>{
|
||||
if (r.message && r.message.toLowerCase().includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error||r.message));
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
ON CREATED — hanya Marker
|
||||
============================================================ */
|
||||
map.on(L.Draw.Event.CREATED, function(e) {
|
||||
var layer = e.layer;
|
||||
if (e.layerType !== 'marker') return;
|
||||
var activeMode = 'masjid';
|
||||
var modeInput = document.querySelector('input[name="mode"]:checked');
|
||||
if (modeInput) activeMode = modeInput.value;
|
||||
if (activeMode === 'masjid') { handleMasjid(layer); }
|
||||
else if (activeMode === 'penduduk') { handlePenduduk(layer); }
|
||||
});
|
||||
|
||||
/* ── MASJID ── */
|
||||
function handleMasjid(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
var defaultRadius = 500;
|
||||
window._previewLatLng = ll;
|
||||
layer.addTo(map);
|
||||
var previewCircle = L.circle([ll.lat,ll.lng], { radius:defaultRadius, color:'#f59e0b', fillColor:'#f59e0b', fillOpacity:0.08, weight:2, dashArray:'6 4' }).addTo(map);
|
||||
window._previewCircle = previewCircle;
|
||||
recalcAllRadius({ lat:ll.lat, lng:ll.lng, radius:defaultRadius });
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head" style="background:linear-gradient(135deg,#92400e,#f59e0b);color:#fff;">🕌 Tambah Masjid</div><div class="pf-body">
|
||||
<label>Nama Masjid</label><input id="mNama" placeholder="contoh: Masjid Al-Falah">
|
||||
<label>PIC Masjid</label><input id="mPIC" placeholder="contoh: Ust. Ahmad">
|
||||
<label>Radius Deteksi</label>
|
||||
<div class="radius-slider-wrap">
|
||||
<input type="range" min="100" max="5000" step="50" value="${defaultRadius}" id="mRadiusSlider" oninput="previewRadius(this.value)">
|
||||
<div class="radius-val"><span>100m</span><span id="mRadiusVal">${defaultRadius} m</span><span>5000m</span></div>
|
||||
</div>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="saveMasjid(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:290 }).openPopup();
|
||||
layer.on('popupclose', function() {
|
||||
map.removeLayer(previewCircle);
|
||||
window._previewCircle=null; window._previewLatLng=null;
|
||||
map.removeLayer(layer);
|
||||
recalcAllRadius();
|
||||
});
|
||||
}
|
||||
function previewRadius(val) {
|
||||
val = parseFloat(val);
|
||||
document.getElementById('mRadiusVal').textContent = fmtNum(val,0)+' m';
|
||||
if (window._previewCircle) window._previewCircle.setRadius(val);
|
||||
if (window._previewLatLng) {
|
||||
recalcAllRadius({ lat:window._previewLatLng.lat, lng:window._previewLatLng.lng, radius:val });
|
||||
}
|
||||
}
|
||||
function saveMasjid(lat, lng) {
|
||||
var nama=document.getElementById('mNama').value.trim(), pic=document.getElementById('mPIC').value.trim(), radius=parseFloat(document.getElementById('mRadiusSlider').value);
|
||||
if (!nama||!pic) { alert('Nama dan PIC wajib diisi!'); return; }
|
||||
fetch('../php/insert_masjid.php',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({nama,pic,radius,lat,lng}) })
|
||||
.then(r=>r.json())
|
||||
.then(r=>{ if(r.id) location.reload(); else alert('Gagal: '+(r.error||r.message)); })
|
||||
.catch(e => alert('Terjadi kesalahan jaringan atau server saat menyimpan masjid.'));
|
||||
}
|
||||
|
||||
/* ── PENDUDUK ── */
|
||||
function handlePenduduk(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.addTo(map);
|
||||
var selectStyle = "width:100%; margin-bottom:10px; padding:6px; border-radius:6px; border:1px solid #1e2233; background:#0a0d14; color:#e2e8f0;";
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head" style="background:linear-gradient(135deg,#701a75,#e879f9);color:#fff;">👨👩👧👦 Tambah Penduduk Miskin</div><div class="pf-body">
|
||||
<label>Nama Kepala Keluarga</label><input id="pdNama" placeholder="contoh: Bapak Suratman">
|
||||
<label>Jumlah Anggota KK</label><input id="pdJml" type="number" min="1" value="1">
|
||||
<label>Alamat</label><input id="pdAlamat" placeholder="Sedang mengambil alamat riil... ⏳">
|
||||
<label>Penghasilan Per Bulan (50%)</label>
|
||||
<select id="pdPenghasilan" style="${selectStyle}">
|
||||
<option value="< 500rb">< 500rb</option>
|
||||
<option value="500rb - 1jt">500rb - 1jt</option>
|
||||
<option value="> 1jt">> 1jt</option>
|
||||
</select>
|
||||
<label>Kondisi Rumah (30%)</label>
|
||||
<select id="pdRumah" style="${selectStyle}">
|
||||
<option value="Bambu/Tepas">Bambu/Tepas</option>
|
||||
<option value="Kayu">Kayu</option>
|
||||
<option value="Tembok">Tembok</option>
|
||||
</select>
|
||||
<label>Sumber Air Bersih (20%)</label>
|
||||
<select id="pdAir" style="${selectStyle}">
|
||||
<option value="Sungai">Sungai</option>
|
||||
<option value="Sumur">Sumur</option>
|
||||
<option value="PDAM">PDAM</option>
|
||||
</select>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePenduduk(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:310 }).openPopup();
|
||||
|
||||
// Reverse Geocoding OSM Nominatim
|
||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${ll.lat}&lon=${ll.lng}&zoom=18&addressdetails=1`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
var addrInput = document.getElementById('pdAlamat');
|
||||
if (addrInput) {
|
||||
if (data && data.display_name) {
|
||||
addrInput.value = data.display_name;
|
||||
} else {
|
||||
addrInput.placeholder = 'Gagal menemukan alamat, harap isi manual';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
var addrInput = document.getElementById('pdAlamat');
|
||||
if (addrInput) addrInput.placeholder = 'Koneksi API OSM gagal, isi manual';
|
||||
});
|
||||
|
||||
layer.on('popupclose', function() { map.removeLayer(layer); });
|
||||
}
|
||||
function savePenduduk(lat, lng) {
|
||||
var nama_kk=document.getElementById('pdNama').value.trim(),
|
||||
jumlah_anggota=parseInt(document.getElementById('pdJml').value)||1,
|
||||
alamat=document.getElementById('pdAlamat').value.trim(),
|
||||
penghasilan=document.getElementById('pdPenghasilan').value,
|
||||
kondisi_rumah=document.getElementById('pdRumah').value,
|
||||
sumber_air=document.getElementById('pdAir').value;
|
||||
if (!nama_kk||!alamat) { alert('Nama KK dan Alamat wajib diisi!'); return; }
|
||||
fetch('../php/insert_penduduk.php',{
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify({nama_kk, jumlah_anggota, alamat, lat, lng, penghasilan, kondisi_rumah, sumber_air})
|
||||
})
|
||||
.then(r=>r.json())
|
||||
.then(r=>{ if(r.id) location.reload(); else alert('Gagal: '+(r.error||r.message)); })
|
||||
.catch(e => alert('Terjadi kesalahan jaringan atau server saat menyimpan penduduk. Coba periksa koneksi atau error log.'));
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/* ============================================================
|
||||
draw_sosial_public.js — Dashboard Publik (READ ONLY)
|
||||
v3.0 — Heatmap, Masjid Penanggung, Stats, Blind Spot
|
||||
Tanpa fitur CRUD (tambah/edit/hapus)
|
||||
============================================================ */
|
||||
|
||||
/* ── MAP INIT ── */
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors', maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
/* ── LAYER GROUPS ── */
|
||||
var layerGroupMasjid = L.featureGroup().addTo(map);
|
||||
var layerGroupPenduduk = L.featureGroup().addTo(map);
|
||||
var heatmapLayer = null;
|
||||
var showMasjid = true;
|
||||
var showPenduduk = true;
|
||||
var showHeatmap = false;
|
||||
|
||||
/* ── DATA STORAGE ── */
|
||||
var masjidData = [];
|
||||
var pendudukData = [];
|
||||
var blindSpotIds = [];
|
||||
|
||||
/* ── CONSTANTS ── */
|
||||
const JENIS_BANTUAN_OPTS = [
|
||||
{ value: 'BLT / Modal Usaha', icon: '💰' },
|
||||
{ value: 'Bedah Rumah', icon: '🏠' },
|
||||
{ value: 'Air Bersih / PDAM', icon: '💧' },
|
||||
{ value: 'Sembako', icon: '🍚' },
|
||||
{ value: 'Beasiswa Pendidikan', icon: '📚' },
|
||||
];
|
||||
function getJenisIcon(jenis) {
|
||||
const found = JENIS_BANTUAN_OPTS.find(o => o.value === jenis);
|
||||
return found ? found.icon : '🤝';
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
TOGGLE LAYERS
|
||||
============================================================ */
|
||||
function toggleLayer(type) {
|
||||
if (type === 'masjid') {
|
||||
showMasjid = !showMasjid;
|
||||
showMasjid ? map.addLayer(layerGroupMasjid) : map.removeLayer(layerGroupMasjid);
|
||||
document.getElementById('btnMasjid').classList.toggle('active', showMasjid);
|
||||
} else if (type === 'penduduk') {
|
||||
showPenduduk = !showPenduduk;
|
||||
showPenduduk ? map.addLayer(layerGroupPenduduk) : map.removeLayer(layerGroupPenduduk);
|
||||
document.getElementById('btnPenduduk').classList.toggle('active', showPenduduk);
|
||||
if (!showPenduduk && showHeatmap) toggleHeatmap();
|
||||
} else if (type === 'heatmap') {
|
||||
toggleHeatmap();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleHeatmap() {
|
||||
showHeatmap = !showHeatmap;
|
||||
var btn = document.getElementById('btnHeatmap');
|
||||
if (showHeatmap) {
|
||||
map.removeLayer(layerGroupPenduduk);
|
||||
showPenduduk = false;
|
||||
document.getElementById('btnPenduduk').classList.remove('active');
|
||||
if (heatmapLayer) map.addLayer(heatmapLayer);
|
||||
if (btn) btn.classList.add('active');
|
||||
} else {
|
||||
if (heatmapLayer) map.removeLayer(heatmapLayer);
|
||||
map.addLayer(layerGroupPenduduk);
|
||||
showPenduduk = true;
|
||||
document.getElementById('btnPenduduk').classList.add('active');
|
||||
if (btn) btn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
HELPERS
|
||||
============================================================ */
|
||||
function fmtNum(n, dec=2) {
|
||||
return parseFloat(n).toLocaleString('id-ID', { maximumFractionDigits: dec });
|
||||
}
|
||||
function getSkorLabel(skor) {
|
||||
if (skor >= 70) return { label: 'Sangat Rentan', color: '#ef4444', emoji: '🔴' };
|
||||
if (skor >= 40) return { label: 'Rentan', color: '#f97316', emoji: '🟠' };
|
||||
return { label: 'Cukup', color: '#eab308', emoji: '🟡' };
|
||||
}
|
||||
function findMasjidPenanggung(pLat, pLng) {
|
||||
var nearest = null, minDist = Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var d = map.distance([pLat, pLng], [m.lat, m.lng]);
|
||||
if (d <= m.radius && d < minDist) { minDist = d; nearest = m; }
|
||||
});
|
||||
return nearest;
|
||||
}
|
||||
|
||||
/* ── Icons ── */
|
||||
function makeMasjidIcon() {
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="30" height="42" viewBox="0 0 30 42"><path d="M15 0C6.7 0 0 6.7 0 15c0 11.25 15 27 15 27S30 26.25 30 15C30 6.7 23.3 0 15 0z" fill="#f59e0b" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><text x="15" y="20" text-anchor="middle" font-size="13" fill="white">🕌</text></svg>`, iconSize:[30,42], iconAnchor:[15,42], popupAnchor:[0,-44], className:'' });
|
||||
}
|
||||
function makePendudukIcon(inRadius, isBlindspot=false) {
|
||||
var c = isBlindspot ? '#ef4444' : (inRadius ? '#f59e0b' : '#e879f9');
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="22" height="32" viewBox="0 0 22 32"><path d="M11 0C4.9 0 0 4.9 0 11c0 8.25 11 21 11 21S22 19.25 22 11C22 4.9 17.1 0 11 0z" fill="${c}" stroke="rgba(255,255,255,.3)" stroke-width="1.5"/><circle cx="11" cy="11" r="4" fill="rgba(0,0,0,.35)"/><circle cx="11" cy="11" r="2.5" fill="white"/></svg>`, iconSize:[22,32], iconAnchor:[11,32], popupAnchor:[0,-34], className: isBlindspot ? 'blindspot-marker' : '' });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
POPUP BUILDERS — READ ONLY
|
||||
============================================================ */
|
||||
|
||||
/* ── POPUP MASJID ── */
|
||||
function buildMasjidPopup(d) {
|
||||
var penerima = pendudukData
|
||||
.map(p => ({ ...p, dist: map.distance([p.lat, p.lng], [d.lat, d.lng]) }))
|
||||
.filter(p => p.dist <= d.radius)
|
||||
.sort((a, b) => b.skor - a.skor);
|
||||
|
||||
var rowsPenerima = penerima.length === 0
|
||||
? `<div style="color:#6b7280;font-size:12px;text-align:center;padding:12px">Belum ada penduduk dalam radius ini.</div>`
|
||||
: penerima.map(p => {
|
||||
var sl = getSkorLabel(p.skor);
|
||||
return `<div style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid #1e2233;">
|
||||
<span style="font-size:16px;min-width:20px">${sl.emoji}</span>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:12px;font-weight:600;color:#e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${p.nama || '—'}</div>
|
||||
<div style="font-size:10px;color:#6b7280">Skor: ${fmtNum(p.skor,1)} · ${sl.label}</div>
|
||||
</div>
|
||||
<span title="${p.jenis}" style="font-size:16px">${getJenisIcon(p.jenis)}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
return `<div class="ip" style="min-width:280px">
|
||||
<div class="ip-head" style="background:linear-gradient(135deg,#92400e,#f59e0b)">🕌 ${d.nama}</div>
|
||||
<div style="display:flex;border-bottom:1px solid #1e2233;margin-bottom:8px">
|
||||
<button id="tab1_${d.id}" onclick="switchTab(${d.id},1)" style="flex:1;padding:7px;font-size:11px;background:#1e2233;border:none;color:#f59e0b;cursor:pointer;font-weight:600">📋 Profil</button>
|
||||
<button id="tab2_${d.id}" onclick="switchTab(${d.id},2)" style="flex:1;padding:7px;font-size:11px;background:transparent;border:none;color:#6b7280;cursor:pointer">👥 Penerima (${penerima.length})</button>
|
||||
</div>
|
||||
<div id="tabContent1_${d.id}" class="ip-body">
|
||||
<div class="ip-row"><span class="ip-label">PIC</span><span class="ip-val">${d.pic}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Radius Layanan</span><span class="ip-val">${fmtNum(d.radius,0)} m</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Jml. Ditanggung</span><span class="ip-val" style="color:#f59e0b;font-weight:700">${penerima.length} KK</span></div>
|
||||
</div>
|
||||
<div id="tabContent2_${d.id}" class="ip-body" style="display:none;max-height:220px;overflow-y:auto">
|
||||
${rowsPenerima}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function switchTab(id, tab) {
|
||||
document.getElementById('tabContent1_' + id).style.display = tab === 1 ? '' : 'none';
|
||||
document.getElementById('tabContent2_' + id).style.display = tab === 2 ? '' : 'none';
|
||||
document.getElementById('tab1_' + id).style.background = tab === 1 ? '#1e2233' : 'transparent';
|
||||
document.getElementById('tab1_' + id).style.color = tab === 1 ? '#f59e0b' : '#6b7280';
|
||||
document.getElementById('tab2_' + id).style.background = tab === 2 ? '#1e2233' : 'transparent';
|
||||
document.getElementById('tab2_' + id).style.color = tab === 2 ? '#f59e0b' : '#6b7280';
|
||||
}
|
||||
|
||||
/* ── POPUP PENDUDUK ── */
|
||||
function buildPendudukPopup(d) {
|
||||
var skor = parseFloat(d.skor_kerentanan) || 0;
|
||||
var sl = getSkorLabel(skor);
|
||||
var pLat = parseFloat(d.latitude), pLng = parseFloat(d.longitude);
|
||||
var penanggung = findMasjidPenanggung(pLat, pLng);
|
||||
|
||||
var penanggungHtml = penanggung
|
||||
? `<div class="ip-row"><span class="ip-label">Masjid Penanggung</span><span class="ip-val" style="color:#f59e0b;font-weight:600">🕌 ${penanggung.nama}</span></div>`
|
||||
: `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val" style="color:#ef4444;font-weight:600">⚠️ Blind Spot</span></div>`;
|
||||
|
||||
var jenisIcon = getJenisIcon(d.jenis_bantuan);
|
||||
|
||||
return `<div class="ip">
|
||||
<div class="ip-head" style="background:linear-gradient(135deg,#701a75,#e879f9)">👨👩👧👦 KK — ${d.nama_kk}</div>
|
||||
<div class="ip-body">
|
||||
<div class="ip-row"><span class="ip-label">Jml. Anggota KK</span><span class="ip-val">${d.jumlah_anggota} orang</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Alamat</span><span class="ip-val" style="font-size:11px">${d.alamat}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Penghasilan</span><span class="ip-val">${d.penghasilan}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Kondisi Rumah</span><span class="ip-val">${d.kondisi_rumah}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Sumber Air</span><span class="ip-val">${d.sumber_air}</span></div>
|
||||
<div class="ip-row">
|
||||
<span class="ip-label">Skor Kerentanan</span>
|
||||
<span class="ip-val" style="color:${sl.color};font-weight:700">${sl.emoji} ${fmtNum(skor,1)} — ${sl.label}</span>
|
||||
</div>
|
||||
${penanggungHtml}
|
||||
<div class="ip-row">
|
||||
<span class="ip-label">Jenis Bantuan</span>
|
||||
<span class="ip-val">${jenisIcon} ${d.jenis_bantuan || '—'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LOAD DATA
|
||||
============================================================ */
|
||||
fetch('../php/show_sosial.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
var pendudukRaw = [];
|
||||
|
||||
data.forEach(d => {
|
||||
if (d.type === 'masjid') {
|
||||
var mLat=parseFloat(d.latitude), mLng=parseFloat(d.longitude);
|
||||
var mR = parseFloat(d.radius_layanan) || parseFloat(d.radius) || 500;
|
||||
var circle = L.circle([mLat,mLng], { radius:mR, color:'#f59e0b', fillColor:'#f59e0b', fillOpacity:0.08, weight:2, dashArray:'6 4' });
|
||||
layerGroupMasjid.addLayer(circle);
|
||||
var mEntry = { id:parseInt(d.id), lat:mLat, lng:mLng, radius:mR, nama:d.nama, pic:d.pic, circleLayer:circle };
|
||||
masjidData.push(mEntry);
|
||||
var marker = L.marker([mLat,mLng], { icon:makeMasjidIcon() });
|
||||
marker._type='masjid'; marker._db_id=d.id;
|
||||
mEntry.markerLayer = marker;
|
||||
layerGroupMasjid.addLayer(marker);
|
||||
}
|
||||
if (d.type === 'penduduk') pendudukRaw.push(d);
|
||||
});
|
||||
|
||||
var heatPoints = [];
|
||||
pendudukRaw.forEach(d => {
|
||||
var pLat=parseFloat(d.latitude), pLng=parseFloat(d.longitude);
|
||||
var skor = parseFloat(d.skor_kerentanan) || 0;
|
||||
var inRadius=false, minDist=Infinity;
|
||||
masjidData.forEach(m => {
|
||||
var dist = map.distance([pLat,pLng],[m.lat,m.lng]);
|
||||
if (dist<=m.radius && dist<minDist) { minDist=dist; inRadius=true; }
|
||||
});
|
||||
|
||||
var pEntry = { id:parseInt(d.id), lat:pLat, lng:pLng, skor, jenis:d.jenis_bantuan, nama:d.nama_kk, inRadius, rawData:d };
|
||||
pendudukData.push(pEntry);
|
||||
|
||||
var marker = L.marker([pLat,pLng], { icon:makePendudukIcon(inRadius) });
|
||||
marker._type='penduduk'; marker._db_id=d.id;
|
||||
pEntry.markerLayer = marker;
|
||||
marker.bindPopup(() => buildPendudukPopup(d), { maxWidth:300 });
|
||||
layerGroupPenduduk.addLayer(marker);
|
||||
|
||||
heatPoints.push([pLat, pLng, skor / 100]);
|
||||
});
|
||||
|
||||
// Bind masjid popups
|
||||
masjidData.forEach(m => {
|
||||
m.markerLayer.bindPopup(() => buildMasjidPopup(m), { maxWidth:310 });
|
||||
});
|
||||
|
||||
// Init heatmap
|
||||
if (typeof L.heatLayer !== 'undefined') {
|
||||
heatmapLayer = L.heatLayer(heatPoints, {
|
||||
radius: 35, blur: 25, maxZoom: 16,
|
||||
gradient: { 0.0:'#3b82f6', 0.4:'#eab308', 0.7:'#f97316', 1.0:'#ef4444' }
|
||||
});
|
||||
}
|
||||
|
||||
checkBlindSpots();
|
||||
renderStats();
|
||||
})
|
||||
.catch(err => console.error('Gagal load data:', err));
|
||||
|
||||
/* ============================================================
|
||||
BLIND SPOT
|
||||
============================================================ */
|
||||
function checkBlindSpots() {
|
||||
blindSpotIds = [];
|
||||
pendudukData.forEach(p => {
|
||||
if (!p.inRadius && p.skor >= 70) {
|
||||
blindSpotIds.push(p.id);
|
||||
p.markerLayer.setIcon(makePendudukIcon(false, true));
|
||||
}
|
||||
});
|
||||
var panel = document.getElementById('blindSpotPanel');
|
||||
if (!panel) return;
|
||||
if (blindSpotIds.length > 0) {
|
||||
panel.style.display = 'flex';
|
||||
document.getElementById('blindSpotCount').textContent = blindSpotIds.length;
|
||||
} else {
|
||||
panel.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function zoomToBlindSpots() {
|
||||
if (blindSpotIds.length === 0) return;
|
||||
var bounds = pendudukData.filter(p => blindSpotIds.includes(p.id)).map(p => [p.lat, p.lng]);
|
||||
if (bounds.length > 0) map.fitBounds(bounds, { padding:[60,60] });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
STATS PANEL
|
||||
============================================================ */
|
||||
function renderStats() {
|
||||
var total = pendudukData.length;
|
||||
var sangat = pendudukData.filter(p => p.skor >= 70).length;
|
||||
var rentan = pendudukData.filter(p => p.skor >= 40 && p.skor < 70).length;
|
||||
var cukup = pendudukData.filter(p => p.skor < 40).length;
|
||||
var bs = blindSpotIds.length;
|
||||
|
||||
var jenisCounts = {};
|
||||
JENIS_BANTUAN_OPTS.forEach(o => jenisCounts[o.value] = 0);
|
||||
pendudukData.forEach(p => {
|
||||
if (jenisCounts[p.jenis] !== undefined) jenisCounts[p.jenis]++;
|
||||
else jenisCounts['BLT / Modal Usaha']++;
|
||||
});
|
||||
|
||||
var jenisRows = JENIS_BANTUAN_OPTS.map(o =>
|
||||
`<div style="display:flex;justify-content:space-between;align-items:center;padding:3px 0;font-size:11px">
|
||||
<span style="color:#9ca3af">${o.icon} ${o.value}</span>
|
||||
<span style="font-weight:700;color:#e2e8f0">${jenisCounts[o.value]}</span>
|
||||
</div>`
|
||||
).join('');
|
||||
|
||||
var el = document.getElementById('statsContent');
|
||||
if (!el) return;
|
||||
el.innerHTML = `
|
||||
<div class="stat-row" style="margin-bottom:10px">
|
||||
<div class="stat-item"><div class="stat-num">${total}</div><div class="stat-lbl">Total Warga</div></div>
|
||||
<div class="stat-item"><div class="stat-num" style="color:#ef4444">${sangat}</div><div class="stat-lbl">🔴 Sgt. Rentan</div></div>
|
||||
<div class="stat-item"><div class="stat-num" style="color:#f97316">${rentan}</div><div class="stat-lbl">🟠 Rentan</div></div>
|
||||
<div class="stat-item"><div class="stat-num" style="color:#eab308">${cukup}</div><div class="stat-lbl">🟡 Cukup</div></div>
|
||||
</div>
|
||||
<div style="border-top:1px solid #1e2233;padding-top:8px;margin-bottom:8px">
|
||||
<div style="font-size:10px;color:#6b7280;font-weight:600;margin-bottom:6px;text-transform:uppercase;letter-spacing:0.05em">Jenis Bantuan Dibutuhkan</div>
|
||||
${jenisRows}
|
||||
</div>
|
||||
<div style="border-top:1px solid #1e2233;padding-top:8px">
|
||||
<div style="display:flex;justify-content:space-between;font-size:11px">
|
||||
<span style="color:#9ca3af">⚠️ Blind Spot (skor ≥70)</span>
|
||||
<span style="font-weight:700;color:${bs>0?'#ef4444':'#4ade80'}">${bs} KK</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function toggleStats() {
|
||||
var content = document.getElementById('statsContent');
|
||||
var btn = document.getElementById('statsToggleBtn');
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block'; btn.textContent = '▲';
|
||||
} else {
|
||||
content.style.display = 'none'; btn.textContent = '▼';
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
/* ============================================================
|
||||
draw_spbu.js — WebGIS SPBU Pontianak
|
||||
Handles: SPBU (Point), Jalan (Line), Parsil (Polygon)
|
||||
============================================================ */
|
||||
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors', maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
/* ── LAYER GROUPS ── */
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var layerGroup24 = L.featureGroup().addTo(map);
|
||||
var layerGroupTidak = L.featureGroup().addTo(map);
|
||||
|
||||
var show24 = true;
|
||||
var showTidak = true;
|
||||
|
||||
let currentLayer = null;
|
||||
|
||||
/* ============================================================
|
||||
TOGGLE LAYERS
|
||||
============================================================ */
|
||||
function toggleLayer(type) {
|
||||
if (type === '24jam') {
|
||||
show24 = !show24;
|
||||
show24 ? map.addLayer(layerGroup24) : map.removeLayer(layerGroup24);
|
||||
document.getElementById('btn24').classList.toggle('active', show24);
|
||||
} else if (type === 'tidak') {
|
||||
showTidak = !showTidak;
|
||||
showTidak ? map.addLayer(layerGroupTidak) : map.removeLayer(layerGroupTidak);
|
||||
document.getElementById('btnTidak').classList.toggle('active', showTidak);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DRAW CONTROL
|
||||
============================================================ */
|
||||
var drawControl = new L.Control.Draw({
|
||||
position: 'topleft',
|
||||
edit: { featureGroup: drawnItems },
|
||||
draw: {
|
||||
marker: true,
|
||||
polyline: { shapeOptions: { color: '#a855f7', weight: 3 } },
|
||||
polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } },
|
||||
rectangle: false, circle: false, circlemarker: false
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
/* ============================================================
|
||||
HELPERS
|
||||
============================================================ */
|
||||
function getLineColor(s) {
|
||||
return s==='Nasional' ? '#ef4444' : s==='Provinsi' ? '#38bdf8' : '#a855f7';
|
||||
}
|
||||
function getLength(ll) {
|
||||
let t=0;
|
||||
for(let i=0;i<ll.length-1;i++) t+=ll[i].distanceTo(ll[i+1]);
|
||||
return t;
|
||||
}
|
||||
function fmtNum(n, dec=2) {
|
||||
return parseFloat(n).toLocaleString('id-ID', { maximumFractionDigits: dec });
|
||||
}
|
||||
function statusBadge(s, type) {
|
||||
if (type==='point') return s==='24 Jam' ? `<span class="ip-badge badge-green">24 Jam</span>` : `<span class="ip-badge badge-red">Tidak 24 Jam</span>`;
|
||||
if (type==='line') { let c=s==='Nasional'?'badge-red':s==='Provinsi'?'badge-blue':'badge-gray'; return `<span class="ip-badge ${c}">${s}</span>`; }
|
||||
return `<span class="ip-badge ${s==='SHM'?'badge-green':'badge-blue'}">${s}</span>`;
|
||||
}
|
||||
|
||||
/* ── Icons ── */
|
||||
function makeIcon(status) {
|
||||
var c = status==='24 Jam' ? '#4ade80' : '#f87171';
|
||||
return L.divIcon({ html:`<svg xmlns="http://www.w3.org/2000/svg" width="26" height="38" viewBox="0 0 26 38"><path d="M13 0C5.8 0 0 5.8 0 13c0 9.75 13 25 13 25S26 22.75 26 13C26 5.8 20.2 0 13 0z" fill="${c}" stroke="rgba(255,255,255,.25)" stroke-width="1.5"/><circle cx="13" cy="13" r="5" fill="rgba(0,0,0,.4)"/><circle cx="13" cy="13" r="3" fill="white"/></svg>`, iconSize:[26,38], iconAnchor:[13,38], popupAnchor:[0,-40], className:'' });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
POPUP BUILDERS
|
||||
============================================================ */
|
||||
function buildInfoPopup(d, type) {
|
||||
const hc = { point:'linear-gradient(135deg,#1d4ed8,#3b82f6)', line:'linear-gradient(135deg,#7c3aed,#a855f7)', polygon:'linear-gradient(135deg,#065f46,#10b981)' };
|
||||
const ic = { point:'⛽', line:'🛣️', polygon:'📐' };
|
||||
const ti = { point:`SPBU — ${d.nama}`, line:`Jalan — ${d.nama}`, polygon:`Parsil — ${d.nama}` };
|
||||
|
||||
let rows = '';
|
||||
if (type==='point') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">No. SPBU</span><span class="ip-val">${d.no_spbu}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'point')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Koordinat</span><span class="ip-val" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span></div>`;
|
||||
} else if (type==='line') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'line')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Panjang</span><span class="ip-val">${fmtNum(d.panjang)} m</span></div>`;
|
||||
} else if (type==='polygon') {
|
||||
rows = `<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status,'polygon')}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Luas</span><span class="ip-val">${fmtNum(d.luas)} m²</span></div>`;
|
||||
}
|
||||
|
||||
return `<div class="ip"><div class="ip-head" style="background:${hc[type]}">${ic[type]} ${ti[type]}</div><div class="ip-body">${rows}<button class="ip-del" onclick="hapusData(${d.id},'${type}')">🗑 Hapus</button></div></div>`;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
LOAD DATA
|
||||
============================================================ */
|
||||
fetch('../php/show_spbu.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
data.forEach(d => {
|
||||
if (d.type === 'point') {
|
||||
var layer = L.marker([d.latitude, d.longitude], { icon: makeIcon(d.status) });
|
||||
layer._type='point'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'point'), { maxWidth:260 });
|
||||
d.status==='24 Jam' ? layerGroup24.addLayer(layer) : layerGroupTidak.addLayer(layer);
|
||||
}
|
||||
if (d.type === 'line') {
|
||||
var coords = JSON.parse(d.geom);
|
||||
var layer = L.polyline(coords, { color:getLineColor(d.status), weight:4, opacity:0.9 });
|
||||
layer._type='line'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'line'), { maxWidth:260 });
|
||||
drawnItems.addLayer(layer);
|
||||
}
|
||||
if (d.type === 'polygon') {
|
||||
var coords = JSON.parse(d.geom);
|
||||
var layer = L.polygon(coords, { color:'#10b981', weight:2, fillColor:'#10b981', fillOpacity:0.3 });
|
||||
layer._type='polygon'; layer._db_id=d.id;
|
||||
layer.bindPopup(buildInfoPopup(d,'polygon'), { maxWidth:260 });
|
||||
drawnItems.addLayer(layer);
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Gagal load data:', err));
|
||||
|
||||
/* ============================================================
|
||||
HAPUS
|
||||
============================================================ */
|
||||
function hapusData(id, type) {
|
||||
if (!confirm('Yakin ingin menghapus?')) return;
|
||||
fetch(`../php/delete.php?id=${id}&type=${type}`)
|
||||
.then(r=>r.json()).then(r=>{
|
||||
if (r.message && r.message.toLowerCase().includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error||r.message));
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
ON CREATED
|
||||
============================================================ */
|
||||
map.on(L.Draw.Event.CREATED, function(e) {
|
||||
var layer = e.layer;
|
||||
|
||||
if (e.layerType === 'marker') { drawnItems.addLayer(layer); handlePoint(layer); }
|
||||
if (e.layerType === 'polyline') { drawnItems.addLayer(layer); handleLine(layer); }
|
||||
if (e.layerType === 'polygon') { drawnItems.addLayer(layer); handlePolygon(layer); }
|
||||
});
|
||||
|
||||
/* ── SPBU ── */
|
||||
function handlePoint(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.bindPopup(`<div class="pf"><div class="pf-head pf-head-point">⛽ Tambah SPBU</div><div class="pf-body">
|
||||
<label>Nama SPBU</label><input id="pNama" placeholder="contoh: SPBU Sungai Jawi">
|
||||
<label>No. SPBU</label><input id="pNo" placeholder="contoh: 64.761.xx">
|
||||
<label>Status Operasional</label>
|
||||
<select id="pStatus"><option value="24 Jam">24 Jam</option><option value="Tidak">Tidak 24 Jam</option></select>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePoint(${ll.lat},${ll.lng})">💾 Simpan</button>
|
||||
</div></div>`, { maxWidth:280 }).openPopup();
|
||||
}
|
||||
function savePoint(lat, lng) {
|
||||
var nama=document.getElementById('pNama').value.trim(), no=document.getElementById('pNo').value.trim(), status=document.getElementById('pStatus').value;
|
||||
if (!nama||!no) { alert('Nama dan No. SPBU wajib diisi!'); return; }
|
||||
fetch('../php/insert.php',{ method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({nama,no,status,lat,lng}) })
|
||||
.then(r=>r.json()).then(r=>{ if(r.message&&r.message.includes('berhasil')) location.reload(); else alert('Gagal: '+r.message); });
|
||||
}
|
||||
|
||||
/* ── LINE ── */
|
||||
function handleLine(layer) {
|
||||
currentLayer=layer;
|
||||
var ll=layer.getLatLngs(), p=getLength(ll); layer._temp={latlngs:ll,panjang:p};
|
||||
L.popup({maxWidth:280}).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf"><div class="pf-head pf-head-line">🛣️ Tambah Jalan</div><div class="pf-body">
|
||||
<label>Nama Jalan</label><input id="lNama" placeholder="contoh: Jl. Ahmad Yani">
|
||||
<label>Klasifikasi</label><select id="lStatus"><option value="Nasional">Nasional</option><option value="Provinsi">Provinsi</option><option value="Kabupaten">Kabupaten</option></select>
|
||||
<div class="pf-info">📏 ${fmtNum(p)} m</div>
|
||||
<button class="pf-btn" onclick="saveLine()">💾 Simpan</button>
|
||||
</div></div>`).openOn(map);
|
||||
}
|
||||
function saveLine() {
|
||||
var layer=currentLayer; if(!layer){alert('Layer tidak ditemukan');return;}
|
||||
var nama=document.getElementById('lNama').value.trim(), status=document.getElementById('lStatus').value;
|
||||
if(!nama){alert('Nama wajib diisi!');return;}
|
||||
fetch('../php/insert_line.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama,status,panjang:layer._temp.panjang,geom:JSON.stringify(layer._temp.latlngs)})})
|
||||
.then(r=>r.json()).then(r=>{ if(r.id){layer._db_id=r.id;layer._type='line';map.closePopup();alert('Berhasil!');} else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── POLYGON ── */
|
||||
function handlePolygon(layer) {
|
||||
currentLayer=layer;
|
||||
var ll=layer.getLatLngs(), luas=L.GeometryUtil.geodesicArea(ll[0]); layer._temp={latlngs:ll,luas};
|
||||
L.popup({maxWidth:280}).setLatLng(layer.getBounds().getCenter()).setContent(`<div class="pf"><div class="pf-head pf-head-polygon">📐 Tambah Parsil</div><div class="pf-body">
|
||||
<label>Nama Area</label><input id="gNama" placeholder="contoh: Lahan A">
|
||||
<label>Status Kepemilikan</label><select id="gStatus"><option value="SHM">SHM</option><option value="HGB">HGB</option><option value="HGU">HGU</option><option value="HP">HP</option></select>
|
||||
<div class="pf-info">📐 ${fmtNum(luas)} m²</div>
|
||||
<button class="pf-btn" onclick="savePolygon()">💾 Simpan</button>
|
||||
</div></div>`).openOn(map);
|
||||
}
|
||||
function savePolygon() {
|
||||
var layer=currentLayer; if(!layer){alert('Layer tidak ditemukan');return;}
|
||||
var nama=document.getElementById('gNama').value.trim(), status=document.getElementById('gStatus').value;
|
||||
if(!nama){alert('Nama wajib diisi!');return;}
|
||||
fetch('../php/insert_polygon.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({nama,status,luas:layer._temp.luas,geom:JSON.stringify(layer._temp.latlngs)})})
|
||||
.then(r=>r.json()).then(r=>{ if(r.id){layer._db_id=r.id;layer._type='polygon';map.closePopup();alert('Berhasil!');} else alert('Gagal: '+(r.error||r.message)); });
|
||||
}
|
||||
|
||||
/* ── EDIT & DELETE ── */
|
||||
map.on('draw:edited', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (!layer._db_id) return;
|
||||
if (layer._type==='line') fetch('../php/update_line.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:layer._db_id,geom:JSON.stringify(layer.getLatLngs()),panjang:getLength(layer.getLatLngs())})});
|
||||
if (layer._type==='polygon') fetch('../php/update_polygon.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:layer._db_id,geom:JSON.stringify(layer.getLatLngs()),luas:L.GeometryUtil.geodesicArea(layer.getLatLngs()[0])})});
|
||||
});
|
||||
});
|
||||
map.on('draw:deleted', function(e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
if (layer._db_id&&layer._type) fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
/* ============================================================
|
||||
draw_spbu_point.js - WebGIS SPBU Point Only
|
||||
============================================================ */
|
||||
|
||||
var map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13);
|
||||
L.control.zoom({ position: 'bottomright' }).addTo(map);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
var drawnItems = new L.FeatureGroup().addTo(map);
|
||||
var layerGroup24 = L.featureGroup().addTo(map);
|
||||
var layerGroupTidak = L.featureGroup().addTo(map);
|
||||
var show24 = true;
|
||||
var showTidak = true;
|
||||
|
||||
function toggleLayer(type) {
|
||||
if (type === '24jam') {
|
||||
show24 = !show24;
|
||||
show24 ? map.addLayer(layerGroup24) : map.removeLayer(layerGroup24);
|
||||
document.getElementById('btn24').classList.toggle('active', show24);
|
||||
} else if (type === 'tidak') {
|
||||
showTidak = !showTidak;
|
||||
showTidak ? map.addLayer(layerGroupTidak) : map.removeLayer(layerGroupTidak);
|
||||
document.getElementById('btnTidak').classList.toggle('active', showTidak);
|
||||
}
|
||||
}
|
||||
|
||||
var drawControl = new L.Control.Draw({
|
||||
position: 'topleft',
|
||||
edit: { featureGroup: drawnItems },
|
||||
draw: {
|
||||
marker: true,
|
||||
polyline: false,
|
||||
polygon: false,
|
||||
rectangle: false,
|
||||
circle: false,
|
||||
circlemarker: false
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
|
||||
function makeIcon(status) {
|
||||
var c = status === '24 Jam' ? '#4ade80' : '#f87171';
|
||||
return L.divIcon({
|
||||
html: `<svg xmlns="http://www.w3.org/2000/svg" width="26" height="38" viewBox="0 0 26 38"><path d="M13 0C5.8 0 0 5.8 0 13c0 9.75 13 25 13 25S26 22.75 26 13C26 5.8 20.2 0 13 0z" fill="${c}" stroke="rgba(255,255,255,.25)" stroke-width="1.5"/><circle cx="13" cy="13" r="5" fill="rgba(0,0,0,.4)"/><circle cx="13" cy="13" r="3" fill="white"/></svg>`,
|
||||
iconSize: [26, 38],
|
||||
iconAnchor: [13, 38],
|
||||
popupAnchor: [0, -40],
|
||||
className: ''
|
||||
});
|
||||
}
|
||||
|
||||
function statusBadge(status) {
|
||||
return status === '24 Jam'
|
||||
? `<span class="ip-badge badge-green">24 Jam</span>`
|
||||
: `<span class="ip-badge badge-red">Tidak 24 Jam</span>`;
|
||||
}
|
||||
|
||||
function buildInfoPopup(d) {
|
||||
return `<div class="ip">
|
||||
<div class="ip-head" style="background:linear-gradient(135deg,#1d4ed8,#3b82f6)">⛽ SPBU - ${d.nama}</div>
|
||||
<div class="ip-body">
|
||||
<div class="ip-row"><span class="ip-label">No. SPBU</span><span class="ip-val">${d.no_spbu}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status)}</span></div>
|
||||
<div class="ip-row"><span class="ip-label">Koordinat</span><span class="ip-val" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span></div>
|
||||
<button class="ip-del" onclick="hapusData(${d.id},'point')">Hapus</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
fetch('../php/show_spbu.php')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
data.forEach(d => {
|
||||
var layer = L.marker([d.latitude, d.longitude], { icon: makeIcon(d.status) });
|
||||
layer._type = 'point';
|
||||
layer._db_id = d.id;
|
||||
layer.bindPopup(buildInfoPopup(d), { maxWidth: 260 });
|
||||
d.status === '24 Jam' ? layerGroup24.addLayer(layer) : layerGroupTidak.addLayer(layer);
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Gagal load data SPBU:', err));
|
||||
|
||||
function hapusData(id, type) {
|
||||
if (!confirm('Yakin ingin menghapus SPBU ini?')) return;
|
||||
fetch(`../php/delete.php?id=${id}&type=${type}`)
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.message && r.message.toLowerCase().includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error || r.message));
|
||||
});
|
||||
}
|
||||
|
||||
map.on(L.Draw.Event.CREATED, function(e) {
|
||||
if (e.layerType !== 'marker') return;
|
||||
var layer = e.layer;
|
||||
drawnItems.addLayer(layer);
|
||||
handlePoint(layer);
|
||||
});
|
||||
|
||||
function handlePoint(layer) {
|
||||
var ll = layer.getLatLng();
|
||||
layer.bindPopup(`<div class="pf">
|
||||
<div class="pf-head pf-head-point">⛽ Tambah SPBU</div>
|
||||
<div class="pf-body">
|
||||
<label>Nama SPBU</label><input id="pNama" placeholder="contoh: SPBU Sungai Jawi">
|
||||
<label>No. SPBU</label><input id="pNo" placeholder="contoh: 64.761.xx">
|
||||
<label>Status Operasional</label>
|
||||
<select id="pStatus"><option value="24 Jam">24 Jam</option><option value="Tidak">Tidak 24 Jam</option></select>
|
||||
<div class="pf-info">📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}</div>
|
||||
<button class="pf-btn" onclick="savePoint(${ll.lat},${ll.lng})">Simpan</button>
|
||||
</div>
|
||||
</div>`, { maxWidth: 280 }).openPopup();
|
||||
}
|
||||
|
||||
function savePoint(lat, lng) {
|
||||
var nama = document.getElementById('pNama').value.trim();
|
||||
var no = document.getElementById('pNo').value.trim();
|
||||
var status = document.getElementById('pStatus').value;
|
||||
if (!nama || !no) {
|
||||
alert('Nama dan No. SPBU wajib diisi!');
|
||||
return;
|
||||
}
|
||||
fetch('../php/insert.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ nama, no, status, lat, lng })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (r.message && r.message.includes('berhasil')) location.reload();
|
||||
else alert('Gagal: ' + (r.error || r.message));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user