6215b4367a
Sistem WebGIS analisis sebaran penduduk miskin dan jangkauan rumah ibadah. Fitur: peta interaktif Leaflet.js, CRUD rumah ibadah & penduduk miskin, auto-kategorisasi haversine, heatmap, export PDF/CSV, dashboard analitik, manajemen pengguna (superadmin/admin), login sistem. Teknologi: PHP, MySQL, Leaflet.js, OpenStreetMap, Nominatim Geocoding, mPDF. Catatan: jalankan `composer install` di folder uas/ untuk install dependencies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
209 lines
9.5 KiB
PHP
209 lines
9.5 KiB
PHP
<?php
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
include 'koneksi.php';
|
|
|
|
// ── Data dari DB ──────────────────────────────────────────────────────────
|
|
$allMasjid = mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM masjid"), MYSQLI_ASSOC);
|
|
$allKemiskinan = mysqli_fetch_all(mysqli_query($conn, "SELECT * FROM kemiskinan"), MYSQLI_ASSOC);
|
|
|
|
// ── Haversine (meter) ─────────────────────────────────────────────────────
|
|
function haversine($lat1, $lng1, $lat2, $lng2) {
|
|
$R = 6371000;
|
|
$phi1 = deg2rad($lat1); $phi2 = deg2rad($lat2);
|
|
$dp = deg2rad($lat2 - $lat1);
|
|
$dl = deg2rad($lng2 - $lng1);
|
|
$a = sin($dp/2)**2 + cos($phi1)*cos($phi2)*sin($dl/2)**2;
|
|
return (int) round(2 * $R * asin(sqrt($a)));
|
|
}
|
|
|
|
// ── Analisis coverage (sama dengan logika JS) ─────────────────────────────
|
|
$kkCoverage = []; // kkId -> [masjid, ...]
|
|
$masjidCoverage = []; // masjidId -> ['kk'=>[], 'jiwa'=>0]
|
|
foreach ($allMasjid as $m) {
|
|
$masjidCoverage[$m['id']] = ['kk' => [], 'jiwa' => 0];
|
|
}
|
|
foreach ($allKemiskinan as $kk) {
|
|
$kkCoverage[$kk['id']] = [];
|
|
foreach ($allMasjid as $m) {
|
|
$dist = haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']);
|
|
if ($dist <= (int)$m['radius_m']) {
|
|
$kkCoverage[$kk['id']][] = $m;
|
|
$masjidCoverage[$m['id']]['kk'][] = $kk;
|
|
$masjidCoverage[$m['id']]['jiwa'] += (int)$kk['jumlah_kk'];
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Statistik ringkasan ───────────────────────────────────────────────────
|
|
$totalKK = count($allKemiskinan);
|
|
$totalJiwa = array_sum(array_column($allKemiskinan, 'jumlah_kk'));
|
|
$countT = 0; $jiwaT = 0;
|
|
foreach ($allKemiskinan as $kk) {
|
|
if (count($kkCoverage[$kk['id']]) > 0) { $countT++; $jiwaT += (int)$kk['jumlah_kk']; }
|
|
}
|
|
$countTT = $totalKK - $countT;
|
|
$jiwaTT = $totalJiwa - $jiwaT;
|
|
$pct = $totalKK > 0 ? number_format(($countT / $totalKK) * 100, 1) : '0.0';
|
|
$pctTT = $totalKK > 0 ? number_format((($countTT) / $totalKK) * 100, 1) : '0.0';
|
|
|
|
// ── Tanggal dalam format Indonesia ───────────────────────────────────────
|
|
$bulan = ['','Januari','Februari','Maret','April','Mei','Juni',
|
|
'Juli','Agustus','September','Oktober','November','Desember'];
|
|
$tgl = date('j') . ' ' . $bulan[(int)date('n')] . ' ' . date('Y');
|
|
|
|
// ── Baris tabel masjid ────────────────────────────────────────────────────
|
|
$rowsMasjid = '';
|
|
foreach ($allMasjid as $i => $m) {
|
|
$cov = $masjidCoverage[$m['id']];
|
|
$bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb';
|
|
$rowsMasjid .= "
|
|
<tr style='background:{$bg};'>
|
|
<td style='text-align:center;'>" . ($i+1) . "</td>
|
|
<td>{$m['nama_masjid']}</td>
|
|
<td style='text-align:center;'>{$m['radius_m']}</td>
|
|
<td style='text-align:center;'><b>" . count($cov['kk']) . "</b></td>
|
|
<td style='text-align:center;'><b>{$cov['jiwa']}</b></td>
|
|
</tr>";
|
|
}
|
|
if (!$rowsMasjid) $rowsMasjid = "<tr><td colspan='5' style='text-align:center;color:#999;'>Belum ada data masjid.</td></tr>";
|
|
|
|
// ── Baris tabel KK miskin ─────────────────────────────────────────────────
|
|
$rowsKK = '';
|
|
foreach ($allKemiskinan as $i => $kk) {
|
|
$covers = $kkCoverage[$kk['id']];
|
|
$covered = count($covers) > 0;
|
|
|
|
// Jarak ke masjid terdekat
|
|
$minDist = '-';
|
|
if (count($allMasjid) > 0) {
|
|
$dists = array_map(fn($m) => haversine($kk['lat'], $kk['lng'], $m['lat'], $m['lng']), $allMasjid);
|
|
$minDist = min($dists);
|
|
}
|
|
|
|
$statusTxt = $covered
|
|
? '<span style="color:#1a7a3e;font-weight:bold;">Terjangkau</span>'
|
|
: '<span style="color:#c0392b;font-weight:bold;">Tidak Terjangkau</span>';
|
|
$masjidNames = $covered ? implode(', ', array_column($covers, 'nama_masjid')) : '-';
|
|
$bg = ($i % 2 === 0) ? '#ffffff' : '#f9fafb';
|
|
|
|
$pkhTxt = $kk['pkh'] ? '<span style="color:#155724;font-weight:bold;">✓</span>' : '-';
|
|
$bpntTxt = $kk['bpnt'] ? '<span style="color:#004085;font-weight:bold;">✓</span>' : '-';
|
|
$bltTxt = $kk['blt'] ? '<span style="color:#856404;font-weight:bold;">✓</span>' : '-';
|
|
|
|
$rowsKK .= "
|
|
<tr style='background:{$bg};'>
|
|
<td style='text-align:center;'>" . ($i+1) . "</td>
|
|
<td>{$kk['nama_kk']}</td>
|
|
<td style='text-align:center;'>{$kk['jumlah_kk']}</td>
|
|
<td style='font-size:9px;'>{$kk['alamat']}</td>
|
|
<td style='text-align:center;'>{$pkhTxt}</td>
|
|
<td style='text-align:center;'>{$bpntTxt}</td>
|
|
<td style='text-align:center;'>{$bltTxt}</td>
|
|
<td style='text-align:center;'>{$statusTxt}</td>
|
|
<td style='font-size:9px;'>{$masjidNames}</td>
|
|
<td style='text-align:center;'>{$minDist}</td>
|
|
</tr>";
|
|
}
|
|
if (!$rowsKK) $rowsKK = "<tr><td colspan='10' style='text-align:center;color:#999;'>Belum ada data kemiskinan.</td></tr>";
|
|
|
|
// ── Template HTML untuk mPDF ──────────────────────────────────────────────
|
|
$html = <<<HTML
|
|
<style>
|
|
body { font-family: sans-serif; font-size: 11px; color: #222; }
|
|
h1 { font-size: 16px; text-align: center; margin-bottom: 2px; color: #1a3a5c; }
|
|
.subtitle { text-align: center; font-size: 10px; color: #555; margin-bottom: 16px; }
|
|
.section { font-size: 12px; font-weight: bold; color: #1a3a5c;
|
|
border-bottom: 2px solid #1a3a5c; padding-bottom: 3px; margin: 14px 0 8px; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 10px; }
|
|
th { background: #1a3a5c; color: white; padding: 6px 5px; text-align: center; }
|
|
td { padding: 5px; border-bottom: 1px solid #e0e0e0; vertical-align: top; }
|
|
.stat-grid { width: 100%; border-collapse: collapse; margin-bottom: 4px; }
|
|
.stat-cell { width: 25%; padding: 8px; text-align: center; border: 1px solid #ddd; }
|
|
.stat-big { font-size: 22px; font-weight: bold; }
|
|
.stat-lbl { font-size: 9px; color: #666; margin-top: 2px; }
|
|
.green { color: #1a7a3e; }
|
|
.red { color: #c0392b; }
|
|
</style>
|
|
|
|
<h1>Laporan Analisis Kemiskinan & Jangkauan Masjid</h1>
|
|
<div class="subtitle">Tanggal Cetak: {$tgl} | Sumber Data: Sistem WebGIS</div>
|
|
|
|
<div class="section">Ringkasan Statistik</div>
|
|
<table class="stat-grid">
|
|
<tr>
|
|
<td class="stat-cell">
|
|
<div class="stat-big">{$totalKK}</div>
|
|
<div class="stat-lbl">Total KK Miskin</div>
|
|
</td>
|
|
<td class="stat-cell">
|
|
<div class="stat-big">{$totalJiwa}</div>
|
|
<div class="stat-lbl">Total Jiwa</div>
|
|
</td>
|
|
<td class="stat-cell">
|
|
<div class="stat-big green">{$countT} ({$pct}%)</div>
|
|
<div class="stat-lbl">KK Terjangkau • {$jiwaT} jiwa</div>
|
|
</td>
|
|
<td class="stat-cell">
|
|
<div class="stat-big red">{$countTT} ({$pctTT}%)</div>
|
|
<div class="stat-lbl">KK Tidak Terjangkau • {$jiwaTT} jiwa</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<div class="section">Statistik per Masjid</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th width="5%">No</th>
|
|
<th width="35%">Nama Masjid</th>
|
|
<th width="15%">Radius (m)</th>
|
|
<th width="20%">KK Terjangkau</th>
|
|
<th width="25%">Jiwa Terjangkau</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>{$rowsMasjid}</tbody>
|
|
</table>
|
|
|
|
<div class="section">Daftar KK Miskin & Status Jangkauan</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th width="4%">No</th>
|
|
<th width="14%">Nama KK</th>
|
|
<th width="5%">Jiwa</th>
|
|
<th width="15%">Alamat</th>
|
|
<th width="5%">PKH</th>
|
|
<th width="5%">BPNT</th>
|
|
<th width="5%">BLT</th>
|
|
<th width="10%">Status</th>
|
|
<th width="22%">Dijangkau Masjid</th>
|
|
<th width="15%">Jarak Terdekat (m)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>{$rowsKK}</tbody>
|
|
</table>
|
|
HTML;
|
|
|
|
// ── Generate PDF ──────────────────────────────────────────────────────────
|
|
try {
|
|
$mpdf = new \Mpdf\Mpdf([
|
|
'mode' => 'utf-8',
|
|
'format' => 'A4-L', // landscape untuk tabel lebar
|
|
'margin_top' => 14,
|
|
'margin_bottom' => 14,
|
|
'margin_left' => 12,
|
|
'margin_right' => 12,
|
|
]);
|
|
$mpdf->SetTitle('Laporan Analisis Kemiskinan & Jangkauan Masjid');
|
|
$mpdf->WriteHTML($html);
|
|
$fname = 'laporan_kemiskinan_' . date('Y-m-d') . '.pdf';
|
|
$mpdf->Output($fname, 'D'); // 'D' = force download
|
|
} catch (\Mpdf\MpdfException $e) {
|
|
http_response_code(500);
|
|
echo '<pre>Gagal membuat PDF: ' . htmlspecialchars($e->getMessage()) . "\n\n"
|
|
. "Pastikan extension PHP GD aktif di XAMPP:\n"
|
|
. " 1. Buka C:\\xampp\\php\\php.ini\n"
|
|
. " 2. Cari ;extension=gd dan hapus tanda titik koma\n"
|
|
. " 3. Restart Apache di XAMPP Control Panel</pre>";
|
|
}
|