From 5585c34c5cf45c456b60d966417e69e1ad735d2f Mon Sep 17 00:00:00 2001 From: Syariffullah Date: Sat, 6 Jun 2026 14:25:49 +0700 Subject: [PATCH] feat: implement map visualization, layer management, and searching for poverty data records --- README.md | 60 ++++++++ api/penduduk_miskin/bulk_create.php | 43 ++++++ assets/css/style.css | 52 +++++++ assets/js/features/geojson.js | 189 +++++++++++++++++------ assets/js/features/kemiskinan.js | 231 ++++++++++++++++++++++++++++ assets/js/map.js | 17 ++ assets/js/panel.js | 6 +- database.sql | 117 ++++++++++++++ index.php | 3 + test_miskin.csv | 4 + test_miskin.geojson | 29 ++++ 11 files changed, 704 insertions(+), 47 deletions(-) create mode 100644 README.md create mode 100644 api/penduduk_miskin/bulk_create.php create mode 100644 database.sql create mode 100644 test_miskin.csv create mode 100644 test_miskin.geojson diff --git a/README.md b/README.md new file mode 100644 index 0000000..e12869f --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# WebGIS Pemetaan Kemiskinan Kota Pontianak + +Aplikasi WebGIS sederhana berbasis Leaflet.js untuk memetakan data kemiskinan, sebaran penduduk miskin, log bantuan, rumah ibadah, SPBU, serta data jalan dan parsil tanah di Kota Pontianak. + +## Fitur Utama + +- **Pemetaan Interaktif**: Visualisasi peta dengan Leaflet.js yang berpusat di Kota Pontianak. +- **Manajemen Data Spasial**: + - **SPBU**: Lokasi titik SPBU beserta informasi layanan 24 jam. + - **Jalan**: Fitur line/polyline dengan kategori status jalan (Nasional, Provinsi, Kabupaten). + - **Parsil**: Fitur area/polygon tanah dengan kategori sertifikasi (SHM, HGB, HGU, HP). + - **Rumah Ibadah**: Lokasi titik rumah ibadah (Masjid, Gereja, Vihara, dll) beserta jangkauan radius jemaah/bantuan. + - **Penduduk Miskin**: Pemetaan warga kurang mampu dan integrasi log/riwayat bantuan sosial yang disalurkan melalui rumah ibadah terdekat. +- **Pencarian Cepat**: Cari data jalan, parsil, SPBU, atau rumah ibadah langsung dari bilah pencarian. +- **Impor GeoJSON Eksternal**: Pengguna dapat menambahkan file GeoJSON dari perangkat lokal ke dalam peta. + +## Persyaratan Sistem + +- **Server Web**: Apache (misal menggunakan XAMPP atau Laragon). +- **Bahasa Pemrograman**: PHP 7.4 ke atas. +- **Database**: MySQL versi 5.7+ atau MariaDB versi 10.2+ (Wajib mendukung fitur fungsi spasial seperti `ST_GeomFromGeoJSON` dan `ST_AsGeoJSON`). + +## Cara Instalasi & Penggunaan + +1. **Unduh/Clone Repositori**: + Tempatkan folder `webgis-poverty-pontianak` di dalam direktori root server web Anda (misalnya `C:/xampp/htdocs/webgis/webgis-poverty-pontianak`). + +2. **Setup Database**: + - Buka phpMyAdmin atau MySQL client pilihan Anda. + - Buat database baru bernama `webgis`: + ```sql + CREATE DATABASE webgis; + ``` + - Import file `database.sql` ke dalam database `webgis` tersebut. + +3. **Konfigurasi Koneksi**: + - Buka file [api/db_connect.php](api/db_connect.php). + - Sesuaikan konfigurasi host, user, password, dan database sesuai dengan setup database MySQL lokal Anda: + ```php + $host = "localhost"; + $user = "root"; + $pass = ""; + $db = "webgis"; + ``` + +4. **Jalankan Aplikasi**: + - Aktifkan modul **Apache** dan **MySQL** di Control Panel XAMPP Anda. + - Buka browser dan navigasikan ke alamat: + [http://localhost/webgis/webgis-poverty-pontianak/index.php](http://localhost/webgis/webgis-poverty-pontianak/index.php) + +## Struktur Folder + +- `api/` — Kumpulan REST API endpoint untuk operasi CRUD masing-masing fitur. + - `jalan/`, `parsil/`, `spbu/`, `rumah_ibadah/`, `penduduk_miskin/`, `log_bantuan/` +- `assets/` — File aset frontend. + - `css/` — Gaya UI (custom style.css). + - `js/` — Logika frontend peta dan pengelolaan interaksi panel data. + - `features/` — Modul fitur Leaflet (menggambar peta, manajemen layer, geolokasi). +- `index.php` — Halaman utama interface WebGIS. +- `database.sql` — Skema database beserta data dummy Pontianak untuk pengetesan awal. diff --git a/api/penduduk_miskin/bulk_create.php b/api/penduduk_miskin/bulk_create.php new file mode 100644 index 0000000..e8f60d3 --- /dev/null +++ b/api/penduduk_miskin/bulk_create.php @@ -0,0 +1,43 @@ +begin_transaction(); + $inserted = 0; + $errors = []; + foreach ($data as $item) { + if (isset($item->lat) && isset($item->lng) && $item->lat !== '' && $item->lng !== '') { + $nama = $conn->real_escape_string($item->nama ?? ''); + $kategori = $conn->real_escape_string($item->kategori_bantuan ?? 'Makan'); + $jumlah_jiwa = (int)($item->jumlah_jiwa ?? 1); + $lat = (float)$item->lat; + $lng = (float)$item->lng; + + $query = "INSERT INTO penduduk_miskin (nama, kategori_bantuan, jumlah_jiwa, lat, lng) + VALUES ('$nama', '$kategori', $jumlah_jiwa, $lat, $lng)"; + + if (!$conn->query($query)) { + $errors[] = $conn->error; + } else { + $inserted++; + } + } + } + + if (empty($errors) && $inserted > 0) { + $conn->commit(); + echo json_encode(["status" => "success", "message" => "Berhasil mengimpor $inserted data penduduk miskin."]); + } else if ($inserted === 0) { + $conn->rollback(); + echo json_encode(["status" => "error", "message" => "Tidak ada data valid yang diimpor."]); + } else { + $conn->rollback(); + echo json_encode(["status" => "error", "message" => "Gagal mengimpor beberapa data. Transaksi dibatalkan.", "errors" => $errors]); + } +} else { + echo json_encode(["status" => "error", "message" => "Format data salah."]); +} +?> diff --git a/assets/css/style.css b/assets/css/style.css index f80837f..0ef9b57 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -841,3 +841,55 @@ body.right-panel-open .custom-layer-panel { .emoji-marker .bubble.ibadah { background: linear-gradient(135deg, #f97316, #ea580c); } .emoji-marker .bubble.miskin-in { background: linear-gradient(135deg, #ef4444, #dc2626); } .emoji-marker .bubble.miskin-out { background: linear-gradient(135deg, #22c55e, #16a34a); } + +/* ===== Toast Notification ===== */ +#toastContainer { + position: fixed; + bottom: 28px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; +} + +.toast { + display: flex; + align-items: center; + gap: 10px; + padding: 12px 20px; + border-radius: 12px; + font-size: 13px; + font-weight: 500; + color: white; + box-shadow: 0 6px 24px rgba(0,0,0,0.18); + backdrop-filter: blur(6px); + pointer-events: auto; + animation: toastSlideIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) both; + max-width: 340px; + text-align: center; +} + +.toast.hiding { + animation: toastSlideOut 0.3s ease forwards; +} + +.toast-success { background: linear-gradient(135deg, #22c55e, #16a34a); } +.toast-error { background: linear-gradient(135deg, #ef4444, #dc2626); } +.toast-info { background: linear-gradient(135deg, #6366f1, #4f46e5); } +.toast-warning { background: linear-gradient(135deg, #f59e0b, #d97706); } + +.toast-icon { font-size: 16px; flex-shrink: 0; } + +@keyframes toastSlideIn { + 0% { opacity: 0; transform: translateY(20px) scale(0.9); } + 100% { opacity: 1; transform: translateY(0) scale(1); } +} + +@keyframes toastSlideOut { + 0% { opacity: 1; transform: translateY(0) scale(1); } + 100% { opacity: 0; transform: translateY(10px) scale(0.95); } +} diff --git a/assets/js/features/geojson.js b/assets/js/features/geojson.js index f44330b..7c4fab9 100644 --- a/assets/js/features/geojson.js +++ b/assets/js/features/geojson.js @@ -16,6 +16,18 @@ window.toggleGeoJsonMenu = function() { } }; +// Cek apakah fitur GeoJSON ini terlihat seperti data penduduk miskin +function isMiskinFeature(feature) { + if (!feature || !feature.geometry || feature.geometry.type !== 'Point') return false; + const props = feature.properties || {}; + const keys = Object.keys(props).map(k => k.toLowerCase()); + // Dianggap data miskin jika punya properti nama (dan bukan ibadah atau spbu) + const hasNama = keys.some(k => ['nama', 'name', 'penduduk'].includes(k)); + const isIbadah = keys.some(k => ['jenis', 'radius', 'alamat'].includes(k)); + const isSpbu = keys.some(k => ['alamat_spbu', 'is_24_jam'].includes(k)); + return hasNama && !isIbadah && !isSpbu; +} + fileGeoJson.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; @@ -25,58 +37,143 @@ fileGeoJson.addEventListener('change', function(e) { try { const geoJsonData = JSON.parse(event.target.result); const fileName = file.name; - const layerId = 'gj_' + (++geoJsonCounter); - - // Buat FeatureGroup khusus untuk file ini - const individualLayer = L.featureGroup(); - - // Tambahkan ke map - L.geoJSON(geoJsonData, { - onEachFeature: function (feature, layer) { - if (feature.properties) { - let popupContent = '

Informasi Feature

'; - layer.bindPopup(popupContent); - } - }, - style: function(feature) { + + // Pisahkan fitur menjadi dua kelompok: miskin dan non-miskin + const features = geoJsonData.type === 'FeatureCollection' + ? geoJsonData.features + : [geoJsonData]; + + const miskinFeatures = features.filter(f => isMiskinFeature(f)); + const otherFeatures = features.filter(f => !isMiskinFeature(f)); + + // ---- Proses fitur miskin: Simpan ke DB, lalu tampilkan seperti penduduk miskin ---- + if (miskinFeatures.length > 0) { + const bulkData = miskinFeatures.map(f => { + const props = f.properties || {}; + const coords = f.geometry.coordinates; return { - color: feature.properties.color || '#3388ff', - weight: 2, - fillOpacity: 0.4 + nama: props.nama || props.name || props.penduduk || 'Data Impor', + kategori_bantuan: props.kategori_bantuan || props.kategori || props.bantuan || 'Makan', + jumlah_jiwa: parseInt(props.jumlah_jiwa || props.jumlah || props.jiwa || 1, 10) || 1, + lat: parseFloat(coords[1]), + lng: parseFloat(coords[0]) }; + }); + + fetch('api/penduduk_miskin/bulk_create.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(bulkData) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + // Reload layer penduduk miskin agar marker tampil dengan edit/hapus/log bantuan + if (typeof loadPendudukMiskin === 'function') { + loadPendudukMiskin(); + } + if (typeof window.refreshActivePanel === 'function') { + window.refreshActivePanel(); + } + showToast(data.message || 'Data berhasil diimpor ke layer Penduduk Miskin.', 'success'); + } else { + showToast(data.message || 'Gagal menyimpan data miskin.', 'error'); + } + }) + .catch(err => { + console.error(err); + showToast('Gagal terhubung ke server saat menyimpan data miskin.', 'error'); + }); + } + + // ---- Proses fitur non-miskin: Render sebagai layer GeoJSON biasa ---- + if (otherFeatures.length > 0) { + const layerId = 'gj_' + (++geoJsonCounter); + const individualLayer = L.featureGroup(); + + const otherGeoJson = { + type: 'FeatureCollection', + features: otherFeatures + }; + + L.geoJSON(otherGeoJson, { + pointToLayer: function(feature, latlng) { + const props = feature.properties || {}; + let emoji = props.emoji; + + if (!emoji) { + const text = JSON.stringify(props).toLowerCase(); + if (text.includes('spbu')) emoji = '⛽'; + else if (text.includes('masjid')) emoji = '🕌'; + else if (text.includes('gereja')) emoji = '⛪'; + else if (text.includes('vihara')) emoji = '🪷'; + else if (text.includes('pura')) emoji = '🛕'; + else if (text.includes('kelenteng')) emoji = '🏮'; + else emoji = '📍'; + } + + let cls = 'miskin-out'; + if (emoji === '⛽') cls = 'spbu-24'; + else if (['🕌','⛪','🛕','🪷','🏮'].includes(emoji)) cls = 'ibadah'; + + const icon = L.divIcon({ + className: '', + html: `
${emoji}
`, + iconSize: [38, 38], + iconAnchor: [19, 38], + popupAnchor: [0, -40] + }); + + return L.marker(latlng, { icon: icon }); + }, + onEachFeature: function(feature, layer) { + if (feature.properties) { + let popupContent = '

Informasi Feature

'; + layer.bindPopup(popupContent); + } + }, + style: function(feature) { + return { + color: (feature.properties && feature.properties.color) || '#3388ff', + weight: 2, + fillOpacity: 0.4 + }; + } + }).addTo(individualLayer); + + individualLayer.addTo(map); + geoJsonLayers[layerId] = individualLayer; + + // Tambahkan checkbox ke UI + const container = document.getElementById('geoJsonLayersContainer'); + const label = document.createElement('label'); + label.className = 'layer-option'; + label.innerHTML = ` ${fileName}`; + + label.querySelector('input').addEventListener('change', function(e) { + if (e.target.checked) { + map.addLayer(geoJsonLayers[layerId]); + } else { + map.removeLayer(geoJsonLayers[layerId]); + } + }); + + container.appendChild(label); + + if (miskinFeatures.length === 0) { + showToast('File GeoJSON berhasil dimuat!', 'success'); } - }).addTo(individualLayer); - - individualLayer.addTo(map); - geoJsonLayers[layerId] = individualLayer; - - // Tambahkan checkbox ke UI - const container = document.getElementById('geoJsonLayersContainer'); - const label = document.createElement('label'); - label.className = 'layer-option'; - label.innerHTML = ` ${fileName}`; - - label.querySelector('input').addEventListener('change', function(e) { - if (e.target.checked) { - map.addLayer(geoJsonLayers[layerId]); - } else { - map.removeLayer(geoJsonLayers[layerId]); - } - }); - - container.appendChild(label); - - // Reset input file agar bisa import file yang sama jika dihapus + } + + // Reset input file agar bisa import file yang sama fileGeoJson.value = ''; - - alert('File GeoJSON berhasil dimuat!'); } catch (error) { - alert('Gagal memproses file GeoJSON. Pastikan format valid.'); + showToast('Gagal memproses file GeoJSON. Pastikan format valid.', 'error'); console.error(error); } }; diff --git a/assets/js/features/kemiskinan.js b/assets/js/features/kemiskinan.js index f606edf..d592a02 100644 --- a/assets/js/features/kemiskinan.js +++ b/assets/js/features/kemiskinan.js @@ -552,6 +552,237 @@ function updateSemuaWarnaMiskin() { }); } +// ========================== +// ===== Import Feature ===== +// ========================== +let parsedImportData = []; + +window.openImportMiskinModal = function() { + parsedImportData = []; // Reset data + + const bodyHTML = ` +
+ + +
+ Format CSV yang didukung:
+ Header minimal berisi: nama, kategori_bantuan (atau kategori), jumlah_jiwa, lat, lng.
+ Pemisah: Koma (,), Titik Koma (;), atau Tab. +
+
+ + + `; + + map.closePopup(); + + openModal("Impor Penduduk Miskin", bodyHTML, function() { + if (parsedImportData.length === 0) { + showToast('Silakan pilih file terlebih dahulu atau pastikan data valid.', 'warning'); + return; + } + + // Kirim data ke API bulk_create.php + fetch('api/penduduk_miskin/bulk_create.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(parsedImportData) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + closeModal(); + loadPendudukMiskin(); + showToast(data.message, 'success'); + } else { + showToast(data.message || 'Gagal mengimpor data.', 'error'); + } + }) + .catch(err => { + console.error(err); + showToast('Terjadi kesalahan saat menghubungi server.', 'error'); + }); + }); + + // Register file input listener + document.getElementById('importMiskinFile').addEventListener('change', handleImportFileChange); +}; + +function parseCSV(text) { + const lines = text.split(/\r?\n/); + if (lines.length < 2) return []; + + const firstLine = lines[0]; + let delimiter = ','; + if (firstLine.includes(';')) delimiter = ';'; + else if (firstLine.includes('\t')) delimiter = '\t'; + + const headers = firstLine.split(delimiter).map(h => h.trim().replace(/^["']|["']$/g, '').toLowerCase()); + + const results = []; + for (let i = 1; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line) continue; + + let fields = []; + let currentField = ''; + let insideQuote = false; + + for (let j = 0; j < line.length; j++) { + const char = line[j]; + if (char === '"' || char === "'") { + insideQuote = !insideQuote; + } else if (char === delimiter && !insideQuote) { + fields.push(currentField.trim()); + currentField = ''; + } else { + currentField += char; + } + } + fields.push(currentField.trim()); + + fields = fields.map(f => f.replace(/^["']|["']$/g, '')); + + const row = {}; + headers.forEach((header, index) => { + row[header] = fields[index] || ''; + }); + results.push(row); + } + return results; +} + +function handleImportFileChange(e) { + const file = e.target.files[0]; + const previewContainer = document.getElementById('importPreviewContainer'); + const previewCount = document.getElementById('importPreviewCount'); + const previewTableBody = document.getElementById('importPreviewTableBody'); + const errorMsg = document.getElementById('importErrorMsg'); + + if (!file) { + previewContainer.style.display = 'none'; + parsedImportData = []; + return; + } + + errorMsg.style.display = 'none'; + previewContainer.style.display = 'none'; + previewTableBody.innerHTML = ''; + parsedImportData = []; + + const reader = new FileReader(); + reader.onload = function(event) { + try { + const text = event.target.result; + let rawData = []; + + if (file.name.endsWith('.csv')) { + rawData = parseCSV(text); + } else if (file.name.endsWith('.json') || file.name.endsWith('.geojson')) { + const json = JSON.parse(text); + if (json.type === 'FeatureCollection' && Array.isArray(json.features)) { + rawData = json.features.map(f => { + const props = f.properties || {}; + const coords = f.geometry && f.geometry.coordinates; + return { + nama: props.nama || props.name || props.penduduk || '', + kategori_bantuan: props.kategori_bantuan || props.kategori || props.bantuan || 'Makan', + jumlah_jiwa: props.jumlah_jiwa || props.jumlah || props.jiwa || 1, + lat: coords ? coords[1] : (props.lat || props.latitude), + lng: coords ? coords[0] : (props.lng || props.longitude) + }; + }); + } else if (Array.isArray(json)) { + rawData = json; + } else { + throw new Error("Format JSON tidak didukung."); + } + } + + if (rawData.length === 0) { + errorMsg.textContent = "File kosong atau format tidak didukung."; + errorMsg.style.display = 'block'; + return; + } + + const validRows = []; + let invalidCount = 0; + + rawData.forEach(row => { + const nama = row.nama || row.name || row.penduduk || row['nama lengkap'] || row.fullname || ''; + const kategori_bantuan = row.kategori_bantuan || row.kategori || row.bantuan || row['kategori bantuan'] || 'Makan'; + const jumlah_jiwa = parseInt(row.jumlah_jiwa || row.jumlah || row.jiwa || row['jumlah jiwa'] || 1, 10) || 1; + const lat = parseFloat(row.lat || row.latitude || row.y); + const lng = parseFloat(row.lng || row.longitude || row.long || row.x); + + const isValid = !isNaN(lat) && !isNaN(lng) && lat !== 0 && lng !== 0 && nama.trim() !== ''; + + if (isValid) { + validRows.push({ nama, kategori_bantuan, jumlah_jiwa, lat, lng }); + } else { + invalidCount++; + } + }); + + parsedImportData = validRows; + + if (validRows.length === 0) { + errorMsg.textContent = "Tidak ditemukan data valid (pastikan kolom nama, lat, dan lng terisi)."; + errorMsg.style.display = 'block'; + return; + } + + validRows.forEach(row => { + const tr = document.createElement('tr'); + tr.style.borderBottom = '1px solid #e2e8f0'; + tr.innerHTML = ` + ${row.nama} + ${row.kategori_bantuan} + ${row.jumlah_jiwa} + ${row.lat.toFixed(5)}, ${row.lng.toFixed(5)} + `; + previewTableBody.appendChild(tr); + }); + + previewCount.textContent = validRows.length; + previewContainer.style.display = 'block'; + + const statusEl = document.getElementById('importPreviewStatus'); + if (statusEl) { + if (invalidCount > 0) { + statusEl.innerHTML = ` ${invalidCount} baris tidak valid diabaikan`; + } else { + statusEl.innerHTML = ` Semua data valid`; + } + } + + } catch (err) { + console.error(err); + errorMsg.textContent = "Gagal memproses file. Pastikan format file sesuai."; + errorMsg.style.display = 'block'; + } + }; + reader.readAsText(file); +} + // Initial Load loadRumahIbadah(); loadPendudukMiskin(); diff --git a/assets/js/map.js b/assets/js/map.js index 714f5f1..44e0c23 100644 --- a/assets/js/map.js +++ b/assets/js/map.js @@ -2,6 +2,23 @@ // Koordinat awal: [-0.0263, 109.3425] (Pontianak) const map = L.map('map', { zoomControl: false }).setView([-0.0263, 109.3425], 13); +// ===== Toast Notification ===== +window.showToast = function(message, type = 'success', duration = 3500) { + const icons = { success: '✅', error: '❌', info: 'ℹ️', warning: '⚠️' }; + const container = document.getElementById('toastContainer'); + if (!container) return; + + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + toast.innerHTML = `${icons[type] || 'ℹ️'}${message}`; + container.appendChild(toast); + + setTimeout(() => { + toast.classList.add('hiding'); + toast.addEventListener('animationend', () => toast.remove()); + }, duration); +}; + // Custom Zoom Control Logic document.getElementById('zoomInBtn').addEventListener('click', function() { map.zoomIn(); }); document.getElementById('zoomOutBtn').addEventListener('click', function() { map.zoomOut(); }); diff --git a/assets/js/panel.js b/assets/js/panel.js index 67939c9..c380d8c 100644 --- a/assets/js/panel.js +++ b/assets/js/panel.js @@ -347,7 +347,11 @@ }); document.getElementById('btnPanelImportMiskin').addEventListener('click', () => { - alert('Fitur impor CSV akan segera tersedia.'); + if (typeof window.openImportMiskinModal === 'function') { + window.openImportMiskinModal(); + } else { + alert('Fitur impor belum siap.'); + } }); const all = []; diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..fd63156 --- /dev/null +++ b/database.sql @@ -0,0 +1,117 @@ +-- Database schema for WebGIS Pemetaan Kemiskinan Pontianak + +CREATE DATABASE IF NOT EXISTS webgis; +USE webgis; + +-- 1. Tabel rumah_ibadah +DROP TABLE IF EXISTS `log_bantuan`; +DROP TABLE IF EXISTS `rumah_ibadah`; +CREATE TABLE `rumah_ibadah` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(255) NOT NULL, + `jenis` varchar(100) NOT NULL, + `alamat` text, + `radius` double NOT NULL DEFAULT 0, + `lat` double NOT NULL, + `lng` double NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 2. Tabel penduduk_miskin +DROP TABLE IF EXISTS `penduduk_miskin`; +CREATE TABLE `penduduk_miskin` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(255) NOT NULL, + `kategori_bantuan` varchar(255) NOT NULL, + `jumlah_jiwa` int(11) NOT NULL DEFAULT 1, + `lat` double NOT NULL, + `lng` double NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 3. Tabel log_bantuan +CREATE TABLE `log_bantuan` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `miskin_id` int(11) NOT NULL, + `ibadah_id` int(11) NOT NULL, + `tipe_bantuan` varchar(255) NOT NULL, + `tanggal` date NOT NULL, + `keterangan` text, + PRIMARY KEY (`id`), + KEY `fk_log_miskin` (`miskin_id`), + KEY `fk_log_ibadah` (`ibadah_id`), + CONSTRAINT `fk_log_ibadah` FOREIGN KEY (`ibadah_id`) REFERENCES `rumah_ibadah` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_log_miskin` FOREIGN KEY (`miskin_id`) REFERENCES `penduduk_miskin` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 4. Tabel jalan +DROP TABLE IF EXISTS `jalan`; +CREATE TABLE `jalan` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(255) NOT NULL, + `status` varchar(100) NOT NULL DEFAULT 'Kabupaten', + `panjang` double NOT NULL DEFAULT 0, + `geom` geometry NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 5. Tabel parsil +DROP TABLE IF EXISTS `parsil`; +CREATE TABLE `parsil` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(255) NOT NULL, + `status` varchar(100) NOT NULL DEFAULT 'SHM', + `luas` double NOT NULL DEFAULT 0, + `geom` geometry NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- 6. Tabel spbu +DROP TABLE IF EXISTS `spbu`; +CREATE TABLE `spbu` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `nama` varchar(255) NOT NULL, + `no_wa` varchar(20) DEFAULT NULL, + `is_24_jam` tinyint(1) NOT NULL DEFAULT 0, + `lat` double NOT NULL, + `lng` double NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Data Contoh (Sample Data) untuk Pontianak + +-- Data Contoh Rumah Ibadah +INSERT INTO `rumah_ibadah` (`nama`, `jenis`, `alamat`, `radius`, `lat`, `lng`) VALUES +('Masjid Raya Mujahidin', 'Masjid', 'Jl. Jenderal Ahmad Yani, Akcaya, Kec. Pontianak Sel.', 500, -0.0435, 109.3440), +('Gereja Katedral Santo Yosef', 'Gereja', 'Jl. Patria Tama, Darat Sekip, Kec. Pontianak Kota', 500, -0.0278, 109.3370), +('Vihara Bodhisatva Karaniya Metta', 'Vihara', 'Jl. Kapten Marsan, Darat Sekip, Kec. Pontianak Kota', 500, -0.0232, 109.3432); + +-- Data Contoh Penduduk Miskin +INSERT INTO `penduduk_miskin` (`nama`, `kategori_bantuan`, `jumlah_jiwa`, `lat`, `lng`) VALUES +('Budi Santoso', 'Makan', 4, -0.0420, 109.3415), +('Siti Aminah', 'Pemberdayaan', 3, -0.0285, 109.3395), +('Ahmad Subarjo', 'Makan', 5, -0.0245, 109.3448); + +-- Data Contoh Log Bantuan +INSERT INTO `log_bantuan` (`miskin_id`, `ibadah_id`, `tipe_bantuan`, `tanggal`, `keterangan`) VALUES +(1, 1, 'Sembako', '2026-05-15', 'Pembagian paket sembako bulanan'), +(2, 2, 'Pendidikan', '2026-05-20', 'Bantuan biaya sekolah anak'), +(3, 1, 'Uang Tunai', '2026-06-01', 'Santunan tunai darurat'); + +-- Data Contoh SPBU +INSERT INTO `spbu` (`nama`, `no_wa`, `is_24_jam`, `lat`, `lng`) VALUES +('SPBU Ahmad Yani', '081234567890', 1, -0.0485, 109.3490), +('SPBU Oesman Sapta Odang', '081234567891', 0, -0.0345, 109.3512), +('SPBU Kota Baru', '081234567892', 1, -0.0512, 109.3198); + +-- Data Contoh Jalan (Polyline) +-- Menggunakan ST_GeomFromText untuk membuat LineString +INSERT INTO `jalan` (`nama`, `status`, `panjang`, `geom`) VALUES +('Jl. Jenderal Ahmad Yani', 'Nasional', 2500, ST_GeomFromText('LINESTRING(109.3400 -0.0380, 109.3450 -0.0450, 109.3500 -0.0520)')), +('Jl. Gajah Mada', 'Provinsi', 1800, ST_GeomFromText('LINESTRING(109.3350 -0.0290, 109.3410 -0.0280, 109.3480 -0.0270)')); + +-- Data Contoh Parsil (Polygon) +-- Menggunakan ST_GeomFromText untuk membuat Polygon (pastikan koordinat berputar kembali ke titik awal untuk menutup polygon) +INSERT INTO `parsil` (`nama`, `status`, `luas`, `geom`) VALUES +('Parsil Tanah A', 'SHM', 150, ST_GeomFromText('POLYGON((109.3360 -0.0250, 109.3375 -0.0250, 109.3375 -0.0260, 109.3360 -0.0260, 109.3360 -0.0250))')), +('Parsil Tanah B', 'HGB', 320, ST_GeomFromText('POLYGON((109.3405 -0.0310, 109.3420 -0.0310, 109.3420 -0.0330, 109.3405 -0.0330, 109.3405 -0.0310))')); diff --git a/index.php b/index.php index 345e427..ff36493 100644 --- a/index.php +++ b/index.php @@ -290,6 +290,9 @@ + +
+ diff --git a/test_miskin.csv b/test_miskin.csv new file mode 100644 index 0000000..d9bc426 --- /dev/null +++ b/test_miskin.csv @@ -0,0 +1,4 @@ +nama,kategori_bantuan,jumlah_jiwa,lat,lng +Budi Santoso,Makan,4,-0.025,109.340 +Siti Aminah,Pemberdayaan,3,-0.028,109.345 +Andi Wijaya,Makan,5,-0.024,109.341 diff --git a/test_miskin.geojson b/test_miskin.geojson new file mode 100644 index 0000000..ccc4d62 --- /dev/null +++ b/test_miskin.geojson @@ -0,0 +1,29 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "nama": "Joko Widodo", + "kategori_bantuan": "Pemberdayaan", + "jumlah_jiwa": 2 + }, + "geometry": { + "type": "Point", + "coordinates": [109.343, -0.027] + } + }, + { + "type": "Feature", + "properties": { + "nama": "Mega Wati", + "kategori_bantuan": "Makan", + "jumlah_jiwa": 6 + }, + "geometry": { + "type": "Point", + "coordinates": [109.344, -0.029] + } + } + ] +}