commit 32f444f8ade9ed1fa6af8b7778a44bc9652e9e54 Author: powji17 Date: Thu Jun 4 09:42:37 2026 +0700 Initial commit: Upload project WebGIS pemetaan sosial diff --git a/01/hapus_area.php b/01/hapus_area.php new file mode 100644 index 0000000..819df19 --- /dev/null +++ b/01/hapus_area.php @@ -0,0 +1,18 @@ +prepare("DELETE FROM area_polygon WHERE id = ?"); + $stmt->bind_param("i", $id); + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} +$conn->close(); +?> \ No newline at end of file diff --git a/01/hapus_jalan.php b/01/hapus_jalan.php new file mode 100644 index 0000000..175d531 --- /dev/null +++ b/01/hapus_jalan.php @@ -0,0 +1,38 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id'])) { + $id = $data['id']; + + // Hapus data dari tabel jalan_polyline berdasarkan ID + $stmt = $conn->prepare("DELETE FROM jalan_polyline WHERE id = ?"); + $stmt->bind_param("i", $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Garis jalan berhasil dihapus"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal menghapus data: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "ID tidak valid"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/01/hapus_spbu.php b/01/hapus_spbu.php new file mode 100644 index 0000000..ec8d657 --- /dev/null +++ b/01/hapus_spbu.php @@ -0,0 +1,37 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id'])) { + $id = $data['id']; + + $stmt = $conn->prepare("DELETE FROM spbu_point WHERE id = ?"); + $stmt->bind_param("i", $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Data berhasil dihapus"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal menghapus data: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "ID tidak valid"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/01/index.html b/01/index.html new file mode 100644 index 0000000..fa564f9 --- /dev/null +++ b/01/index.html @@ -0,0 +1,104 @@ + + + + + + Sistem Pemetaan + + + + + + + + + + + + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/01/load_area.php b/01/load_area.php new file mode 100644 index 0000000..f583e4b --- /dev/null +++ b/01/load_area.php @@ -0,0 +1,20 @@ +query($sql); +$data_area = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + // Mengubah string geojson dari database kembali menjadi Object JSON + // Ini wajib agar Leaflet.geoJSON() di Javascript bisa merendernya + $row['geojson'] = json_decode($row['geojson']); + $data_area[] = $row; + } +} + +echo json_encode($data_area); +$conn->close(); +?> \ No newline at end of file diff --git a/01/load_jalan.php b/01/load_jalan.php new file mode 100644 index 0000000..7455a87 --- /dev/null +++ b/01/load_jalan.php @@ -0,0 +1,18 @@ +query($sql); +$data_jalan = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + // Parse string geojson menjadi object agar mudah dibaca Javascript + $row['geojson'] = json_decode($row['geojson']); + $data_jalan[] = $row; + } +} +echo json_encode($data_jalan); +$conn->close(); +?> \ No newline at end of file diff --git a/01/load_spbu.php b/01/load_spbu.php new file mode 100644 index 0000000..ed542b0 --- /dev/null +++ b/01/load_spbu.php @@ -0,0 +1,33 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +// Ambil semua data dari tabel spbu_point +$sql = "SELECT * FROM spbu_point"; +$result = $conn->query($sql); + +$data_spbu = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + $data_spbu[] = $row; + } +} + +// Kirim data sebagai JSON +echo json_encode($data_spbu); + +$conn->close(); +?> \ No newline at end of file diff --git a/01/script.js b/01/script.js new file mode 100644 index 0000000..5d6029f --- /dev/null +++ b/01/script.js @@ -0,0 +1,405 @@ +// --- INISIALISASI PETA (Koordinat Pontianak) --- +const map = L.map('map').setView([-0.055348, 109.349512], 13); +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); + +const popup = L.popup(); +const daftarMarker = {}, daftarPolyline = {}, daftarPolygon = {}; + +let currentMode = 'point'; +let tempLatLngs = [], tempPolyline = null; +let tempPolygonLatLngs = [], tempPolygon = null; +let targetEditJalanId = null; // Menyimpan ID jalan yang sedang diedit +let targetEditKavlingId = null; // Menyimpan ID kavling yang sedang diedit + +// Konfigurasi SweetAlert2 (Toast Pojok Kanan Atas) +const Toast = Swal.mixin({ + toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true +}); + +// Definisi Ikon Kustom +const ikonHijau = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); +const ikonMerah = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); + +// --- MANAJEMEN MODE UI --- +function ubahMode(mode) { + currentMode = mode; + + // Matikan mode edit Geoman global jika sedang aktif + map.pm.disableGlobalEditMode(); + + // Reset state edit + if (targetEditJalanId) batalkanEditJalan(); + if (targetEditKavlingId) batalkanEditKavling(); + + // Atur tampilan tombol aktif + document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); + document.querySelector(`.mode-btn[data-mode="${mode}"]`).classList.add('active'); + + // Munculkan menu yang sesuai di sidebar + document.getElementById('polyline-actions').style.display = (mode === 'polyline') ? 'block' : 'none'; + document.getElementById('polygon-actions').style.display = (mode === 'polygon') ? 'block' : 'none'; + document.getElementById('edit-jalan-actions').style.display = (mode === 'edit_jalan') ? 'block' : 'none'; + document.getElementById('edit-kavling-actions').style.display = (mode === 'edit_kavling') ? 'block' : 'none'; + + map.closePopup(); + if (mode !== 'polyline') batalPolyline(); + if (mode !== 'polygon') batalPolygon(); +} + +// --- FUNGSI BANTUAN --- +function hitungPanjangJalan(latlngs) { let t=0; for (let i=0; i res.json()).then(data => { + data.forEach(spbu => { + let marker = L.marker([spbu.lat, spbu.lng], { icon: (spbu.buka_24jam == '1') ? ikonHijau : ikonMerah, draggable: true }).addTo(map); + marker.bindPopup(`${spbu.nama_spbu}
WA: ${spbu.no_wa}
24 Jam: ${spbu.buka_24jam == '1' ? 'Ya' : 'Tidak'}
`); + marker.on('dragend', function(e) { updateLokasiSPBU(spbu.id, e.target.getLatLng().lat, e.target.getLatLng().lng); }); + daftarMarker[spbu.id] = marker; + }); + }); + + fetch('load_jalan.php').then(res => res.json()).then(data => { + data.forEach(jalan => { + let geojsonGroup = L.geoJSON(jalan.geojson, { style: { color: warnaJalan(jalan.status), weight: 4 } }).addTo(map); + let polylineLayer = geojsonGroup.getLayers()[0]; // Ambil layer aslinya + + polylineLayer.bindPopup(`Jalan: ${jalan.nama_jalan}
Status: ${jalan.status}
Panjang: ${parseFloat(jalan.panjang_m).toFixed(2)} m
`); + + // Tambahkan event click khusus untuk mode edit + polylineLayer.on('click', function(e) { + if (currentMode === 'edit_jalan') { + aktifkanEditJalan(jalan.id, jalan.nama_jalan); + } + }); + + daftarPolyline[jalan.id] = polylineLayer; + }); + }); + + fetch('load_area.php').then(res => res.json()).then(data => { + data.forEach(area => { + let geojsonGroup = L.geoJSON(area.geojson, { style: { color: warnaPolygon(area.status_kepemilikan), fillOpacity: 0.4 } }).addTo(map); + let polygonLayer = geojsonGroup.getLayers()[0]; // Ambil layer aslinya + + polygonLayer.bindPopup(`Kavling: ${area.nama_kavling}
Status: ${area.status_kepemilikan}
Luas: ${parseFloat(area.luas_m2).toFixed(2)} m²
`); + + // Tambahkan event click khusus untuk mode edit + polygonLayer.on('click', function(e) { + if (currentMode === 'edit_kavling') { + aktifkanEditKavling(area.id, area.nama_kavling); + } + }); + + daftarPolygon[area.id] = polygonLayer; + }); + }); +} +loadDB(); + +// --- EVENT KLIK PETA --- +function onMapClick(e) { + if (currentMode === 'point') { + let formHtml = ` +
+

SPBU Baru

+
+ + +
+   +

+ +
+
`; + popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); + } else if (currentMode === 'polyline') { + tempLatLngs.push(e.latlng); + if (!tempPolyline) tempPolyline = L.polyline(tempLatLngs, {color: '#3498db', weight: 4}).addTo(map); + else tempPolyline.setLatLngs(tempLatLngs); + } else if (currentMode === 'polygon') { + tempPolygonLatLngs.push(e.latlng); + if (!tempPolygon) tempPolygon = L.polygon(tempPolygonLatLngs, {color: '#2ecc71', fillColor: '#2ecc71', fillOpacity: 0.4}).addTo(map); + else tempPolygon.setLatLngs(tempPolygonLatLngs); + } +} +map.on('click', onMapClick); + +// --- LOGIKA SPBU --- +function simpanSPBU(event, lat, lng) { + event.preventDefault(); + let dataKirim = { lat: lat, lng: lng, nama_spbu: document.getElementById('nama_spbu').value, no_wa: document.getElementById('no_wa').value, buka_24jam: document.querySelector('input[name="buka_24jam"]:checked').value }; + + fetch('simpan_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'SPBU Berhasil Disimpan!' }); + map.closePopup(); + let markerBaru = L.marker([lat, lng], { icon: (dataKirim.buka_24jam === '1') ? ikonHijau : ikonMerah, draggable: true }).addTo(map); + markerBaru.bindPopup(`${dataKirim.nama_spbu}
WA: ${dataKirim.no_wa}
24 Jam: ${dataKirim.buka_24jam == '1' ? 'Ya' : 'Tidak'}
`); + markerBaru.on('dragend', function(e) { updateLokasiSPBU(data.id, e.target.getLatLng().lat, e.target.getLatLng().lng); }); + daftarMarker[data.id] = markerBaru; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan SPBU!' }); + }); +} +function updateLokasiSPBU(id, latBaru, lngBaru) { + fetch('update_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, lat: latBaru, lng: lngBaru }) }) + .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); +} +function hapusSPBU(id) { + Swal.fire({ title: 'Hapus Titik SPBU?', text: "Data tidak dapat dikembalikan!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Titik SPBU Dihapus!' }); if (daftarMarker[id]) { map.removeLayer(daftarMarker[id]); delete daftarMarker[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// --- LOGIKA JALAN --- +function batalPolyline() { if (tempPolyline) { map.removeLayer(tempPolyline); tempPolyline = null; } tempLatLngs = []; } +function simpanPolyline() { + if (tempLatLngs.length < 2) return Toast.fire({ icon: 'warning', title: 'Klik minimal 2 titik di peta!' }); + let nama = document.getElementById('input_nama_jalan').value, status = document.getElementById('input_status_jalan').value; + if (!nama) return Toast.fire({ icon: 'warning', title: 'Nama Jalan harus diisi!' }); + + let panjang = hitungPanjangJalan(tempLatLngs); + let dataKirim = { nama_jalan: nama, status: status, panjang_m: panjang, geojson: tempPolyline.toGeoJSON() }; + + fetch('simpan_jalan.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Jalan Disimpan (${panjang.toFixed(0)}m)` }); + tempPolyline.setStyle({color: warnaJalan(status)}); + tempPolyline.bindPopup(`Jalan: ${nama}
Status: ${status}
Panjang: ${panjang.toFixed(2)} m
`).openPopup(); + + daftarPolyline[data.id] = tempPolyline; + document.getElementById('input_nama_jalan').value = ''; + + // PERBAIKAN BUG: Hanya kosongkan variabel penampung sementara, jangan removeLayer dari map. + tempPolyline = null; + tempLatLngs = []; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); + }); +} +function hapusPolyline(id) { + Swal.fire({ title: 'Hapus Jalan?', text: "Data permanen dihapus!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_jalan.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Jalan Dihapus!' }); if (daftarPolyline[id]) { map.removeLayer(daftarPolyline[id]); delete daftarPolyline[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// --- LOGIKA KAVLING --- +function batalPolygon() { if (tempPolygon) { map.removeLayer(tempPolygon); tempPolygon = null; } tempPolygonLatLngs = []; } +function simpanPolygon() { + if (tempPolygonLatLngs.length < 3) return Toast.fire({ icon: 'warning', title: 'Minimal 3 titik untuk area!' }); + let nama = document.getElementById('input_nama_kavling').value, status = document.getElementById('input_status_tanah').value; + if (!nama) return Toast.fire({ icon: 'warning', title: 'Nama kavling harus diisi!' }); + + let luas = hitungLuasArea(tempPolygonLatLngs); + let dataKirim = { nama_kavling: nama, status: status, luas_m2: luas, geojson: tempPolygon.toGeoJSON() }; + + fetch('simpan_area.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Area Disimpan (${luas.toFixed(0)}m²)` }); + tempPolygon.setStyle({color: warnaPolygon(status), fillOpacity: 0.5}); + tempPolygon.bindPopup(`Kavling: ${nama}
Status: ${status}
Luas: ${luas.toFixed(2)} m²
`).openPopup(); + + daftarPolygon[data.id] = tempPolygon; + document.getElementById('input_nama_kavling').value = ''; + + // PERBAIKAN BUG: Sama seperti Polyline + tempPolygon = null; + tempPolygonLatLngs = []; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); + }); +} +function hapusPolygon(id) { + Swal.fire({ title: 'Hapus Kavling?', text: "Area ini akan dihapus permanen!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_area.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Kavling Dihapus!' }); if (daftarPolygon[id]) { map.removeLayer(daftarPolygon[id]); delete daftarPolygon[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// ========================================== +// --- LOGIKA MODE EDIT POLYLINE (JALAN) --- +// ========================================== + +function aktifkanEditJalan(id, namaJalan) { + let layer = daftarPolyline[id]; + if (!layer) return; + + // Jika sedang edit jalan lain, batalkan dulu + if (targetEditJalanId && targetEditJalanId !== id) { + batalkanEditJalan(); + } + + targetEditJalanId = id; + map.closePopup(); // Tutup popup info + + // Tampilkan panel info dan tombol simpan di sidebar + document.getElementById('info-edit-jalan').style.display = 'block'; + document.getElementById('nama-jalan-edit').innerText = "Edit: " + namaJalan; + document.getElementById('btn-simpan-edit-jalan').style.display = 'block'; + + // Aktifkan Leaflet.Geoman khusus pada layer ini + layer.pm.enable({ + allowSelfIntersection: false, + }); +} + +function batalkanEditJalan() { + if (targetEditJalanId && daftarPolyline[targetEditJalanId]) { + daftarPolyline[targetEditJalanId].pm.disable(); + } + targetEditJalanId = null; + document.getElementById('info-edit-jalan').style.display = 'none'; + document.getElementById('btn-simpan-edit-jalan').style.display = 'none'; +} + +function simpanEditPolyline() { + if (!targetEditJalanId) return; + + let layer = daftarPolyline[targetEditJalanId]; + if (!layer) return; + + // 1. Matikan mode edit + layer.pm.disable(); + + // 2. Kalkulasi ulang panjang + let latlngsBaru = layer.getLatLngs(); + let panjangBaru = hitungPanjangJalan(latlngsBaru); + + // 3. Konversi ke GeoJSON + let geojsonBaru = layer.toGeoJSON(); + + let dataKirim = { + id: targetEditJalanId, + panjang_m: panjangBaru, + geojson: geojsonBaru + }; + + fetch('update_jalan.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(dataKirim) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Bentuk Jalan Diperbarui!\nPanjang: ${panjangBaru.toFixed(0)}m` }); + batalkanEditJalan(); + + // Reload halaman secara otomatis setelah 1.5 detik agar data popup ikut ter-refresh + setTimeout(() => { window.location.reload(); }, 1500); + } else { + Toast.fire({ icon: 'error', title: 'Gagal memperbarui jalan: ' + data.message }); + batalkanEditJalan(); + } + }).catch(error => console.error('Error:', error)); +} + + +// =========================================== +// --- LOGIKA MODE EDIT POLYGON (KAVLING) --- +// =========================================== + +function aktifkanEditKavling(id, namaKavling) { + let layer = daftarPolygon[id]; + if (!layer) return; + + // Jika sedang edit kavling lain, batalkan dulu + if (targetEditKavlingId && targetEditKavlingId !== id) { + batalkanEditKavling(); + } + + targetEditKavlingId = id; + map.closePopup(); + + // Tampilkan panel info dan tombol simpan di sidebar + document.getElementById('info-edit-kavling').style.display = 'block'; + document.getElementById('nama-kavling-edit').innerText = "Edit: " + namaKavling; + document.getElementById('btn-simpan-edit-kavling').style.display = 'block'; + + // Aktifkan Leaflet.Geoman khusus pada layer ini + layer.pm.enable({ + allowSelfIntersection: false, + }); +} + +function batalkanEditKavling() { + if (targetEditKavlingId && daftarPolygon[targetEditKavlingId]) { + daftarPolygon[targetEditKavlingId].pm.disable(); + } + targetEditKavlingId = null; + document.getElementById('info-edit-kavling').style.display = 'none'; + document.getElementById('btn-simpan-edit-kavling').style.display = 'none'; +} + +function simpanEditPolygon() { + if (!targetEditKavlingId) return; + + let layer = daftarPolygon[targetEditKavlingId]; + if (!layer) return; + + layer.pm.disable(); + + let latlngsBaru = layer.getLatLngs(); + let luasBaru = hitungLuasArea(latlngsBaru); + let geojsonBaru = layer.toGeoJSON(); + + let dataKirim = { + id: targetEditKavlingId, + luas_m2: luasBaru, + geojson: geojsonBaru + }; + + fetch('update_area.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(dataKirim) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Bentuk Kavling Diperbarui!\nLuas: ${luasBaru.toFixed(0)}m²` }); + batalkanEditKavling(); + + setTimeout(() => { window.location.reload(); }, 1500); + } else { + Toast.fire({ icon: 'error', title: 'Gagal memperbarui kavling: ' + data.message }); + batalkanEditKavling(); + } + }).catch(error => console.error('Error:', error)); +} \ No newline at end of file diff --git a/01/simpan_area.php b/01/simpan_area.php new file mode 100644 index 0000000..566b5b0 --- /dev/null +++ b/01/simpan_area.php @@ -0,0 +1,33 @@ +prepare("INSERT INTO area_polygon (nama_kavling, status_kepemilikan, luas_m2, geojson) VALUES (?, ?, ?, ?)"); + + // Parameter: "ssds" berarti String, String, Double (Desimal), String + $stmt->bind_param("ssds", $nama_kavling, $status, $luas_m2, $geojson_string); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "id" => $stmt->insert_id]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + // Tambahkan pesan error jika data tidak lengkap + echo json_encode(["status" => "error", "message" => "Data area tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/01/simpan_jalan.php b/01/simpan_jalan.php new file mode 100644 index 0000000..785c6b2 --- /dev/null +++ b/01/simpan_jalan.php @@ -0,0 +1,26 @@ +prepare("INSERT INTO jalan_polyline (nama_jalan, status, panjang_m, geojson) VALUES (?, ?, ?, ?)"); + $stmt->bind_param("ssds", $nama_jalan, $status, $panjang_m, $geojson_string); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "id" => $stmt->insert_id]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/01/simpan_spbu.php b/01/simpan_spbu.php new file mode 100644 index 0000000..ec59c32 --- /dev/null +++ b/01/simpan_spbu.php @@ -0,0 +1,54 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +// Ambil data JSON yang dikirim oleh Fetch API +$data = json_decode(file_get_contents("php://input"), true); + +if ($data) { + $lat = $data['lat']; + $lng = $data['lng']; + $nama_spbu = $data['nama_spbu']; + $no_wa = $data['no_wa']; + $buka_24jam = $data['buka_24jam']; + + // Gunakan Prepared Statement untuk keamanan (mencegah SQL Injection) + $stmt = $conn->prepare("INSERT INTO spbu_point (lat, lng, nama_spbu, no_wa, buka_24jam) VALUES (?, ?, ?, ?, ?)"); + $stmt->bind_param("ddssi", $lat, $lng, $nama_spbu, $no_wa, $buka_24jam); + + if ($stmt->execute()) { + // AMBIL ID TERAKHIR YANG BARU SAJA DISIMPAN + $new_id = $stmt->insert_id; + + // Kirim kembali ID tersebut ke JavaScript + echo json_encode([ + "status" => "success", + "message" => "Data berhasil disimpan", + "id" => $new_id + ]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal mengeksekusi query: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Tidak ada data yang diterima"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/01/style.css b/01/style.css new file mode 100644 index 0000000..f01a35c --- /dev/null +++ b/01/style.css @@ -0,0 +1,52 @@ +html, body { + height: 100vh; width: 100vw; margin: 0; padding: 0; + display: flex; font-family: Arial, sans-serif; overflow: hidden; +} + +#sidebar { + width: 280px; background-color: #f8f9fa; padding: 20px; + box-shadow: 2px 0 5px rgba(0,0,0,0.2); z-index: 1000; overflow-y: auto; +} + +#sidebar h3 { margin-top: 0; border-bottom: 2px solid #ddd; padding-bottom: 10px; } + +/* Styling Ikon Mode */ +.mode-selector { margin-bottom: 20px; } +.mode-buttons { display: flex; gap: 10px; justify-content: space-between; } +.mode-btn { + flex: 1; padding: 15px 5px; background: #fff; border: 2px solid #ccc; + border-radius: 8px; cursor: pointer; text-align: center; + transition: all 0.3s ease; color: #555; font-size: 12px; font-weight: bold; +} +.mode-btn i { font-size: 24px; margin-bottom: 8px; display: block; } +.mode-btn:hover { background: #e9ecef; } + +/* Warna saat tombol mode aktif */ +.mode-btn.active[data-mode="point"] { border-color: #e74c3c; color: #e74c3c; background: #fadbd8; } +.mode-btn.active[data-mode="polyline"] { border-color: #3498db; color: #3498db; background: #d6eaf8; } +.mode-btn.active[data-mode="polygon"] { border-color: #2ecc71; color: #2ecc71; background: #d5f5e3; } +/* Styling tombol edit aktif */ +.mode-btn.active[data-mode="edit_jalan"] { background-color: #f1c40f !important; color: white !important; } +.mode-btn.active[data-mode="edit_kavling"] { background-color: #27ae60 !important; color: white !important; } + +/* Styling Input & Action Buttons */ +.action-buttons { + display: none; background: #fff; padding: 15px; + border-radius: 8px; border: 1px solid #ddd; margin-top: 10px; +} + +.action-buttons input, .action-buttons select { + width: 100%; margin-bottom: 15px; padding: 8px; box-sizing: border-box; + border: 1px solid #ccc; border-radius: 4px; +} + +.action-buttons button { + width: 100%; padding: 10px; margin-bottom: 8px; cursor: pointer; + border-radius: 5px; font-weight: bold; +} + +.btn-success { background-color: #2ecc71; color: white; border: none; } +.btn-danger { background-color: #e74c3c; color: white; border: none; } + +#map-container { flex: 1; position: relative; } +#map { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } \ No newline at end of file diff --git a/01/update_area.php b/01/update_area.php new file mode 100644 index 0000000..cf93d84 --- /dev/null +++ b/01/update_area.php @@ -0,0 +1,31 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['geojson'])) { + $id = $data['id']; + $luas_m2 = $data['luas_m2']; + $geojson_string = json_encode($data['geojson']); + + $stmt = $conn->prepare("UPDATE area_polygon SET luas_m2 = ?, geojson = ? WHERE id = ?"); + $stmt->bind_param("dsi", $luas_m2, $geojson_string, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/01/update_jalan.php b/01/update_jalan.php new file mode 100644 index 0000000..e36d3ff --- /dev/null +++ b/01/update_jalan.php @@ -0,0 +1,31 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['geojson'])) { + $id = $data['id']; + $panjang_m = $data['panjang_m']; + $geojson_string = json_encode($data['geojson']); + + $stmt = $conn->prepare("UPDATE jalan_polyline SET panjang_m = ?, geojson = ? WHERE id = ?"); + $stmt->bind_param("dsi", $panjang_m, $geojson_string, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/01/update_spbu.php b/01/update_spbu.php new file mode 100644 index 0000000..1465990 --- /dev/null +++ b/01/update_spbu.php @@ -0,0 +1,40 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['lat']) && isset($data['lng'])) { + $id = $data['id']; + $lat = $data['lat']; + $lng = $data['lng']; + + // Update lat dan lng berdasarkan ID + $stmt = $conn->prepare("UPDATE spbu_point SET lat = ?, lng = ? WHERE id = ?"); + $stmt->bind_param("ddi", $lat, $lng, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Lokasi berhasil diperbarui"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal update lokasi: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/02/hapus_area.php b/02/hapus_area.php new file mode 100644 index 0000000..819df19 --- /dev/null +++ b/02/hapus_area.php @@ -0,0 +1,18 @@ +prepare("DELETE FROM area_polygon WHERE id = ?"); + $stmt->bind_param("i", $id); + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} +$conn->close(); +?> \ No newline at end of file diff --git a/02/hapus_jalan.php b/02/hapus_jalan.php new file mode 100644 index 0000000..175d531 --- /dev/null +++ b/02/hapus_jalan.php @@ -0,0 +1,38 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id'])) { + $id = $data['id']; + + // Hapus data dari tabel jalan_polyline berdasarkan ID + $stmt = $conn->prepare("DELETE FROM jalan_polyline WHERE id = ?"); + $stmt->bind_param("i", $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Garis jalan berhasil dihapus"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal menghapus data: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "ID tidak valid"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/02/hapus_spbu.php b/02/hapus_spbu.php new file mode 100644 index 0000000..ec8d657 --- /dev/null +++ b/02/hapus_spbu.php @@ -0,0 +1,37 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id'])) { + $id = $data['id']; + + $stmt = $conn->prepare("DELETE FROM spbu_point WHERE id = ?"); + $stmt->bind_param("i", $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Data berhasil dihapus"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal menghapus data: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "ID tidak valid"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/02/index.html b/02/index.html new file mode 100644 index 0000000..fa564f9 --- /dev/null +++ b/02/index.html @@ -0,0 +1,104 @@ + + + + + + Sistem Pemetaan + + + + + + + + + + + + +
+
+
+ + + + + + + \ No newline at end of file diff --git a/02/load_area.php b/02/load_area.php new file mode 100644 index 0000000..f583e4b --- /dev/null +++ b/02/load_area.php @@ -0,0 +1,20 @@ +query($sql); +$data_area = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + // Mengubah string geojson dari database kembali menjadi Object JSON + // Ini wajib agar Leaflet.geoJSON() di Javascript bisa merendernya + $row['geojson'] = json_decode($row['geojson']); + $data_area[] = $row; + } +} + +echo json_encode($data_area); +$conn->close(); +?> \ No newline at end of file diff --git a/02/load_jalan.php b/02/load_jalan.php new file mode 100644 index 0000000..7455a87 --- /dev/null +++ b/02/load_jalan.php @@ -0,0 +1,18 @@ +query($sql); +$data_jalan = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + // Parse string geojson menjadi object agar mudah dibaca Javascript + $row['geojson'] = json_decode($row['geojson']); + $data_jalan[] = $row; + } +} +echo json_encode($data_jalan); +$conn->close(); +?> \ No newline at end of file diff --git a/02/load_spbu.php b/02/load_spbu.php new file mode 100644 index 0000000..ed542b0 --- /dev/null +++ b/02/load_spbu.php @@ -0,0 +1,33 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +// Ambil semua data dari tabel spbu_point +$sql = "SELECT * FROM spbu_point"; +$result = $conn->query($sql); + +$data_spbu = array(); + +if ($result->num_rows > 0) { + while($row = $result->fetch_assoc()) { + $data_spbu[] = $row; + } +} + +// Kirim data sebagai JSON +echo json_encode($data_spbu); + +$conn->close(); +?> \ No newline at end of file diff --git a/02/script.js b/02/script.js new file mode 100644 index 0000000..e9cd1a3 --- /dev/null +++ b/02/script.js @@ -0,0 +1,462 @@ +// --- INISIALISASI PETA (Koordinat Pontianak) --- +const map = L.map('map').setView([-0.055348, 109.349512], 13); +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); + +const popup = L.popup(); +const daftarMarker = {}, daftarPolyline = {}, daftarPolygon = {}; + +// 1. BUAT KERTAS TRANSPARAN (LAYER GROUPS) +const layerSPBU24 = L.layerGroup().addTo(map); // SPBU 24 Jam +const layerSPBUNon24 = L.layerGroup().addTo(map); // SPBU Tidak 24 Jam +const layerJalan = L.layerGroup().addTo(map); // Jaringan Jalan +const layerKavling = L.layerGroup().addTo(map); // Area Kavling Tanah + +// 2. DAFTARKAN LAYER-LAYER TERSEBUT KE DALAM CONTROL PANEL +const overlays = { + " SPBU (Buka 24 Jam)": layerSPBU24, + " SPBU (Tidak 24 Jam)": layerSPBUNon24, + " Jaringan Jalan": layerJalan, + " Kavling Tanah": layerKavling +}; + +// 3. MUNCULKAN KONTROL DI POJOK KANAN ATAS PETA +L.control.layers(null, overlays, { collapsed: false }).addTo(map); + +let currentMode = 'point'; +let tempLatLngs = [], tempPolyline = null; +let tempPolygonLatLngs = [], tempPolygon = null; +let targetEditJalanId = null; // Menyimpan ID jalan yang sedang diedit +let targetEditKavlingId = null; // Menyimpan ID kavling yang sedang diedit + +// Konfigurasi SweetAlert2 (Toast Pojok Kanan Atas) +const Toast = Swal.mixin({ + toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true +}); + +// Definisi Ikon Kustom +const ikonHijau = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); +const ikonMerah = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); + +// --- MANAJEMEN MODE UI --- +function ubahMode(mode) { + currentMode = mode; + + // Matikan mode edit Geoman global jika sedang aktif + map.pm.disableGlobalEditMode(); + + // Reset state edit + if (targetEditJalanId) batalkanEditJalan(); + if (targetEditKavlingId) batalkanEditKavling(); + + // Atur tampilan tombol aktif + document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); + document.querySelector(`.mode-btn[data-mode="${mode}"]`).classList.add('active'); + + // Munculkan menu yang sesuai di sidebar + document.getElementById('polyline-actions').style.display = (mode === 'polyline') ? 'block' : 'none'; + document.getElementById('polygon-actions').style.display = (mode === 'polygon') ? 'block' : 'none'; + document.getElementById('edit-jalan-actions').style.display = (mode === 'edit_jalan') ? 'block' : 'none'; + document.getElementById('edit-kavling-actions').style.display = (mode === 'edit_kavling') ? 'block' : 'none'; + + map.closePopup(); + if (mode !== 'polyline') batalPolyline(); + if (mode !== 'polygon') batalPolygon(); +} + +// --- FUNGSI BANTUAN --- +function hitungPanjangJalan(latlngs) { let t=0; for (let i=0; i res.json()).then(data => { + data.forEach(spbu => { + let targetLayer = (spbu.buka_24jam == '1') ? layerSPBU24 : layerSPBUNon24; + let marker = L.marker([spbu.lat, spbu.lng], { icon: (spbu.buka_24jam == '1') ? ikonHijau : ikonMerah, draggable: true }).addTo(targetLayer); + marker.bindPopup(`${spbu.nama_spbu}
WA: ${spbu.no_wa}
24 Jam: ${spbu.buka_24jam == '1' ? 'Ya' : 'Tidak'}
`); + marker.on('dragend', function(e) { updateLokasiSPBU(spbu.id, e.target.getLatLng().lat, e.target.getLatLng().lng); }); + daftarMarker[spbu.id] = marker; + }); + }); + + fetch('load_jalan.php').then(res => res.json()).then(data => { + data.forEach(jalan => { + let geojsonGroup = L.geoJSON(jalan.geojson, { style: { color: warnaJalan(jalan.status), weight: 4 } }).addTo(layerJalan); + let polylineLayer = geojsonGroup.getLayers()[0]; // Ambil layer aslinya + + polylineLayer.bindPopup(`Jalan: ${jalan.nama_jalan}
Status: ${jalan.status}
Panjang: ${parseFloat(jalan.panjang_m).toFixed(2)} m
`); + + // Tambahkan event click khusus untuk mode edit + polylineLayer.on('click', function(e) { + if (currentMode === 'edit_jalan') { + aktifkanEditJalan(jalan.id, jalan.nama_jalan); + } + }); + + daftarPolyline[jalan.id] = polylineLayer; + }); + }); + + fetch('load_area.php').then(res => res.json()).then(data => { + data.forEach(area => { + let geojsonGroup = L.geoJSON(area.geojson, { style: { color: warnaPolygon(area.status_kepemilikan), fillOpacity: 0.4 } }).addTo(layerKavling); + let polygonLayer = geojsonGroup.getLayers()[0]; // Ambil layer aslinya + + polygonLayer.bindPopup(`Kavling: ${area.nama_kavling}
Status: ${area.status_kepemilikan}
Luas: ${parseFloat(area.luas_m2).toFixed(2)} m²
`); + + // Tambahkan event click khusus untuk mode edit + polygonLayer.on('click', function(e) { + if (currentMode === 'edit_kavling') { + aktifkanEditKavling(area.id, area.nama_kavling); + } + }); + + daftarPolygon[area.id] = polygonLayer; + }); + }); +} +loadDB(); + +// --- FUNGSI UNTUK ME-REFRESH PETA SECARA OTOMATIS (SOFT REFRESH) --- +function refreshPeta() { + // 1. Bersihkan semua layer dari peta + layerSPBU24.clearLayers(); + layerSPBUNon24.clearLayers(); + layerJalan.clearLayers(); + layerKavling.clearLayers(); + + // 2. Bersihkan memori agar ID tidak menumpuk/ganda + for (let id in daftarMarker) delete daftarMarker[id]; + for (let id in daftarPolyline) delete daftarPolyline[id]; + for (let id in daftarPolygon) delete daftarPolygon[id]; + + // 3. Panggil ulang data terbaru dari database + loadDB(); +} + +// --- EVENT KLIK PETA --- +function onMapClick(e) { + if (currentMode === 'point') { + let formHtml = ` +
+

SPBU Baru

+
+ + +
+   +

+ +
+
`; + popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); + } else if (currentMode === 'polyline') { + tempLatLngs.push(e.latlng); + if (!tempPolyline) tempPolyline = L.polyline(tempLatLngs, {color: '#3498db', weight: 4}).addTo(map); + else tempPolyline.setLatLngs(tempLatLngs); + } else if (currentMode === 'polygon') { + tempPolygonLatLngs.push(e.latlng); + if (!tempPolygon) tempPolygon = L.polygon(tempPolygonLatLngs, {color: '#2ecc71', fillColor: '#2ecc71', fillOpacity: 0.4}).addTo(map); + else tempPolygon.setLatLngs(tempPolygonLatLngs); + } +} +map.on('click', onMapClick); + +// --- LOGIKA SPBU --- +function simpanSPBU(event, lat, lng) { + event.preventDefault(); + let dataKirim = { lat: lat, lng: lng, nama_spbu: document.getElementById('nama_spbu').value, no_wa: document.getElementById('no_wa').value, buka_24jam: document.querySelector('input[name="buka_24jam"]:checked').value }; + + fetch('simpan_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'SPBU Berhasil Disimpan!' }); + map.closePopup(); + refreshPeta(); + let targetLayerBaru = (dataKirim.buka_24jam === '1') ? layerSPBU24 : layerSPBUNon24; + let markerBaru = L.marker([lat, lng], { icon: (dataKirim.buka_24jam === '1') ? ikonHijau : ikonMerah, draggable: true }).addTo(targetLayerBaru); + markerBaru.bindPopup(`${dataKirim.nama_spbu}
WA: ${dataKirim.no_wa}
24 Jam: ${dataKirim.buka_24jam == '1' ? 'Ya' : 'Tidak'}
`); + markerBaru.on('dragend', function(e) { updateLokasiSPBU(data.id, e.target.getLatLng().lat, e.target.getLatLng().lng); }); + daftarMarker[data.id] = markerBaru; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan SPBU!' }); + }); +} +function updateLokasiSPBU(id, latBaru, lngBaru) { + fetch('update_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id, lat: latBaru, lng: lngBaru }) }) + .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); +} +function hapusSPBU(id) { + Swal.fire({ title: 'Hapus Titik SPBU?', text: "Data tidak dapat dikembalikan!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_spbu.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Titik SPBU Dihapus!' }); if (daftarMarker[id]) { map.removeLayer(daftarMarker[id]); delete daftarMarker[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// --- LOGIKA JALAN --- +function batalPolyline() { if (tempPolyline) { map.removeLayer(tempPolyline); tempPolyline = null; } tempLatLngs = []; } +function simpanPolyline() { + if (tempLatLngs.length < 2) return Toast.fire({ icon: 'warning', title: 'Klik minimal 2 titik di peta!' }); + let nama = document.getElementById('input_nama_jalan').value, status = document.getElementById('input_status_jalan').value; + if (!nama) return Toast.fire({ icon: 'warning', title: 'Nama Jalan harus diisi!' }); + + let panjang = hitungPanjangJalan(tempLatLngs); + let dataKirim = { nama_jalan: nama, status: status, panjang_m: panjang, geojson: tempPolyline.toGeoJSON() }; + + fetch('simpan_jalan.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Jalan Disimpan (${panjang.toFixed(0)}m)` }); + tempPolyline.setStyle({color: warnaJalan(status)}); + tempPolyline.bindPopup(`Jalan: ${nama}
Status: ${status}
Panjang: ${panjang.toFixed(2)} m
`).openPopup(); + + layerJalan.addLayer(tempPolyline); + map.removeLayer(tempPolyline); + + tempPolyline.on('click', function(e) { + if (currentMode === 'edit_jalan') { + aktifkanEditJalan(data.id, nama); + } + }); + + daftarPolyline[data.id] = tempPolyline; + document.getElementById('input_nama_jalan').value = ''; + refreshPeta(); + + // PERBAIKAN BUG: Hanya kosongkan variabel penampung sementara, jangan removeLayer dari map. + tempPolyline = null; + tempLatLngs = []; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); + }); +} +function hapusPolyline(id) { + Swal.fire({ title: 'Hapus Jalan?', text: "Data permanen dihapus!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_jalan.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Jalan Dihapus!' }); if (daftarPolyline[id]) { map.removeLayer(daftarPolyline[id]); delete daftarPolyline[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// --- LOGIKA KAVLING --- +function batalPolygon() { if (tempPolygon) { map.removeLayer(tempPolygon); tempPolygon = null; } tempPolygonLatLngs = []; } +function simpanPolygon() { + if (tempPolygonLatLngs.length < 3) return Toast.fire({ icon: 'warning', title: 'Minimal 3 titik untuk area!' }); + let nama = document.getElementById('input_nama_kavling').value, status = document.getElementById('input_status_tanah').value; + if (!nama) return Toast.fire({ icon: 'warning', title: 'Nama kavling harus diisi!' }); + + let luas = hitungLuasArea(tempPolygonLatLngs); + let dataKirim = { nama_kavling: nama, status: status, luas_m2: luas, geojson: tempPolygon.toGeoJSON() }; + + fetch('simpan_area.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Area Disimpan (${luas.toFixed(0)}m²)` }); + tempPolygon.setStyle({color: warnaPolygon(status), fillOpacity: 0.5}); + tempPolygon.bindPopup(`Kavling: ${nama}
Status: ${status}
Luas: ${luas.toFixed(2)} m²
`).openPopup(); + + layerKavling.addLayer(tempPolygon); + map.removeLayer(tempPolygon); + + tempPolygon.on('click', function(e) { + if (currentMode === 'edit_kavling') { + aktifkanEditKavling(data.id, nama); + } + }); + + daftarPolygon[data.id] = tempPolygon; + document.getElementById('input_nama_kavling').value = ''; + refreshPeta(); + + // PERBAIKAN BUG: Sama seperti Polyline + tempPolygon = null; + tempPolygonLatLngs = []; + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); + }); +} +function hapusPolygon(id) { + Swal.fire({ title: 'Hapus Kavling?', text: "Area ini akan dihapus permanen!", icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Ya, Hapus!' + }).then((result) => { + if (result.isConfirmed) { + fetch('hapus_area.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { Toast.fire({ icon: 'success', title: 'Kavling Dihapus!' }); if (daftarPolygon[id]) { map.removeLayer(daftarPolygon[id]); delete daftarPolygon[id]; } } + else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +// ========================================== +// --- LOGIKA MODE EDIT POLYLINE (JALAN) --- +// ========================================== + +function aktifkanEditJalan(id, namaJalan) { + let layer = daftarPolyline[id]; + if (!layer) return; + + // Jika sedang edit jalan lain, batalkan dulu + if (targetEditJalanId && targetEditJalanId !== id) { + batalkanEditJalan(); + } + + targetEditJalanId = id; + map.closePopup(); // Tutup popup info + + // Tampilkan panel info dan tombol simpan di sidebar + document.getElementById('info-edit-jalan').style.display = 'block'; + document.getElementById('nama-jalan-edit').innerText = "Edit: " + namaJalan; + document.getElementById('btn-simpan-edit-jalan').style.display = 'block'; + + // Aktifkan Leaflet.Geoman khusus pada layer ini + layer.pm.enable({ + allowSelfIntersection: false, + }); +} + +function batalkanEditJalan() { + if (targetEditJalanId && daftarPolyline[targetEditJalanId]) { + daftarPolyline[targetEditJalanId].pm.disable(); + } + targetEditJalanId = null; + document.getElementById('info-edit-jalan').style.display = 'none'; + document.getElementById('btn-simpan-edit-jalan').style.display = 'none'; +} + +function simpanEditPolyline() { + if (!targetEditJalanId) return; + + let layer = daftarPolyline[targetEditJalanId]; + if (!layer) return; + + // 1. Matikan mode edit + layer.pm.disable(); + + // 2. Kalkulasi ulang panjang + let latlngsBaru = layer.getLatLngs(); + let panjangBaru = hitungPanjangJalan(latlngsBaru); + + // 3. Konversi ke GeoJSON + let geojsonBaru = layer.toGeoJSON(); + + let dataKirim = { + id: targetEditJalanId, + panjang_m: panjangBaru, + geojson: geojsonBaru + }; + + fetch('update_jalan.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(dataKirim) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Bentuk Jalan Diperbarui!\nPanjang: ${panjangBaru.toFixed(0)}m` }); + batalkanEditJalan(); + + // Reload halaman secara otomatis setelah 1.5 detik agar data popup ikut ter-refresh + setTimeout(() => { window.location.reload(); }, 1500); + } else { + Toast.fire({ icon: 'error', title: 'Gagal memperbarui jalan: ' + data.message }); + batalkanEditJalan(); + } + }).catch(error => console.error('Error:', error)); +} + + +// =========================================== +// --- LOGIKA MODE EDIT POLYGON (KAVLING) --- +// =========================================== + +function aktifkanEditKavling(id, namaKavling) { + let layer = daftarPolygon[id]; + if (!layer) return; + + // Jika sedang edit kavling lain, batalkan dulu + if (targetEditKavlingId && targetEditKavlingId !== id) { + batalkanEditKavling(); + } + + targetEditKavlingId = id; + map.closePopup(); + + // Tampilkan panel info dan tombol simpan di sidebar + document.getElementById('info-edit-kavling').style.display = 'block'; + document.getElementById('nama-kavling-edit').innerText = "Edit: " + namaKavling; + document.getElementById('btn-simpan-edit-kavling').style.display = 'block'; + + // Aktifkan Leaflet.Geoman khusus pada layer ini + layer.pm.enable({ + allowSelfIntersection: false, + }); +} + +function batalkanEditKavling() { + if (targetEditKavlingId && daftarPolygon[targetEditKavlingId]) { + daftarPolygon[targetEditKavlingId].pm.disable(); + } + targetEditKavlingId = null; + document.getElementById('info-edit-kavling').style.display = 'none'; + document.getElementById('btn-simpan-edit-kavling').style.display = 'none'; +} + +function simpanEditPolygon() { + if (!targetEditKavlingId) return; + + let layer = daftarPolygon[targetEditKavlingId]; + if (!layer) return; + + layer.pm.disable(); + + let latlngsBaru = layer.getLatLngs(); + let luasBaru = hitungLuasArea(latlngsBaru); + let geojsonBaru = layer.toGeoJSON(); + + let dataKirim = { + id: targetEditKavlingId, + luas_m2: luasBaru, + geojson: geojsonBaru + }; + + fetch('update_area.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(dataKirim) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: `Bentuk Kavling Diperbarui!\nLuas: ${luasBaru.toFixed(0)}m²` }); + batalkanEditKavling(); + + setTimeout(() => { window.location.reload(); }, 1500); + } else { + Toast.fire({ icon: 'error', title: 'Gagal memperbarui kavling: ' + data.message }); + batalkanEditKavling(); + } + }).catch(error => console.error('Error:', error)); +} \ No newline at end of file diff --git a/02/simpan_area.php b/02/simpan_area.php new file mode 100644 index 0000000..566b5b0 --- /dev/null +++ b/02/simpan_area.php @@ -0,0 +1,33 @@ +prepare("INSERT INTO area_polygon (nama_kavling, status_kepemilikan, luas_m2, geojson) VALUES (?, ?, ?, ?)"); + + // Parameter: "ssds" berarti String, String, Double (Desimal), String + $stmt->bind_param("ssds", $nama_kavling, $status, $luas_m2, $geojson_string); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "id" => $stmt->insert_id]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + // Tambahkan pesan error jika data tidak lengkap + echo json_encode(["status" => "error", "message" => "Data area tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/02/simpan_jalan.php b/02/simpan_jalan.php new file mode 100644 index 0000000..785c6b2 --- /dev/null +++ b/02/simpan_jalan.php @@ -0,0 +1,26 @@ +prepare("INSERT INTO jalan_polyline (nama_jalan, status, panjang_m, geojson) VALUES (?, ?, ?, ?)"); + $stmt->bind_param("ssds", $nama_jalan, $status, $panjang_m, $geojson_string); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "id" => $stmt->insert_id]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/02/simpan_spbu.php b/02/simpan_spbu.php new file mode 100644 index 0000000..ec59c32 --- /dev/null +++ b/02/simpan_spbu.php @@ -0,0 +1,54 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +// Ambil data JSON yang dikirim oleh Fetch API +$data = json_decode(file_get_contents("php://input"), true); + +if ($data) { + $lat = $data['lat']; + $lng = $data['lng']; + $nama_spbu = $data['nama_spbu']; + $no_wa = $data['no_wa']; + $buka_24jam = $data['buka_24jam']; + + // Gunakan Prepared Statement untuk keamanan (mencegah SQL Injection) + $stmt = $conn->prepare("INSERT INTO spbu_point (lat, lng, nama_spbu, no_wa, buka_24jam) VALUES (?, ?, ?, ?, ?)"); + $stmt->bind_param("ddssi", $lat, $lng, $nama_spbu, $no_wa, $buka_24jam); + + if ($stmt->execute()) { + // AMBIL ID TERAKHIR YANG BARU SAJA DISIMPAN + $new_id = $stmt->insert_id; + + // Kirim kembali ID tersebut ke JavaScript + echo json_encode([ + "status" => "success", + "message" => "Data berhasil disimpan", + "id" => $new_id + ]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal mengeksekusi query: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Tidak ada data yang diterima"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/02/style.css b/02/style.css new file mode 100644 index 0000000..f01a35c --- /dev/null +++ b/02/style.css @@ -0,0 +1,52 @@ +html, body { + height: 100vh; width: 100vw; margin: 0; padding: 0; + display: flex; font-family: Arial, sans-serif; overflow: hidden; +} + +#sidebar { + width: 280px; background-color: #f8f9fa; padding: 20px; + box-shadow: 2px 0 5px rgba(0,0,0,0.2); z-index: 1000; overflow-y: auto; +} + +#sidebar h3 { margin-top: 0; border-bottom: 2px solid #ddd; padding-bottom: 10px; } + +/* Styling Ikon Mode */ +.mode-selector { margin-bottom: 20px; } +.mode-buttons { display: flex; gap: 10px; justify-content: space-between; } +.mode-btn { + flex: 1; padding: 15px 5px; background: #fff; border: 2px solid #ccc; + border-radius: 8px; cursor: pointer; text-align: center; + transition: all 0.3s ease; color: #555; font-size: 12px; font-weight: bold; +} +.mode-btn i { font-size: 24px; margin-bottom: 8px; display: block; } +.mode-btn:hover { background: #e9ecef; } + +/* Warna saat tombol mode aktif */ +.mode-btn.active[data-mode="point"] { border-color: #e74c3c; color: #e74c3c; background: #fadbd8; } +.mode-btn.active[data-mode="polyline"] { border-color: #3498db; color: #3498db; background: #d6eaf8; } +.mode-btn.active[data-mode="polygon"] { border-color: #2ecc71; color: #2ecc71; background: #d5f5e3; } +/* Styling tombol edit aktif */ +.mode-btn.active[data-mode="edit_jalan"] { background-color: #f1c40f !important; color: white !important; } +.mode-btn.active[data-mode="edit_kavling"] { background-color: #27ae60 !important; color: white !important; } + +/* Styling Input & Action Buttons */ +.action-buttons { + display: none; background: #fff; padding: 15px; + border-radius: 8px; border: 1px solid #ddd; margin-top: 10px; +} + +.action-buttons input, .action-buttons select { + width: 100%; margin-bottom: 15px; padding: 8px; box-sizing: border-box; + border: 1px solid #ccc; border-radius: 4px; +} + +.action-buttons button { + width: 100%; padding: 10px; margin-bottom: 8px; cursor: pointer; + border-radius: 5px; font-weight: bold; +} + +.btn-success { background-color: #2ecc71; color: white; border: none; } +.btn-danger { background-color: #e74c3c; color: white; border: none; } + +#map-container { flex: 1; position: relative; } +#map { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } \ No newline at end of file diff --git a/02/update_area.php b/02/update_area.php new file mode 100644 index 0000000..cf93d84 --- /dev/null +++ b/02/update_area.php @@ -0,0 +1,31 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['geojson'])) { + $id = $data['id']; + $luas_m2 = $data['luas_m2']; + $geojson_string = json_encode($data['geojson']); + + $stmt = $conn->prepare("UPDATE area_polygon SET luas_m2 = ?, geojson = ? WHERE id = ?"); + $stmt->bind_param("dsi", $luas_m2, $geojson_string, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/02/update_jalan.php b/02/update_jalan.php new file mode 100644 index 0000000..e36d3ff --- /dev/null +++ b/02/update_jalan.php @@ -0,0 +1,31 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['geojson'])) { + $id = $data['id']; + $panjang_m = $data['panjang_m']; + $geojson_string = json_encode($data['geojson']); + + $stmt = $conn->prepare("UPDATE jalan_polyline SET panjang_m = ?, geojson = ? WHERE id = ?"); + $stmt->bind_param("dsi", $panjang_m, $geojson_string, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success"]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/02/update_spbu.php b/02/update_spbu.php new file mode 100644 index 0000000..1465990 --- /dev/null +++ b/02/update_spbu.php @@ -0,0 +1,40 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id']) && isset($data['lat']) && isset($data['lng'])) { + $id = $data['id']; + $lat = $data['lat']; + $lng = $data['lng']; + + // Update lat dan lng berdasarkan ID + $stmt = $conn->prepare("UPDATE spbu_point SET lat = ?, lng = ? WHERE id = ?"); + $stmt->bind_param("ddi", $lat, $lng, $id); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "message" => "Lokasi berhasil diperbarui"]); + } else { + echo json_encode(["status" => "error", "message" => "Gagal update lokasi: " . $stmt->error]); + } + + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/03/api/prasejahtera/hapus_prasejahtera.php b/03/api/prasejahtera/hapus_prasejahtera.php new file mode 100644 index 0000000..a231a56 --- /dev/null +++ b/03/api/prasejahtera/hapus_prasejahtera.php @@ -0,0 +1,40 @@ +connect_error) { + echo json_encode(["status" => "error", "message" => "Koneksi database gagal"]); + exit; +} + +// Menangkap data JSON yang dikirim dari fetch() JavaScript +$data = json_decode(file_get_contents("php://input"), true); + +if ($data && isset($data['id'])) { + $id = $data['id']; + + // Sesuaikan nama tabel dengan screenshot phpMyAdmin Anda: rumah_prasejahtera + $stmt = $conn->prepare("DELETE FROM rumah_prasejahtera WHERE id = ?"); + $stmt->bind_param("i", $id); // "i" berarti integer + + if ($stmt->execute()) { + // Cek apakah benar-benar ada baris yang terhapus di database + if ($stmt->affected_rows > 0) { + echo json_encode(["status" => "success"]); + } else { + // Jika ID tidak ditemukan di tabel + echo json_encode(["status" => "error", "message" => "Data dengan ID $id tidak ditemukan di database."]); + } + } else { + // Jika query MySQL gagal/error + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + // Jika JavaScript gagal mengirimkan ID + echo json_encode(["status" => "error", "message" => "ID tidak diterima oleh server PHP."]); +} + +$conn->close(); +?> \ No newline at end of file diff --git a/03/api/prasejahtera/load_prasejahtera.php b/03/api/prasejahtera/load_prasejahtera.php new file mode 100644 index 0000000..2d73d84 --- /dev/null +++ b/03/api/prasejahtera/load_prasejahtera.php @@ -0,0 +1,8 @@ +query("SELECT * FROM rumah_prasejahtera"); +$data = []; +while ($row = $result->fetch_assoc()) $data[] = $row; +echo json_encode($data); +?> \ No newline at end of file diff --git a/03/api/prasejahtera/simpan_prasejahtera.php b/03/api/prasejahtera/simpan_prasejahtera.php new file mode 100644 index 0000000..29afaa9 --- /dev/null +++ b/03/api/prasejahtera/simpan_prasejahtera.php @@ -0,0 +1,40 @@ +prepare("INSERT INTO rumah_prasejahtera + (nama_kepala_keluarga, penghasilan, jumlah_anak, umur_anak, status_ortu, ada_pensiun, nominal_pensiun, ada_terusan, nominal_terusan, keterangan, lat, lng) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + + // PERBAIKAN DI SINI: Jumlah huruf (12) sekarang sudah pas dengan jumlah variabelnya (12) + $stmt->bind_param("sdisssdsdsdd", $nama_kk, $penghasilan, $jumlah_anak, $umur_anak, $status_ortu, $ada_pensiun, $nominal_pensiun, $ada_terusan, $nominal_terusan, $keterangan, $lat, $lng); + + if ($stmt->execute()) { + echo json_encode(["status" => "success", "id" => $stmt->insert_id]); + } else { + echo json_encode(["status" => "error", "message" => $stmt->error]); + } + $stmt->close(); +} else { + echo json_encode(["status" => "error", "message" => "Data tidak lengkap"]); +} +$conn->close(); +?> \ No newline at end of file diff --git a/03/api/prasejahtera/update_prasejahtera.php b/03/api/prasejahtera/update_prasejahtera.php new file mode 100644 index 0000000..b6ef430 --- /dev/null +++ b/03/api/prasejahtera/update_prasejahtera.php @@ -0,0 +1,10 @@ +prepare("UPDATE rumah_prasejahtera SET lat=?, lng=? WHERE id=?"); +$stmt->bind_param("ddi", $data['lat'], $data['lng'], $data['id']); +echo $stmt->execute() + ? json_encode(['status' => 'success']) + : json_encode(['status' => 'error']); +?> \ No newline at end of file diff --git a/03/api/rumahibadah/hapus_ibadah.php b/03/api/rumahibadah/hapus_ibadah.php new file mode 100644 index 0000000..d67571d --- /dev/null +++ b/03/api/rumahibadah/hapus_ibadah.php @@ -0,0 +1,10 @@ +prepare("DELETE FROM rumah_ibadah WHERE id = ?"); +$stmt->bind_param("i", $data['id']); +echo $stmt->execute() + ? json_encode(['status' => 'success']) + : json_encode(['status' => 'error']); +?> \ No newline at end of file diff --git a/03/api/rumahibadah/load_ibadah.php b/03/api/rumahibadah/load_ibadah.php new file mode 100644 index 0000000..af97af8 --- /dev/null +++ b/03/api/rumahibadah/load_ibadah.php @@ -0,0 +1,8 @@ +query("SELECT * FROM rumah_ibadah"); +$data = []; +while ($row = $result->fetch_assoc()) $data[] = $row; +echo json_encode($data); +?> \ No newline at end of file diff --git a/03/api/rumahibadah/simpan_ibadah.php b/03/api/rumahibadah/simpan_ibadah.php new file mode 100644 index 0000000..eefb1f5 --- /dev/null +++ b/03/api/rumahibadah/simpan_ibadah.php @@ -0,0 +1,10 @@ +prepare("INSERT INTO rumah_ibadah (nama_ibadah, jenis, radius_m, lat, lng) VALUES (?, ?, ?, ?, ?)"); +$stmt->bind_param("ssidd", $data['nama_ibadah'], $data['jenis'], $data['radius_m'], $data['lat'], $data['lng']); +echo $stmt->execute() + ? json_encode(['status' => 'success', 'id' => $conn->insert_id]) + : json_encode(['status' => 'error']); +?> \ No newline at end of file diff --git a/03/api/rumahibadah/update_ibadah.php b/03/api/rumahibadah/update_ibadah.php new file mode 100644 index 0000000..a5d7940 --- /dev/null +++ b/03/api/rumahibadah/update_ibadah.php @@ -0,0 +1,10 @@ +prepare("UPDATE rumah_ibadah SET lat=?, lng=? WHERE id=?"); +$stmt->bind_param("ddi", $data['lat'], $data['lng'], $data['id']); +echo $stmt->execute() + ? json_encode(['status' => 'success']) + : json_encode(['status' => 'error']); +?> \ No newline at end of file diff --git a/03/assets/css/style.css b/03/assets/css/style.css new file mode 100644 index 0000000..352ba25 --- /dev/null +++ b/03/assets/css/style.css @@ -0,0 +1,54 @@ +html, body { + height: 100vh; width: 100vw; margin: 0; padding: 0; + display: flex; font-family: Arial, sans-serif; overflow: hidden; +} + +#sidebar { + width: 280px; background-color: #f8f9fa; padding: 20px; + box-shadow: 2px 0 5px rgba(0,0,0,0.2); z-index: 1000; overflow-y: auto; +} + +#sidebar h3 { margin-top: 0; border-bottom: 2px solid #ddd; padding-bottom: 10px; } + +/* Styling Ikon Mode */ +.mode-selector { margin-bottom: 20px; } +.mode-buttons { display: flex; gap: 10px; justify-content: space-between; } +.mode-btn { + flex: 1; padding: 15px 5px; background: #fff; border: 2px solid #ccc; + border-radius: 8px; cursor: pointer; text-align: center; + transition: all 0.3s ease; color: #555; font-size: 12px; font-weight: bold; +} +.mode-btn i { font-size: 24px; margin-bottom: 8px; display: block; } +.mode-btn:hover { background: #e9ecef; } + +/* Warna saat tombol mode aktif */ +.mode-btn.active[data-mode="point"] { border-color: #e74c3c; color: #e74c3c; background: #fadbd8; } +.mode-btn.active[data-mode="polyline"] { border-color: #3498db; color: #3498db; background: #d6eaf8; } +.mode-btn.active[data-mode="polygon"] { border-color: #2ecc71; color: #2ecc71; background: #d5f5e3; } +.mode-btn.active[data-mode="prasejahtera"] { border-color: #c0392b; color: #ffffff; background: #e74c3c; } +.mode-btn.active[data-mode="ibadah"] { border-color: #6c3483; color: #ffffff; background: #8e44ad; } +/* Styling tombol edit aktif */ +.mode-btn.active[data-mode="edit_jalan"] { background-color: #f1c40f !important; color: white !important; } +.mode-btn.active[data-mode="edit_kavling"] { background-color: #27ae60 !important; color: white !important; } + +/* Styling Input & Action Buttons */ +.action-buttons { + display: none; background: #fff; padding: 15px; + border-radius: 8px; border: 1px solid #ddd; margin-top: 10px; margin-bottom: 20px; +} + +.action-buttons input, .action-buttons select { + width: 100%; margin-bottom: 15px; padding: 8px; box-sizing: border-box; + border: 1px solid #ccc; border-radius: 4px; +} + +.action-buttons button { + width: 100%; padding: 10px; margin-bottom: 8px; cursor: pointer; + border-radius: 5px; font-weight: bold; +} + +.btn-success { background-color: #2ecc71; color: white; border: none; } +.btn-danger { background-color: #e74c3c; color: white; border: none; } + +#map-container { flex: 1; position: relative; } +#map { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } \ No newline at end of file diff --git a/03/assets/js/script.js b/03/assets/js/script.js new file mode 100644 index 0000000..481372e --- /dev/null +++ b/03/assets/js/script.js @@ -0,0 +1,519 @@ +// --- INISIALISASI PETA (Koordinat Pontianak) --- +const map = L.map('map').setView([-0.055348, 109.349512], 13); +L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); + +const popup = L.popup(); +const daftarMarker = {}, daftarPolyline = {}, daftarPolygon = {}; + +// 1. BUAT KERTAS TRANSPARAN (LAYER GROUPS) +const layerPrasejahtera = L.layerGroup().addTo(map); +const layerIbadah = L.layerGroup().addTo(map); + +// 2. DAFTARKAN LAYER-LAYER TERSEBUT KE DALAM CONTROL PANEL +const overlays = { + " Rumah Prasejahtera": layerPrasejahtera, + " Rumah Ibadah": layerIbadah, +}; + +// 3. MUNCULKAN KONTROL DI POJOK KANAN ATAS PETA +L.control.layers(null, overlays, { collapsed: false }).addTo(map); + +let currentMode = 'none'; +let tempLatLngs = [], tempPolyline = null; +let tempPolygonLatLngs = [], tempPolygon = null; +let targetEditJalanId = null; // Menyimpan ID jalan yang sedang diedit +let targetEditKavlingId = null; // Menyimpan ID kavling yang sedang diedit + +// Konfigurasi SweetAlert2 (Toast Pojok Kanan Atas) +const Toast = Swal.mixin({ + toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true +}); + +// Definisi Ikon Kustom +const ikonHijau = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); +const ikonMerah = new L.Icon({ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41] }); + +// ikon rumah prasejahtera +const ikonPrasejahtera = L.divIcon({ + html: `
+ +
`, + iconSize: [32, 32], + iconAnchor: [16, 32], + popupAnchor: [0, -34], + className: '' +}); + +const ikonPrasejahteraTercakup = L.divIcon({ + html: `
+ +
`, + iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -34], className: '' +}); + +function buatIkonIbadah(jenis) { + const config = { + 'Masjid': { icon: 'fa-mosque', color: '#1abc9c' }, + 'Gereja': { icon: 'fa-church', color: '#3498db' }, + 'Vihara': { icon: 'fa-torii-gate', color: '#e67e22' }, + 'Pura': { icon: 'fa-om', color: '#9b59b6' }, + 'Klenteng': { icon: 'fa-yin-yang', color: '#e74c3c' }, + }; + const c = config[jenis] || { icon: 'fa-place-of-worship', color: '#7f8c8d' }; + return L.divIcon({ + html: `
+ +
`, + iconSize: [32, 32], iconAnchor: [16, 32], popupAnchor: [0, -34], className: '' + }); +} + +// Simpan data ibadah dan prasejahtera dalam memori untuk kalkulasi +const dataPrasejahtera = {}; // { id: {lat, lng, marker} } +const dataIbadah = {}; // { id: {lat, lng, radius_m, lingkaran} } + +function perbaruiWarnaPrasejahtera() { + for (let pid in dataPrasejahtera) { + let p = dataPrasejahtera[pid]; + let tercakup = false; + + for (let iid in dataIbadah) { + let ib = dataIbadah[iid]; + let jarak = map.distance([p.lat, p.lng], [ib.lat, ib.lng]); + if (jarak <= ib.radius_m) { tercakup = true; break; } + } + + // Ganti ikon sesuai status + let ikonBaru = tercakup ? ikonPrasejahteraTercakup : ikonPrasejahtera; + p.marker.setIcon(ikonBaru); + } +} + +// --- MANAJEMEN MODE UI --- +function ubahMode(mode) { + console.log('Mode diklik:', mode, '| Mode saat ini:', currentMode); // ← tambah ini + + if (currentMode === mode) { + // Jika mode yang diklik sama dengan yang aktif, matikan semua + currentMode = 'none'; // Mode netral + + map.pm.disableGlobalEditMode(); + if (targetEditJalanId) batalkanEditJalan(); + if (targetEditKavlingId) batalkanEditKavling(); + + // Matikan semua highlight tombol aktif + document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); + + // Sembunyikan semua panel sidebar + document.getElementById('polyline-actions').style.display = 'none'; + document.getElementById('polygon-actions').style.display = 'none'; + document.getElementById('edit-jalan-actions').style.display = 'none'; + document.getElementById('edit-kavling-actions').style.display = 'none'; + + map.closePopup(); + batalPolyline(); + batalPolygon(); + return; // Hentikan eksekusi, tidak perlu lanjut ke bawah + } + currentMode = mode; + + // Matikan mode edit Geoman global jika sedang aktif + map.pm.disableGlobalEditMode(); + + // Reset state edit + if (targetEditJalanId) batalkanEditJalan(); + if (targetEditKavlingId) batalkanEditKavling(); + + // Atur tampilan tombol aktif + document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active')); + document.querySelector(`.mode-btn[data-mode="${mode}"]`).classList.add('active'); + + // Munculkan menu yang sesuai di sidebar + document.getElementById('polyline-actions').style.display = (mode === 'polyline') ? 'block' : 'none'; + document.getElementById('polygon-actions').style.display = (mode === 'polygon') ? 'block' : 'none'; + document.getElementById('edit-jalan-actions').style.display = (mode === 'edit_jalan') ? 'block' : 'none'; + document.getElementById('edit-kavling-actions').style.display = (mode === 'edit_kavling') ? 'block' : 'none'; + + map.closePopup(); + if (mode !== 'polyline') batalPolyline(); + if (mode !== 'polygon') batalPolygon(); +} + +// --- LOAD DATA DARI DATABASE --- +function loadIbadah() { + fetch('api/rumahibadah/load_ibadah.php').then(res => res.json()).then(data => { + data.forEach(ib => { + let marker = L.marker([ib.lat, ib.lng], { icon: buatIkonIbadah(ib.jenis), draggable: true }) + .addTo(layerIbadah); + + // Lingkaran radius + let lingkaran = L.circle([ib.lat, ib.lng], { + radius: ib.radius_m, color: '#8e44ad', + fillColor: '#8e44ad', fillOpacity: 0.1, weight: 1, dashArray: '5,5' + }).addTo(layerIbadah); + + marker.bindPopup(`${ib.nama_ibadah}
Jenis Rumah Ibadah: ${ib.jenis}
Jangkauan Bantuan: ${ib.radius_m} m
+ `); + + marker.on('dragend', function(e) { + let pos = e.target.getLatLng(); + lingkaran.setLatLng(pos); + dataIbadah[ib.id] = { lat: pos.lat, lng: pos.lng, radius_m: ib.radius_m, lingkaran }; + updateLokasiIbadah(ib.id, pos.lat, pos.lng); + perbaruiWarnaPrasejahtera(); + }); + + dataIbadah[ib.id] = { lat: ib.lat, lng: ib.lng, radius_m: ib.radius_m, lingkaran }; + daftarMarker['ib_' + ib.id] = marker; + }); + + // Setelah semua ibadah dimuat, perbarui warna prasejahtera + perbaruiWarnaPrasejahtera(); + }); +} + +function formatRupiah(angka) { + return new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR' }).format(angka); +} + +function loadDB() { + fetch('api/prasejahtera/load_prasejahtera.php').then(res => res.json()).then(data => { + data.forEach(rumah => { + // PERBAIKAN: Gunakan 'rumah.', bukan 'data.' + let penghasilanTeks = formatRupiah(rumah.penghasilan); + let statusOrtuTeks = `Status Ortu: ${rumah.status_ortu}
`; + + if (rumah.status_ortu === 'Bekerja') { + statusOrtuTeks += `Penghasilan: ${formatRupiah(rumah.penghasilan)}
`; + } else if (rumah.status_ortu === 'Pensiun') { + statusOrtuTeks += `Uang Pensiun: ${rumah.ada_pensiun === 'Ya' ? formatRupiah(rumah.nominal_pensiun) : 'Tidak Ada'}
`; + } else if (rumah.status_ortu === 'Meninggal') { + statusOrtuTeks += `Uang Terusan: ${rumah.ada_terusan === 'Ya' ? formatRupiah(rumah.nominal_terusan) : 'Tidak Ada'}
`; + } + + let isiPopup = ` +
+ ${rumah.nama_kepala_keluarga}
+ Anak: ${rumah.jumlah_anak} orang (Umur: ${rumah.umur_anak || '-'})
+ ${statusOrtuTeks} + Keterangan: ${rumah.keterangan || '-'}
+
+ +
+ `; + + let marker = L.marker([rumah.lat, rumah.lng], { icon: ikonPrasejahtera, draggable: true }) + .addTo(layerPrasejahtera); + marker.bindPopup(isiPopup); + marker.on('dragend', function(e) { + let pos = e.target.getLatLng(); + dataPrasejahtera[rumah.id].lat = pos.lat; + dataPrasejahtera[rumah.id].lng = pos.lng; + updateLokasiPrasejahtera(rumah.id, pos.lat, pos.lng); + perbaruiWarnaPrasejahtera(); + }); + dataPrasejahtera[rumah.id] = { lat: rumah.lat, lng: rumah.lng, marker }; + daftarMarker['p_' + rumah.id] = marker; + }); + + // Muat ibadah SETELAH prasejahtera selesai + loadIbadah(); + }); +} +loadDB(); + +// --- FUNGSI UNTUK ME-REFRESH PETA SECARA OTOMATIS (SOFT REFRESH) --- +function refreshPeta() { + layerPrasejahtera.clearLayers(); + layerIbadah.clearLayers(); + + // 2. Bersihkan memori agar ID tidak menumpuk/ganda + for (let id in dataIbadah) delete dataIbadah[id]; + for (let id in dataPrasejahtera) delete dataPrasejahtera[id]; + + // 3. Panggil ulang data terbaru dari database + loadDB(); +} + +// --- EVENT KLIK PETA --- +function onMapClick(e) { + if (currentMode === 'point') { + let formHtml = ` +
+

SPBU Baru

+
+ + +
+   +

+ +
+
`; + popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); + } else if (currentMode === 'polyline') { + tempLatLngs.push(e.latlng); + if (!tempPolyline) tempPolyline = L.polyline(tempLatLngs, {color: '#3498db', weight: 4}).addTo(map); + else tempPolyline.setLatLngs(tempLatLngs); + } else if (currentMode === 'polygon') { + tempPolygonLatLngs.push(e.latlng); + if (!tempPolygon) tempPolygon = L.polygon(tempPolygonLatLngs, {color: '#2ecc71', fillColor: '#2ecc71', fillOpacity: 0.4}).addTo(map); + else tempPolygon.setLatLngs(tempPolygonLatLngs); + } else if (currentMode === 'prasejahtera') { + let formHtml = ` +
+

Data Prasejahtera

+ + + + + + + + + + + + + +
+ + +
+ + + + + + + + + +
+ `; + popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); + } else if (currentMode === 'ibadah') { + let formHtml = ` +
+

+ Rumah Ibadah +

+
+ + + + + + +
+
`; + popup.setLatLng(e.latlng).setContent(formHtml).openOn(map); + } +} +map.on('click', onMapClick); + +function simpanPrasejahtera(lat, lng) { + // 1. Validasi Manual + let nama_kk = document.getElementById('nama_kk').value.trim(); + if (!nama_kk || nama_kk === "") { + Toast.fire({ icon: 'warning', title: 'Nama Kepala Keluarga wajib diisi!' }); + return; // Hentikan proses eksekusi jika kosong + } + + // 2. Susun Data + let dataKirim = { + lat: lat, lng: lng, + nama_kk: nama_kk, + penghasilan: parseInt(document.getElementById('penghasilan').value) || 0, + jumlah_anak: parseInt(document.getElementById('jumlah_anak').value) || 0, + umur_anak: document.getElementById('umur_anak').value, + status_ortu: document.getElementById('status_ortu').value, + ada_pensiun: document.getElementById('ada_pensiun').value, + nominal_pensiun: parseInt(document.getElementById('nominal_pensiun').value) || 0, + ada_terusan: document.getElementById('ada_terusan').value, + nominal_terusan: parseInt(document.getElementById('nominal_terusan').value) || 0, + keterangan: document.getElementById('keterangan').value + }; + + // 3. Kirim ke Database + fetch('api/prasejahtera/simpan_prasejahtera.php', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(dataKirim) + }) + .then(res => res.json()) + .then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'Data Berhasil Disimpan!' }); + map.closePopup(); + refreshPeta(); // Sinkronisasi seketika + } else { + Toast.fire({ icon: 'error', title: 'Gagal Menyimpan: ' + (data.message || 'Error Database') }); + } + }) + .catch(error => { + console.error("Terjadi masalah:", error); + Toast.fire({ icon: 'error', title: 'Koneksi ke Server Gagal!' }); + }); +} + +function hapusPrasejahtera(id) { + Swal.fire({ title: 'Hapus data ini?', icon: 'warning', showCancelButton: true, + confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', + confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' + }).then(result => { + if (result.isConfirmed) { + fetch('api/prasejahtera/hapus_prasejahtera.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id: id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'Data Dihapus!' }); + + // Cukup panggil fungsi ini, Leaflet akan menyapu bersih peta + // dan memanggil ulang data terbaru dari database secara otomatis! + refreshPeta(); + + } else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +function updateLokasiPrasejahtera(id, lat, lng) { + fetch('api/prasejahtera/update_prasejahtera.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id, lat, lng }) }) + .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); +} + +function simpanIbadah(event, lat, lng) { + event.preventDefault(); + let dataKirim = { + lat, lng, + nama_ibadah: document.getElementById('nama_ibadah').value, + jenis: document.getElementById('jenis_ibadah').value, + radius_m: parseInt(document.getElementById('radius_ibadah').value) + }; + fetch('api/rumahibadah/simpan_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(dataKirim) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'Rumah Ibadah Disimpan!' }); + map.closePopup(); + refreshPeta(); + } else Toast.fire({ icon: 'error', title: 'Gagal Menyimpan!' }); + }); +} + +function hapusIbadah(id) { + Swal.fire({ title: 'Hapus Rumah Ibadah?', icon: 'warning', showCancelButton: true, + confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', + confirmButtonText: 'Ya, Hapus!', cancelButtonText: 'Batal' + }).then(result => { + if (result.isConfirmed) { + fetch('api/rumahibadah/hapus_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id }) }) + .then(res => res.json()).then(data => { + if (data.status === 'success') { + Toast.fire({ icon: 'success', title: 'Data Dihapus!' }); + + // Gunakan refreshPeta agar sinkron dengan database 100% + refreshPeta(); + + } else Toast.fire({ icon: 'error', title: 'Gagal Menghapus!' }); + }); + } + }); +} + +function updateLokasiIbadah(id, lat, lng) { + fetch('api/rumahibadah/update_ibadah.php', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ id, lat, lng }) }) + .then(res => res.json()).then(data => { if (data.status === 'success') Toast.fire({ icon: 'info', title: 'Posisi Diperbarui' }); }); +} + +// --- FUNGSI TOGGLE FORM PRASEJAHTERA --- +function toggleStatusOrtu() { + let status = document.getElementById('status_ortu').value; + + // Atur visibilitas grup + document.getElementById('grup_bekerja').style.display = (status === 'Bekerja') ? 'block' : 'none'; + document.getElementById('grup_pensiun').style.display = (status === 'Pensiun') ? 'block' : 'none'; + document.getElementById('grup_meninggal').style.display = (status === 'Meninggal') ? 'block' : 'none'; + + // Reset nilai yang tersembunyi agar tidak ikut tersimpan salah + if(status !== 'Bekerja') document.getElementById('penghasilan').value = 0; + if(status !== 'Pensiun') { document.getElementById('ada_pensiun').value = 'Tidak'; togglePensiun(); } + if(status !== 'Meninggal') { document.getElementById('ada_terusan').value = 'Tidak'; toggleTerusan(); } +} + +function togglePensiun() { + let ada = document.getElementById('ada_pensiun').value; + document.getElementById('grup_nominal_pensiun').style.display = (ada === 'Ya') ? 'block' : 'none'; + if(ada === 'Tidak') document.getElementById('nominal_pensiun').value = 0; +} + +function toggleTerusan() { + let ada = document.getElementById('ada_terusan').value; + document.getElementById('grup_nominal_terusan').style.display = (ada === 'Ya') ? 'block' : 'none'; + if(ada === 'Tidak') document.getElementById('nominal_terusan').value = 0; +} \ No newline at end of file diff --git a/03/index.html b/03/index.html new file mode 100644 index 0000000..e31f272 --- /dev/null +++ b/03/index.html @@ -0,0 +1,37 @@ + + + + + + Sistem Pemetaan + + + + + + + + + + + + +
+
+
+ + + + + + + \ No newline at end of file