This commit is contained in:
2026-05-12 10:16:16 +07:00
parent bae0569701
commit c2efc4414f
37 changed files with 2222 additions and 18 deletions
+29
View File
@@ -0,0 +1,29 @@
# Rancangan Sistem Geospasial untuk Kontribusi Pengentasan Kemiskinan
Ringkasan singkat: rancangan ini menggabungkan data titik (SPBU, fasilitas), jaringan jalan (polyline), parsel tanah (polygon dengan luas otomatis), dan peta tematik (choropleth). Tujuan: menyediakan basis data spasial untuk analisis kebutuhan bantuan, akses layanan, dan prioritas intervensi.
Komponen:
- Data: sampel sensus, kemiskinan, infrastruktur, peta batas administrasi, penggunaan lahan, parsel tanah.
- Backend: API PHP ringan untuk CRUD GeoJSON + penyimpanan file atau database spatial (PostGIS direkomendasikan).
- Frontend: Leaflet + plugins (Draw, Turf.js) untuk editing, pengukuran, dan visualisasi layer groups.
- Analitik: agregasi statistik per kelurahan/kecamatan untuk choropleth (tingkat kemiskinan, akses fasilitas).
Alur kerja singkat:
1. Kumpulkan & validasi data atribut (nama, id, status ekonomis, luas parsel).
2. Masukkan ke sistem: titik/line/polygon.
3. Hitung atribut turunan: luas parsel (m²), distance-to-nearest-facility, road-condition index.
4. Hasilkan peta tematik & laporan untuk prioritisasi intervensi.
Rekomendasi teknis singkat:
- Gunakan PostGIS untuk query spasial yang efisien.
- Simpan snapshot GeoJSON untuk interoperabilitas.
- Bangun API otentikasi dan logging perubahan (audit trail) untuk kebijakan bantuan.
Catatan: folder lain di repo ini berisi contoh starter untuk tiap fitur (uji_spbu, fitur_jalan_parsel, layer_groups_choropleth).
Global Leaflet loader:
- File: `global/leaflet-loader.js` — tambahkan tag berikut ke halaman HTML Anda untuk memuat Leaflet dari satu sumber global:
<script src="../global/leaflet-loader.js"></script>
Gunakan `rancangan_sistem/index.html` sebagai contoh integrasi: halaman akan memanggil `../get_rumah_ibadah.php` dan `../get_penduduk_miskin.php` dari root repository dan menambahkan data ke peta.
@@ -0,0 +1,26 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
$stmt = $conn->prepare("DELETE FROM penduduk_miskin WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin dihapus']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+26
View File
@@ -0,0 +1,26 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
$stmt = $conn->prepare("DELETE FROM rumah_ibadah WHERE id = ?");
$stmt->bind_param("i", $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah dihapus']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+34
View File
@@ -0,0 +1,34 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
$sql = "SELECT id, nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m, bantuan_status, bantuan_catatan, bantuan_updated_at, created_at
FROM penduduk_miskin
ORDER BY created_at DESC";
$result = $conn->query($sql);
$data = [];
if (!$result) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
$conn->close();
exit;
}
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['jumlah_anggota'] = (int)$row['jumlah_anggota'];
$row['rumah_ibadah_id'] = $row['rumah_ibadah_id'] !== null ? (int)$row['rumah_ibadah_id'] : null;
$row['jarak_ke_rumah_ibadah_m'] = $row['jarak_ke_rumah_ibadah_m'] !== null ? (float)$row['jarak_ke_rumah_ibadah_m'] : null;
$row['bantuan_status'] = $row['bantuan_status'] ?: 'belum';
$row['bantuan_catatan'] = $row['bantuan_catatan'] ?? '';
$data[] = $row;
}
}
echo json_encode($data);
$conn->close();
?>
@@ -0,0 +1,24 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
$sql = "SELECT id, nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m, bantuan_status, bantuan_catatan, bantuan_updated_at, created_at FROM penduduk_miskin ORDER BY created_at DESC";
$result = $conn->query($sql);
$features = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$lat = isset($row['latitude']) ? (float)$row['latitude'] : null;
$lng = isset($row['longitude']) ? (float)$row['longitude'] : null;
$props = $row;
unset($props['latitude'], $props['longitude']);
$features[] = [
'type' => 'Feature',
'geometry' => [ 'type' => 'Point', 'coordinates' => [$lng, $lat] ],
'properties' => $props
];
}
}
echo json_encode([ 'type' => 'FeatureCollection', 'features' => $features ]);
$conn->close();
+30
View File
@@ -0,0 +1,30 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
$sql = "SELECT id, nama, alamat, pic, latitude, longitude, radius_m, created_at
FROM rumah_ibadah
ORDER BY created_at DESC";
$result = $conn->query($sql);
$data = [];
if (!$result) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Query gagal: ' . $conn->error]);
$conn->close();
exit;
}
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$row['latitude'] = (float)$row['latitude'];
$row['longitude'] = (float)$row['longitude'];
$row['radius_m'] = (int)$row['radius_m'];
$data[] = $row;
}
}
echo json_encode($data);
$conn->close();
?>
@@ -0,0 +1,24 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
$sql = "SELECT id, nama, alamat, pic, latitude, longitude, radius_m, created_at FROM rumah_ibadah ORDER BY created_at DESC";
$result = $conn->query($sql);
$features = [];
if ($result && $result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
$lat = isset($row['latitude']) ? (float)$row['latitude'] : null;
$lng = isset($row['longitude']) ? (float)$row['longitude'] : null;
$props = $row;
unset($props['latitude'], $props['longitude']);
$features[] = [
'type' => 'Feature',
'geometry' => [ 'type' => 'Point', 'coordinates' => [$lng, $lat] ],
'properties' => $props
];
}
}
echo json_encode([ 'type' => 'FeatureCollection', 'features' => $features ]);
$conn->close();
+57
View File
@@ -0,0 +1,57 @@
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rancangan Sistem — Integrasi Rumah Ibadah & Penduduk Miskin</title>
<style>html,body,#map{height:100%;margin:0} .info{position:absolute;right:8px;top:8px;z-index:400;background:#fff;padding:6px}</style>
</head>
<body>
<div id="map"></div>
<div class="info">Data: Rumah Ibadah & Penduduk Miskin (dari API)</div>
<script src="../global/leaflet-loader.js"></script>
<script>
(function waitForLeaflet(){
if(window.L) return init();
window.addEventListener('leaflet:loaded', init);
setTimeout(function(){ if(window.L) init(); }, 1000);
function init(){
const map = L.map('map').setView([-0.03,109.34],12);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution:''}).addTo(map);
const overlays = {};
const control = L.control.layers(null, overlays, {collapsed:false}).addTo(map);
function addGeoData(data, title){
try{
if(!data) return;
if(data.type==='FeatureCollection' || data.features){
const layer = L.geoJSON(data, {onEachFeature:function(feature,layer){
const p = feature.properties || {};
layer.bindPopup(title+'<br>'+Object.keys(p).map(k=>k+': '+p[k]).join('<br>'));
}}).addTo(map);
overlays[title]=layer; control.addOverlay(layer,title);
} else if(Array.isArray(data)){
const lg = L.layerGroup();
data.forEach(it=>{
const lat = it.lat || it.latitude || it.y;
const lng = it.lng || it.lon || it.longitude || it.x;
if(lat && lng){
const m = L.marker([lat,lng]).bindPopup(title+'<br>'+(it.nama||it.name||''));
lg.addLayer(m);
}
});
lg.addTo(map); overlays[title]=lg; control.addOverlay(lg,title);
}
}catch(e){ console.error('addGeoData error',e); }
}
// load rumah ibadah and penduduk miskin as GeoJSON (endpoints in this folder)
fetch('get_rumah_ibadah_geojson.php').then(r=>r.json()).then(j=>addGeoData(j,'Rumah Ibadah')).catch(e=>console.error(e));
fetch('get_penduduk_miskin_geojson.php').then(r=>r.json()).then(j=>addGeoData(j,'Penduduk Miskin')).catch(e=>console.error(e));
}
})();
</script>
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000;
$toRadians = function ($value) {
return $value * M_PI / 180;
};
$deltaLat = $toRadians($lat2 - $lat1);
$deltaLng = $toRadians($lng2 - $lng1);
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
}
function findNearestRumahIbadah($conn, $lat, $lng) {
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
if (!$result || $result->num_rows === 0) {
return null;
}
$nearest = null;
while ($row = $result->fetch_assoc()) {
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
if ($nearest === null || $distance < $nearest['distance']) {
$nearest = [
'id' => (int)$row['id'],
'nama' => $row['nama'],
'distance' => $distance
];
}
}
return $nearest;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
if ($namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
if (!$nearest) {
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
$conn->close();
exit;
}
$jarak = $nearest['distance'];
$rumahIbadahId = $nearest['id'];
$rumahIbadahNama = $nearest['nama'];
$stmt = $conn->prepare("INSERT INTO penduduk_miskin (nama_ketua_kk, jumlah_anggota, latitude, longitude, rumah_ibadah_id, rumah_ibadah_nama, jarak_ke_rumah_ibadah_m) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->bind_param("siddisd", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Penduduk miskin berhasil disimpan']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$nama = trim($_POST['nama'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$pic = trim($_POST['pic'] ?? '');
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$radius = $_POST['radius_m'] ?? 1000;
if ($nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$radius = max(50, (int)$radius);
$stmt = $conn->prepare("INSERT INTO rumah_ibadah (nama, alamat, pic, latitude, longitude, radius_m) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->bind_param("sssddi", $nama, $alamat, $pic, $lat, $lng, $radius);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil disimpan']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
@@ -0,0 +1,48 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['status' => 'error', 'message' => 'Metode tidak diizinkan']);
$conn->close();
exit;
}
$id = (int)($_POST['id'] ?? 0);
$status = strtolower(trim($_POST['bantuan_status'] ?? 'belum'));
$catatan = trim($_POST['bantuan_catatan'] ?? '');
if ($id <= 0) {
echo json_encode(['status' => 'error', 'message' => 'ID tidak valid']);
$conn->close();
exit;
}
if (!in_array($status, ['belum', 'proses', 'sudah'], true)) {
$status = 'belum';
}
$stmt = $conn->prepare("UPDATE penduduk_miskin SET bantuan_status = ?, bantuan_catatan = ?, bantuan_updated_at = CURRENT_TIMESTAMP WHERE id = ?");
$stmt->bind_param("ssi", $status, $catatan, $id);
if ($stmt->execute()) {
$updatedAtResult = $conn->query("SELECT bantuan_updated_at FROM penduduk_miskin WHERE id = " . (int)$id . " LIMIT 1");
$updatedAt = null;
if ($updatedAtResult && $updatedAtResult->num_rows > 0) {
$row = $updatedAtResult->fetch_assoc();
$updatedAt = $row['bantuan_updated_at'] ?? null;
}
echo json_encode([
'status' => 'success',
'message' => 'Status bantuan berhasil diperbarui',
'bantuan_updated_at' => $updatedAt
]);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
$conn->close();
?>
@@ -0,0 +1,82 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
function distanceMeters($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371000;
$toRadians = function ($value) {
return $value * M_PI / 180;
};
$deltaLat = $toRadians($lat2 - $lat1);
$deltaLng = $toRadians($lng2 - $lng1);
$a = sin($deltaLat / 2) * sin($deltaLat / 2) + cos($toRadians($lat1)) * cos($toRadians($lat2)) * sin($deltaLng / 2) * sin($deltaLng / 2);
return 2 * $earthRadius * atan2(sqrt($a), sqrt(1 - $a));
}
function findNearestRumahIbadah($conn, $lat, $lng) {
$result = $conn->query("SELECT id, nama, latitude, longitude FROM rumah_ibadah ORDER BY created_at DESC");
if (!$result || $result->num_rows === 0) {
return null;
}
$nearest = null;
while ($row = $result->fetch_assoc()) {
$distance = distanceMeters($lat, $lng, (float)$row['latitude'], (float)$row['longitude']);
if ($nearest === null || $distance < $nearest['distance']) {
$nearest = [
'id' => (int)$row['id'],
'nama' => $row['nama'],
'distance' => $distance
];
}
}
return $nearest;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
$namaKetuaKk = trim($_POST['nama_ketua_kk'] ?? '');
$jumlahAnggota = (int)($_POST['jumlah_anggota'] ?? 0);
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$rumahIbadahId = $_POST['rumah_ibadah_id'] ?? null;
$rumahIbadahNama = trim($_POST['rumah_ibadah_nama'] ?? '');
$jarak = $_POST['jarak_ke_rumah_ibadah_m'] ?? null;
if ($id <= 0 || $namaKetuaKk === '' || $jumlahAnggota <= 0 || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data penduduk miskin tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$nearest = findNearestRumahIbadah($conn, $lat, $lng);
if (!$nearest) {
echo json_encode(['status' => 'error', 'message' => 'Tambahkan rumah ibadah terlebih dahulu']);
$conn->close();
exit;
}
$jarak = $nearest['distance'];
$rumahIbadahId = $nearest['id'];
$rumahIbadahNama = $nearest['nama'];
$stmt = $conn->prepare("UPDATE penduduk_miskin SET nama_ketua_kk = ?, jumlah_anggota = ?, latitude = ?, longitude = ?, rumah_ibadah_id = ?, rumah_ibadah_nama = ?, jarak_ke_rumah_ibadah_m = ? WHERE id = ?");
$stmt->bind_param("siddisdi", $namaKetuaKk, $jumlahAnggota, $lat, $lng, $rumahIbadahId, $rumahIbadahNama, $jarak, $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Data penduduk miskin berhasil diperbarui']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>
+36
View File
@@ -0,0 +1,36 @@
<?php
header('Content-Type: application/json');
include __DIR__ . '/../db_config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = (int)($_POST['id'] ?? 0);
$nama = trim($_POST['nama'] ?? '');
$alamat = trim($_POST['alamat'] ?? '');
$pic = trim($_POST['pic'] ?? '');
$lat = $_POST['latitude'] ?? null;
$lng = $_POST['longitude'] ?? null;
$radius = $_POST['radius_m'] ?? 1000;
if ($id <= 0 || $nama === '' || $alamat === '' || $pic === '' || $lat === null || $lng === null) {
echo json_encode(['status' => 'error', 'message' => 'Data rumah ibadah tidak lengkap']);
$conn->close();
exit;
}
$lat = (float)$lat;
$lng = (float)$lng;
$radius = max(50, (int)$radius);
$stmt = $conn->prepare("UPDATE rumah_ibadah SET nama = ?, alamat = ?, pic = ?, latitude = ?, longitude = ?, radius_m = ? WHERE id = ?");
$stmt->bind_param("sssddii", $nama, $alamat, $pic, $lat, $lng, $radius, $id);
if ($stmt->execute()) {
echo json_encode(['status' => 'success', 'message' => 'Rumah ibadah berhasil diperbarui']);
} else {
echo json_encode(['status' => 'error', 'message' => $stmt->error]);
}
$stmt->close();
}
$conn->close();
?>