/* ============================================================ 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;i24 Jam` : `Tidak 24 Jam`; if (type==='line') { let c=s==='Nasional'?'badge-red':s==='Provinsi'?'badge-blue':'badge-gray'; return `${s}`; } return `${s}`; } /* ── Icons ── */ function makeIcon(status) { var c = status==='24 Jam' ? '#4ade80' : '#f87171'; return L.divIcon({ html:``, iconSize:[26,38], iconAnchor:[13,38], popupAnchor:[0,-40], className:'' }); } function makeMasjidIcon() { return L.divIcon({ html:`🕌`, iconSize:[30,42], iconAnchor:[15,42], popupAnchor:[0,-44], className:'' }); } function makePendudukIcon(inRadius) { var c = inRadius ? '#f59e0b' : '#e879f9'; return L.divIcon({ html:``, 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 = `
No. SPBU${d.no_spbu}
Status${statusBadge(d.status,'point')}
Koordinat${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}
`; } else if (type==='line') { rows = `
Status${statusBadge(d.status,'line')}
Panjang${fmtNum(d.panjang)} m
`; } else if (type==='polygon') { rows = `
Status${statusBadge(d.status,'polygon')}
Luas${fmtNum(d.luas)} m²
`; } else if (type==='masjid') { rows = `
PIC${d.pic}
Radius${fmtNum(d.radius,0)} m
100m5000m
`; } else if (type==='penduduk') { rows = `
Jml. Anggota KK${d.jumlah_anggota} orang
Alamat${d.alamat}
Koordinat${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}
`; } return `
${ic[type]} ${ti[type]}
${rows}
`; } 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 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(`
⛽ Tambah SPBU
📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}
`, { 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(`
🕌 Tambah Masjid
100m${defaultRadius} m5000m
📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}
`, { 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(`
👨‍👩‍👧‍👦 Tambah Penduduk Miskin
📍 ${ll.lat.toFixed(6)}, ${ll.lng.toFixed(6)}
`, { 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(`
🛣️ Tambah Jalan
📏 ${fmtNum(p)} m
`).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(`
📐 Tambah Parsil
📐 ${fmtNum(luas)} m²
`).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}`); }); });