Files

1178 lines
36 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>WebGIS Manajemen Jalan dan Parsil Tanah</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- LeafletJS CDN -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<!-- Leaflet Draw CDN -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<style>
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
font-family: Arial, Helvetica, sans-serif;
background: #f3f4f6;
}
header {
height: 50px;
padding: 6px 14px;
background: #1f2937;
color: white;
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
}
header h1 {
margin: 0;
font-size: 16px;
font-weight: 700;
}
header p {
margin: 0;
font-size: 12px;
color: #d1d5db;
}
#map {
width: 100%;
height: calc(100vh - 50px);
}
.popup-title {
margin: 0 0 8px;
font-size: 15px;
font-weight: bold;
color: #111827;
}
.popup-row {
margin-bottom: 8px;
}
.popup-row label {
display: block;
margin-bottom: 4px;
font-size: 12px;
font-weight: bold;
color: #374151;
}
.popup-row input,
.popup-row select {
width: 100%;
padding: 7px;
border: 1px solid #d1d5db;
border-radius: 5px;
font-size: 13px;
}
.popup-info {
padding: 7px;
background: #f3f4f6;
border-radius: 5px;
font-size: 12px;
color: #374151;
}
.popup-detail {
min-width: 230px;
font-size: 13px;
line-height: 1.5;
}
.popup-detail b {
color: #111827;
}
.popup-actions {
margin-top: 10px;
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.btn {
border: none;
border-radius: 5px;
padding: 7px 10px;
font-size: 12px;
cursor: pointer;
color: white;
}
.btn-simpan {
background: #16a34a;
}
.btn-edit {
background: #2563eb;
}
.btn-hapus {
background: #dc2626;
}
.btn-batal {
background: #6b7280;
}
.btn:hover {
opacity: 0.9;
}
#legend {
position: absolute;
right: 14px;
bottom: 24px;
z-index: 1000;
background: white;
border-radius: 8px;
padding: 10px 12px;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
font-size: 12px;
max-width: 260px;
}
#legend h3 {
margin: 0 0 7px;
font-size: 13px;
}
#legend .section-title {
margin-top: 8px;
margin-bottom: 4px;
font-weight: bold;
color: #111827;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 4px;
gap: 6px;
}
.legend-line {
width: 22px;
height: 4px;
border-radius: 3px;
}
.legend-box {
width: 14px;
height: 14px;
border-radius: 3px;
opacity: 0.75;
border: 1px solid #11182733;
}
#toast {
position: absolute;
left: 50%;
top: 62px;
transform: translateX(-50%);
z-index: 2000;
background: #111827;
color: white;
padding: 10px 14px;
border-radius: 8px;
font-size: 13px;
display: none;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.25);
}
#loading {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
z-index: 2000;
background: rgba(17, 24, 39, 0.9);
color: white;
padding: 14px 18px;
border-radius: 10px;
display: none;
align-items: center;
gap: 10px;
font-size: 13px;
}
.spinner {
width: 18px;
height: 18px;
border: 3px solid #9ca3af;
border-top: 3px solid white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.leaflet-popup-content {
min-width: 230px;
}
@media (max-width: 600px) {
header h1 {
font-size: 14px;
}
header p {
font-size: 10px;
}
#legend {
left: 10px;
right: 10px;
bottom: 15px;
max-width: none;
}
}
</style>
</head>
<body>
<header>
<h1>WebGIS Manajemen Jalan dan Parsil Tanah</h1>
<p>Gunakan toolbar gambar untuk membuat line/polyline jalan dan polygon parsil tanah.</p>
</header>
<div id="map"></div>
<div id="legend">
<h3>Legenda</h3>
<div class="section-title">Status Jalan</div>
<div class="legend-item">
<span class="legend-line" style="background:#e11d48;"></span>
Jalan Nasional
</div>
<div class="legend-item">
<span class="legend-line" style="background:#2563eb;"></span>
Jalan Provinsi
</div>
<div class="legend-item">
<span class="legend-line" style="background:#16a34a;"></span>
Jalan Kabupaten
</div>
<div class="section-title">Status Kepemilikan Tanah</div>
<div class="legend-item">
<span class="legend-box" style="background:#16a34a;"></span>
SHM
</div>
<div class="legend-item">
<span class="legend-box" style="background:#2563eb;"></span>
HGB
</div>
<div class="legend-item">
<span class="legend-box" style="background:#f97316;"></span>
HGU
</div>
<div class="legend-item">
<span class="legend-box" style="background:#7c3aed;"></span>
HP
</div>
</div>
<div id="toast"></div>
<div id="loading">
<div class="spinner"></div>
<span>Memproses data...</span>
</div>
<script>
// Cek Leaflet sebelum peta diinisialisasi.
if (typeof L === 'undefined') {
alert('LeafletJS gagal dimuat. Pastikan koneksi internet aktif karena CDN digunakan.');
} else {
// Variabel utama peta dan penampung layer.
const peta = L.map('map').setView([-0.05566422376778593, 109.34854622732368], 17);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 22,
attribution: '&copy; OpenStreetMap contributors'
}).addTo(peta);
// Data layer disimpan dalam object dengan key = id.
let jalanMap = {};
let parsilMap = {};
let jalanDataMap = {};
let parsilDataMap = {};
// Layer sementara ketika user menggambar, sebelum data disimpan.
let tempLayers = {};
let tempCounter = 0;
const drawnItems = new L.FeatureGroup();
peta.addLayer(drawnItems);
// Toolbar Leaflet Draw hanya mengaktifkan polyline dan polygon.
const drawControl = new L.Control.Draw({
position: 'topleft',
draw: {
polyline: {
shapeOptions: {
color: '#111827',
weight: 4,
opacity: 0.9
}
},
polygon: {
allowIntersection: false,
showArea: true,
shapeOptions: {
color: '#111827',
weight: 2,
fillOpacity: 0.25
}
},
rectangle: false,
circle: false,
marker: false,
circlemarker: false
},
edit: false
});
peta.addControl(drawControl);
// Event ketika user selesai menggambar.
peta.on(L.Draw.Event.CREATED, function (event) {
const layer = event.layer;
const layerType = event.layerType;
layer.addTo(peta);
if (layerType === 'polyline') {
showTambahJalanPopup(layer);
}
if (layerType === 'polygon') {
showTambahParsilPopup(layer);
}
});
// Jika popup tambah ditutup tanpa simpan, layer sementara dihapus.
peta.on('popupclose', function (event) {
const content = event.popup.getContent();
if (typeof content === 'string') {
const match = content.match(/data-temp-key="([^"]+)"/);
if (match && tempLayers[match[1]]) {
peta.removeLayer(tempLayers[match[1]]);
delete tempLayers[match[1]];
}
}
});
// Load data ketika halaman pertama dibuka.
loadData();
// =========================
// Helper Tampilan
// =========================
function showLoading(show) {
document.getElementById('loading').style.display = show ? 'flex' : 'none';
}
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.display = 'block';
setTimeout(function () {
toast.style.display = 'none';
}, 2500);
}
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;');
}
function escapeAttr(value) {
return escapeHtml(value).replaceAll('`', '&#096;');
}
function formatNumber(value) {
const number = Number(value);
if (Number.isNaN(number)) {
return '0.00';
}
return number.toLocaleString('id-ID', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
function getLayerCenter(layer) {
if (layer.getBounds) {
return layer.getBounds().getCenter();
}
if (layer.getLatLng) {
return layer.getLatLng();
}
return peta.getCenter();
}
function getGeojsonObject(geojson) {
if (typeof geojson === 'string') {
return JSON.parse(geojson);
}
return geojson;
}
function ringkasanGeojson(geojson) {
try {
const obj = getGeojsonObject(geojson);
const geom = obj.type === 'Feature' ? obj.geometry : obj;
if (!geom) {
return 'Geometry tidak terbaca.';
}
if (geom.type === 'LineString') {
return `${geom.coordinates.length} titik koordinat LineString.`;
}
if (geom.type === 'MultiLineString') {
const total = geom.coordinates.reduce((sum, line) => sum + line.length, 0);
return `${total} titik koordinat MultiLineString.`;
}
if (geom.type === 'Polygon') {
const total = geom.coordinates[0] ? geom.coordinates[0].length : 0;
return `${total} titik koordinat Polygon.`;
}
if (geom.type === 'MultiPolygon') {
let total = 0;
geom.coordinates.forEach(function (polygon) {
if (polygon[0]) {
total += polygon[0].length;
}
});
return `${total} titik koordinat MultiPolygon.`;
}
return `Tipe geometry: ${geom.type}.`;
} catch (error) {
return 'GeoJSON tidak valid.';
}
}
// =========================
// Warna Layer
// =========================
function getWarnaJalan(status) {
if (status === 'nasional') {
return '#e11d48'; // merah
}
if (status === 'provinsi') {
return '#2563eb'; // biru
}
if (status === 'kabupaten') {
return '#16a34a'; // hijau
}
return '#111827';
}
function getWarnaParsil(status) {
if (status === 'SHM') {
return '#16a34a'; // hijau
}
if (status === 'HGB') {
return '#2563eb'; // biru
}
if (status === 'HGU') {
return '#f97316'; // orange
}
if (status === 'HP') {
return '#7c3aed'; // ungu
}
return '#111827';
}
// =========================
// Hitung Otomatis Geometry
// =========================
function flattenLatLngs(latlngs) {
const result = [];
latlngs.forEach(function (item) {
if (Array.isArray(item)) {
result.push(...flattenLatLngs(item));
} else {
result.push(item);
}
});
return result;
}
function hitungPanjangPolyline(layer) {
const latlngs = flattenLatLngs(layer.getLatLngs());
let total = 0;
for (let i = 1; i < latlngs.length; i++) {
// Fungsi distanceTo() berasal dari LeafletJS.
total += latlngs[i - 1].distanceTo(latlngs[i]);
}
return Math.round(total * 100) / 100;
}
function hitungLuasPolygon(layer) {
const latlngs = layer.getLatLngs();
const outerRing = Array.isArray(latlngs[0]) ? latlngs[0] : latlngs;
// Fungsi geodesicArea tersedia dari Leaflet Draw / GeometryUtil.
if (L.GeometryUtil && typeof L.GeometryUtil.geodesicArea === 'function') {
return Math.round(Math.abs(L.GeometryUtil.geodesicArea(outerRing)) * 100) / 100;
}
// Fallback sederhana jika GeometryUtil tidak tersedia.
// Perhitungan ini hanya cadangan, bukan perhitungan utama.
let area = 0;
const earthRadius = 6378137;
for (let i = 0; i < outerRing.length; i++) {
const p1 = outerRing[i];
const p2 = outerRing[(i + 1) % outerRing.length];
area += (p2.lng - p1.lng) * Math.PI / 180 *
(2 + Math.sin(p1.lat * Math.PI / 180) + Math.sin(p2.lat * Math.PI / 180));
}
area = area * earthRadius * earthRadius / 2;
return Math.round(Math.abs(area) * 100) / 100;
}
// =========================
// Fetch API
// =========================
async function apiPost(url, formData) {
showLoading(true);
try {
const response = await fetch(url, {
method: 'POST',
body: formData
});
const text = await response.text();
let result;
try {
result = JSON.parse(text);
} catch (error) {
throw new Error('Response server bukan JSON: ' + text);
}
if (!response.ok || !result.success) {
throw new Error(result.message || 'Terjadi kesalahan pada server.');
}
return result;
} finally {
showLoading(false);
}
}
async function loadData() {
showLoading(true);
try {
const response = await fetch('ambil_data.php');
const result = await response.json();
if (!result.success) {
throw new Error(result.message || 'Gagal mengambil data.');
}
// Bersihkan layer lama dari peta.
Object.values(jalanMap).forEach(function (layer) {
peta.removeLayer(layer);
});
Object.values(parsilMap).forEach(function (layer) {
peta.removeLayer(layer);
});
jalanMap = {};
parsilMap = {};
jalanDataMap = {};
parsilDataMap = {};
result.jalan.forEach(function (item) {
renderJalan(item);
});
result.parsil.forEach(function (item) {
renderParsil(item);
});
} catch (error) {
showToast(error.message);
} finally {
showLoading(false);
}
}
// =========================
// Render Layer Jalan
// =========================
function renderJalan(data) {
try {
const geojson = getGeojsonObject(data.geojson);
const layer = L.geoJSON(geojson, {
style: function () {
return {
color: getWarnaJalan(data.status_jalan),
weight: 5,
opacity: 0.9
};
}
});
layer.eachLayer(function (singleLayer) {
singleLayer.on('click', function (event) {
showDetailJalanPopup(data, event.latlng);
});
});
layer.addTo(peta);
jalanMap[data.id] = layer;
jalanDataMap[data.id] = data;
} catch (error) {
console.error('Gagal render jalan:', error, data);
}
}
function showTambahJalanPopup(layer) {
const tempKey = 'temp_jalan_' + (++tempCounter);
tempLayers[tempKey] = layer;
const panjang = hitungPanjangPolyline(layer);
const center = getLayerCenter(layer);
const content = `
<form id="formTambahJalan" data-temp-key="${tempKey}">
<h3 class="popup-title">Tambah Data Jalan</h3>
<div class="popup-row">
<label>Nama Jalan</label>
<input type="text" name="nama_jalan" required placeholder="Contoh: Jalan Merdeka">
</div>
<div class="popup-row">
<label>Status Jalan</label>
<select name="status_jalan" required>
<option value="">-- Pilih Status --</option>
<option value="nasional">Jalan Nasional</option>
<option value="provinsi">Jalan Provinsi</option>
<option value="kabupaten">Jalan Kabupaten</option>
</select>
</div>
<div class="popup-row">
<label>Panjang Otomatis</label>
<div class="popup-info">${formatNumber(panjang)} meter</div>
</div>
<div class="popup-actions">
<button type="submit" class="btn btn-simpan">Simpan</button>
<button type="button" class="btn btn-batal" onclick="batalTambah('${tempKey}')">Batal</button>
</div>
</form>
`;
L.popup()
.setLatLng(center)
.setContent(content)
.openOn(peta);
}
function showDetailJalanPopup(data, latlng) {
const content = `
<div class="popup-detail">
<h3 class="popup-title">Detail Data Jalan</h3>
<div><b>Nama Jalan:</b> ${escapeHtml(data.nama_jalan)}</div>
<div><b>Status:</b> ${escapeHtml(labelStatusJalan(data.status_jalan))}</div>
<div><b>Panjang:</b> ${formatNumber(data.panjang_meter)} meter</div>
<div><b>GeoJSON:</b> ${escapeHtml(ringkasanGeojson(data.geojson))}</div>
<div class="popup-actions">
<button type="button" class="btn btn-edit" onclick="showEditJalanPopup(${Number(data.id)})">Edit</button>
<button type="button" class="btn btn-hapus" onclick="hapusData('jalan', ${Number(data.id)})">Hapus</button>
</div>
</div>
`;
L.popup()
.setLatLng(latlng)
.setContent(content)
.openOn(peta);
}
function showEditJalanPopup(id) {
const data = jalanDataMap[id];
if (!data) {
showToast('Data jalan tidak ditemukan.');
return;
}
const layer = jalanMap[id];
const center = getLayerCenter(layer);
const content = `
<form id="formEditJalan" data-id="${Number(id)}">
<h3 class="popup-title">Edit Data Jalan</h3>
<div class="popup-row">
<label>Nama Jalan</label>
<input type="text" name="nama_jalan" required value="${escapeAttr(data.nama_jalan)}">
</div>
<div class="popup-row">
<label>Status Jalan</label>
<select name="status_jalan" required>
<option value="nasional" ${data.status_jalan === 'nasional' ? 'selected' : ''}>Jalan Nasional</option>
<option value="provinsi" ${data.status_jalan === 'provinsi' ? 'selected' : ''}>Jalan Provinsi</option>
<option value="kabupaten" ${data.status_jalan === 'kabupaten' ? 'selected' : ''}>Jalan Kabupaten</option>
</select>
</div>
<div class="popup-row">
<label>Panjang</label>
<div class="popup-info">${formatNumber(data.panjang_meter)} meter<br>Tidak dapat diedit manual.</div>
</div>
<div class="popup-actions">
<button type="submit" class="btn btn-edit">Edit</button>
<button type="button" class="btn btn-batal" onclick="peta.closePopup()">Batal</button>
</div>
</form>
`;
L.popup()
.setLatLng(center)
.setContent(content)
.openOn(peta);
}
function labelStatusJalan(status) {
if (status === 'nasional') {
return 'Jalan Nasional';
}
if (status === 'provinsi') {
return 'Jalan Provinsi';
}
if (status === 'kabupaten') {
return 'Jalan Kabupaten';
}
return status;
}
// =========================
// Render Layer Parsil
// =========================
function renderParsil(data) {
try {
const geojson = getGeojsonObject(data.geojson);
const layer = L.geoJSON(geojson, {
style: function () {
return {
color: getWarnaParsil(data.status_kepemilikan),
weight: 2,
opacity: 0.9,
fillColor: getWarnaParsil(data.status_kepemilikan),
fillOpacity: 0.35
};
}
});
layer.eachLayer(function (singleLayer) {
singleLayer.on('click', function (event) {
showDetailParsilPopup(data, event.latlng);
});
});
layer.addTo(peta);
parsilMap[data.id] = layer;
parsilDataMap[data.id] = data;
} catch (error) {
console.error('Gagal render parsil:', error, data);
}
}
function showTambahParsilPopup(layer) {
const tempKey = 'temp_parsil_' + (++tempCounter);
tempLayers[tempKey] = layer;
const luas = hitungLuasPolygon(layer);
const center = getLayerCenter(layer);
const content = `
<form id="formTambahParsil" data-temp-key="${tempKey}">
<h3 class="popup-title">Tambah Data Parsil Tanah</h3>
<div class="popup-row">
<label>Nama/Kode Parsil</label>
<input type="text" name="nama_parsil" required placeholder="Contoh: Kavling A1">
</div>
<div class="popup-row">
<label>Status Kepemilikan</label>
<select name="status_kepemilikan" required>
<option value="">-- Pilih Status --</option>
<option value="SHM">SHM - Sertifikat Hak Milik</option>
<option value="HGB">HGB - Sertifikat Hak Guna Bangunan</option>
<option value="HGU">HGU - Sertifikat Hak Guna Usaha</option>
<option value="HP">HP - Sertifikat Hak Pakai</option>
</select>
</div>
<div class="popup-row">
<label>Luas Otomatis</label>
<div class="popup-info">${formatNumber(luas)} meter persegi</div>
</div>
<div class="popup-actions">
<button type="submit" class="btn btn-simpan">Simpan</button>
<button type="button" class="btn btn-batal" onclick="batalTambah('${tempKey}')">Batal</button>
</div>
</form>
`;
L.popup()
.setLatLng(center)
.setContent(content)
.openOn(peta);
}
function showDetailParsilPopup(data, latlng) {
const content = `
<div class="popup-detail">
<h3 class="popup-title">Detail Data Parsil Tanah</h3>
<div><b>Nama Parsil:</b> ${escapeHtml(data.nama_parsil)}</div>
<div><b>Status:</b> ${escapeHtml(labelStatusParsil(data.status_kepemilikan))}</div>
<div><b>Luas:</b> ${formatNumber(data.luas_meter)} m²</div>
<div><b>GeoJSON:</b> ${escapeHtml(ringkasanGeojson(data.geojson))}</div>
<div class="popup-actions">
<button type="button" class="btn btn-edit" onclick="showEditParsilPopup(${Number(data.id)})">Edit</button>
<button type="button" class="btn btn-hapus" onclick="hapusData('parsil', ${Number(data.id)})">Hapus</button>
</div>
</div>
`;
L.popup()
.setLatLng(latlng)
.setContent(content)
.openOn(peta);
}
function showEditParsilPopup(id) {
const data = parsilDataMap[id];
if (!data) {
showToast('Data parsil tanah tidak ditemukan.');
return;
}
const layer = parsilMap[id];
const center = getLayerCenter(layer);
const content = `
<form id="formEditParsil" data-id="${Number(id)}">
<h3 class="popup-title">Edit Data Parsil Tanah</h3>
<div class="popup-row">
<label>Nama/Kode Parsil</label>
<input type="text" name="nama_parsil" required value="${escapeAttr(data.nama_parsil)}">
</div>
<div class="popup-row">
<label>Status Kepemilikan</label>
<select name="status_kepemilikan" required>
<option value="SHM" ${data.status_kepemilikan === 'SHM' ? 'selected' : ''}>SHM - Sertifikat Hak Milik</option>
<option value="HGB" ${data.status_kepemilikan === 'HGB' ? 'selected' : ''}>HGB - Sertifikat Hak Guna Bangunan</option>
<option value="HGU" ${data.status_kepemilikan === 'HGU' ? 'selected' : ''}>HGU - Sertifikat Hak Guna Usaha</option>
<option value="HP" ${data.status_kepemilikan === 'HP' ? 'selected' : ''}>HP - Sertifikat Hak Pakai</option>
</select>
</div>
<div class="popup-row">
<label>Luas</label>
<div class="popup-info">${formatNumber(data.luas_meter)} m²<br>Tidak dapat diedit manual.</div>
</div>
<div class="popup-actions">
<button type="submit" class="btn btn-edit">Edit</button>
<button type="button" class="btn btn-batal" onclick="peta.closePopup()">Batal</button>
</div>
</form>
`;
L.popup()
.setLatLng(center)
.setContent(content)
.openOn(peta);
}
function labelStatusParsil(status) {
if (status === 'SHM') {
return 'SHM - Sertifikat Hak Milik';
}
if (status === 'HGB') {
return 'HGB - Sertifikat Hak Guna Bangunan';
}
if (status === 'HGU') {
return 'HGU - Sertifikat Hak Guna Usaha';
}
if (status === 'HP') {
return 'HP - Sertifikat Hak Pakai';
}
return status;
}
// =========================
// Event Submit Form Popup
// =========================
document.addEventListener('submit', async function (event) {
event.preventDefault();
const form = event.target;
if (form.id === 'formTambahJalan') {
await prosesTambahJalan(form);
}
if (form.id === 'formEditJalan') {
await prosesEditJalan(form);
}
if (form.id === 'formTambahParsil') {
await prosesTambahParsil(form);
}
if (form.id === 'formEditParsil') {
await prosesEditParsil(form);
}
});
async function prosesTambahJalan(form) {
const tempKey = form.dataset.tempKey;
const layer = tempLayers[tempKey];
if (!layer) {
showToast('Layer gambar tidak ditemukan.');
return;
}
const panjang = hitungPanjangPolyline(layer);
const geojson = JSON.stringify(layer.toGeoJSON());
const formData = new FormData(form);
formData.append('tipe', 'jalan');
formData.append('panjang_meter', panjang);
formData.append('geojson', geojson);
try {
const result = await apiPost('tambah.php', formData);
// Hapus layer sementara, lalu render layer dari data database.
peta.removeLayer(layer);
delete tempLayers[tempKey];
renderJalan(result.data);
peta.closePopup();
showToast(result.message);
} catch (error) {
showToast(error.message);
}
}
async function prosesEditJalan(form) {
const id = Number(form.dataset.id);
const formData = new FormData(form);
formData.append('tipe', 'jalan');
formData.append('id', id);
try {
const result = await apiPost('edit.php', formData);
peta.closePopup();
showToast(result.message);
// Load ulang data tanpa reload halaman agar warna layer ikut berubah.
await loadData();
} catch (error) {
showToast(error.message);
}
}
async function prosesTambahParsil(form) {
const tempKey = form.dataset.tempKey;
const layer = tempLayers[tempKey];
if (!layer) {
showToast('Layer gambar tidak ditemukan.');
return;
}
const luas = hitungLuasPolygon(layer);
const geojson = JSON.stringify(layer.toGeoJSON());
const formData = new FormData(form);
formData.append('tipe', 'parsil');
formData.append('luas_meter', luas);
formData.append('geojson', geojson);
try {
const result = await apiPost('tambah.php', formData);
// Hapus layer sementara, lalu render layer dari data database.
peta.removeLayer(layer);
delete tempLayers[tempKey];
renderParsil(result.data);
peta.closePopup();
showToast(result.message);
} catch (error) {
showToast(error.message);
}
}
async function prosesEditParsil(form) {
const id = Number(form.dataset.id);
const formData = new FormData(form);
formData.append('tipe', 'parsil');
formData.append('id', id);
try {
const result = await apiPost('edit.php', formData);
peta.closePopup();
showToast(result.message);
// Load ulang data tanpa reload halaman agar warna polygon ikut berubah.
await loadData();
} catch (error) {
showToast(error.message);
}
}
// =========================
// Hapus dan Batal
// =========================
async function hapusData(tipe, id) {
const label = tipe === 'jalan' ? 'jalan' : 'parsil tanah';
if (!confirm(`Yakin ingin menghapus data ${label} ini?`)) {
return;
}
const formData = new FormData();
formData.append('tipe', tipe);
formData.append('id', id);
try {
const result = await apiPost('hapus.php', formData);
if (tipe === 'jalan' && jalanMap[id]) {
peta.removeLayer(jalanMap[id]);
delete jalanMap[id];
delete jalanDataMap[id];
}
if (tipe === 'parsil' && parsilMap[id]) {
peta.removeLayer(parsilMap[id]);
delete parsilMap[id];
delete parsilDataMap[id];
}
peta.closePopup();
showToast(result.message);
} catch (error) {
showToast(error.message);
}
}
function batalTambah(tempKey) {
if (tempLayers[tempKey]) {
peta.removeLayer(tempLayers[tempKey]);
delete tempLayers[tempKey];
}
peta.closePopup();
}
// Fungsi dibuat global agar bisa dipanggil dari tombol popup.
window.showEditJalanPopup = showEditJalanPopup;
window.showEditParsilPopup = showEditParsilPopup;
window.hapusData = hapusData;
window.batalTambah = batalTambah;
window.peta = peta;
}
</script>
</body>
</html>